Merge "Add ImageDecoder.ImageInfo.getMimeType"
diff --git a/Android.bp b/Android.bp
index 2dcbc92..b9a8dec 100644
--- a/Android.bp
+++ b/Android.bp
@@ -256,6 +256,8 @@
         "core/java/android/service/euicc/IGetEidCallback.aidl",
         "core/java/android/service/euicc/IGetEuiccInfoCallback.aidl",
         "core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl",
+        "core/java/android/service/euicc/IGetOtaStatusCallback.aidl",
+        "core/java/android/service/euicc/IOtaStatusChangedCallback.aidl",
         "core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl",
         "core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl",
         "core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl",
@@ -721,16 +723,26 @@
     ],
 
     srcs: [
+        "core/proto/android/os/batterytype.proto",
         "core/proto/android/os/cpufreq.proto",
         "core/proto/android/os/cpuinfo.proto",
         "core/proto/android/os/kernelwake.proto",
         "core/proto/android/os/pagetypeinfo.proto",
         "core/proto/android/os/procrank.proto",
+        "core/proto/android/os/ps.proto",
         "core/proto/android/os/system_properties.proto",
+        "core/proto/android/util/event_log_tags.proto",
     ],
 
     // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool
-    cmd: "PATH=$$PATH:$$(dirname $(location protoc-gen-cppstream)) $(location aprotoc) --plugin=protoc-gen-cpp-stream=$(location protoc-gen-cppstream) --dependency_out=$(depfile) --cppstream_out=$(genDir)/ -Iexternal/protobuf/src -I . $(in)",
+    cmd: "mkdir -p $(genDir) " +
+        "&& $(location aprotoc) " +
+        "  --plugin=$(location protoc-gen-cppstream) " +
+        "  --dependency_out=$(depfile) " +
+        "  --cppstream_out=$(genDir) " +
+        "  -Iexternal/protobuf/src " +
+        "  -I . " +
+        "  $(in)",
 
     output_extension = "proto.h",
 }
diff --git a/Android.mk b/Android.mk
index 1f37326..8199c57 100644
--- a/Android.mk
+++ b/Android.mk
@@ -32,228 +32,26 @@
 # ============================================================
 include $(CLEAR_VARS)
 
-aidl_files := \
-        frameworks/base/telephony/java/android/telephony/mbms/DownloadRequest.aidl \
-        frameworks/base/telephony/java/android/telephony/mbms/FileInfo.aidl \
-        frameworks/base/telephony/java/android/telephony/mbms/FileServiceInfo.aidl \
-        frameworks/base/telephony/java/android/telephony/mbms/ServiceInfo.aidl \
-        frameworks/base/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl \
-	frameworks/base/telephony/java/android/telephony/ServiceState.aidl \
-	frameworks/base/telephony/java/android/telephony/SubscriptionInfo.aidl \
-	frameworks/base/telephony/java/android/telephony/CellInfo.aidl \
-	frameworks/base/telephony/java/android/telephony/SignalStrength.aidl \
-	frameworks/base/telephony/java/android/telephony/IccOpenLogicalChannelResponse.aidl \
-	frameworks/base/telephony/java/android/telephony/NeighboringCellInfo.aidl \
-	frameworks/base/telephony/java/android/telephony/ModemActivityInfo.aidl \
-	frameworks/base/telephony/java/android/telephony/UiccAccessRule.aidl \
-	frameworks/base/telephony/java/android/telephony/data/DataCallResponse.aidl \
-	frameworks/base/telephony/java/android/telephony/data/DataProfile.aidl \
-	frameworks/base/telephony/java/android/telephony/euicc/DownloadableSubscription.aidl \
-	frameworks/base/telephony/java/android/telephony/euicc/EuiccInfo.aidl \
-	frameworks/base/location/java/android/location/Location.aidl \
-	frameworks/base/location/java/android/location/Address.aidl \
-	frameworks/base/location/java/android/location/Criteria.aidl \
-	frameworks/base/media/java/android/media/MediaMetadata.aidl \
-	frameworks/base/media/java/android/media/MediaDescription.aidl \
-	frameworks/base/media/java/android/media/Rating.aidl \
-	frameworks/base/media/java/android/media/AudioAttributes.aidl \
-	frameworks/base/media/java/android/media/AudioFocusInfo.aidl \
-	frameworks/base/media/java/android/media/session/PlaybackState.aidl \
-	frameworks/base/media/java/android/media/session/MediaSession.aidl \
-	frameworks/base/media/java/android/media/tv/TvInputInfo.aidl \
-	frameworks/base/media/java/android/media/tv/TvTrackInfo.aidl \
-	frameworks/base/media/java/android/media/browse/MediaBrowser.aidl \
-	frameworks/base/wifi/java/android/net/wifi/ScanSettings.aidl \
-	frameworks/base/wifi/java/android/net/wifi/aware/ConfigRequest.aidl \
-	frameworks/base/wifi/java/android/net/wifi/aware/PublishConfig.aidl \
-	frameworks/base/wifi/java/android/net/wifi/aware/SubscribeConfig.aidl \
-	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pInfo.aidl \
-	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.aidl \
-	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pConfig.aidl \
-	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pDevice.aidl \
-	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 \
-	frameworks/base/wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl \
-	frameworks/base/wifi/java/android/net/wifi/WifiConfiguration.aidl \
-	frameworks/base/wifi/java/android/net/wifi/WifiInfo.aidl \
-	frameworks/base/graphics/java/android/graphics/Region.aidl \
-	frameworks/base/graphics/java/android/graphics/Bitmap.aidl \
-	frameworks/base/graphics/java/android/graphics/Point.aidl \
-	frameworks/base/graphics/java/android/graphics/PointF.aidl \
-	frameworks/base/graphics/java/android/graphics/RectF.aidl \
-	frameworks/base/graphics/java/android/graphics/Rect.aidl \
-	frameworks/base/graphics/java/android/graphics/drawable/Icon.aidl \
-	frameworks/base/core/java/android/accounts/AuthenticatorDescription.aidl \
-	frameworks/base/core/java/android/accounts/Account.aidl \
-	frameworks/base/core/java/android/app/admin/ConnectEvent.aidl \
-	frameworks/base/core/java/android/app/admin/DnsEvent.aidl \
-	frameworks/base/core/java/android/app/admin/NetworkEvent.aidl \
-	frameworks/base/core/java/android/app/admin/SystemUpdatePolicy.aidl \
-	frameworks/base/core/java/android/app/admin/PasswordMetrics.aidl \
-	frameworks/base/core/java/android/app/slice/ISliceManager.aidl \
-	frameworks/base/core/java/android/app/slice/ISliceListener.aidl \
-	frameworks/base/core/java/android/print/PrintDocumentInfo.aidl \
-	frameworks/base/core/java/android/print/PageRange.aidl \
-	frameworks/base/core/java/android/print/PrintAttributes.aidl \
-	frameworks/base/core/java/android/print/PrinterCapabilitiesInfo.aidl \
-	frameworks/base/core/java/android/print/PrinterId.aidl \
-	frameworks/base/core/java/android/print/PrintJobInfo.aidl \
-	frameworks/base/core/java/android/print/PrinterInfo.aidl \
-	frameworks/base/core/java/android/print/PrintJobId.aidl \
-	frameworks/base/core/java/android/printservice/recommendation/RecommendationInfo.aidl \
-	frameworks/base/core/java/android/hardware/radio/ProgramSelector.aidl \
-	frameworks/base/core/java/android/hardware/radio/RadioManager.aidl \
-	frameworks/base/core/java/android/hardware/radio/RadioMetadata.aidl \
-	frameworks/base/core/java/android/hardware/usb/UsbDevice.aidl \
-	frameworks/base/core/java/android/hardware/usb/UsbInterface.aidl \
-	frameworks/base/core/java/android/hardware/usb/UsbEndpoint.aidl \
-	frameworks/base/core/java/android/hardware/usb/UsbAccessory.aidl \
-	frameworks/base/core/java/android/os/Messenger.aidl \
-	frameworks/base/core/java/android/os/PatternMatcher.aidl \
-	frameworks/base/core/java/android/os/Message.aidl \
-	frameworks/base/core/java/android/os/UserHandle.aidl \
-	frameworks/base/core/java/android/os/ParcelUuid.aidl \
-	frameworks/base/core/java/android/os/ParcelFileDescriptor.aidl \
-	frameworks/base/core/java/android/os/ResultReceiver.aidl \
-	frameworks/base/core/java/android/os/WorkSource.aidl \
-	frameworks/base/core/java/android/os/DropBoxManager.aidl \
-	frameworks/base/core/java/android/os/Bundle.aidl \
-	frameworks/base/core/java/android/os/Debug.aidl \
-	frameworks/base/core/java/android/os/SharedMemory.aidl \
-	frameworks/base/core/java/android/os/StrictMode.aidl \
-	frameworks/base/core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl \
-	frameworks/base/core/java/android/net/Network.aidl \
-	frameworks/base/core/java/android/net/RouteInfo.aidl \
-	frameworks/base/core/java/android/net/NetworkInfo.aidl \
-	frameworks/base/core/java/android/net/IpPrefix.aidl \
-	frameworks/base/core/java/android/net/NetworkCapabilities.aidl \
-	frameworks/base/core/java/android/net/DhcpInfo.aidl \
-	frameworks/base/core/java/android/net/ProxyInfo.aidl \
-	frameworks/base/core/java/android/net/LinkProperties.aidl \
-	frameworks/base/core/java/android/net/Uri.aidl \
-	frameworks/base/core/java/android/net/NetworkRequest.aidl \
-	frameworks/base/core/java/android/net/LinkAddress.aidl \
-	frameworks/base/core/java/android/util/MemoryIntArray.aidl \
-	frameworks/base/core/java/android/view/Display.aidl \
-	frameworks/base/core/java/android/view/InputDevice.aidl \
-	frameworks/base/core/java/android/view/InputEvent.aidl \
-	frameworks/native/aidl/gui/android/view/Surface.aidl \
-	frameworks/base/core/java/android/view/WindowContentFrameStats.aidl \
-	frameworks/base/core/java/android/view/inputmethod/InputMethodSubtype.aidl \
-	frameworks/base/core/java/android/view/inputmethod/CursorAnchorInfo.aidl \
-	frameworks/base/core/java/android/view/inputmethod/CompletionInfo.aidl \
-	frameworks/base/core/java/android/view/inputmethod/ExtractedText.aidl \
-	frameworks/base/core/java/android/view/inputmethod/EditorInfo.aidl \
-	frameworks/base/core/java/android/view/inputmethod/InputMethodInfo.aidl \
-	frameworks/base/core/java/android/view/inputmethod/CorrectionInfo.aidl \
-	frameworks/base/core/java/android/view/inputmethod/InputBinding.aidl \
-	frameworks/base/core/java/android/view/inputmethod/ExtractedTextRequest.aidl \
-	frameworks/base/core/java/android/view/DragEvent.aidl \
-	frameworks/base/core/java/android/view/KeyEvent.aidl \
-	frameworks/base/core/java/android/view/WindowManager.aidl \
-	frameworks/base/core/java/android/view/WindowAnimationFrameStats.aidl \
-	frameworks/base/core/java/android/view/MotionEvent.aidl \
-	frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl \
-	frameworks/base/core/java/android/view/accessibility/AccessibilityRecord.aidl \
-	frameworks/base/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl \
-	frameworks/base/core/java/android/view/accessibility/AccessibilityEvent.aidl \
-	frameworks/base/core/java/android/view/textservice/SpellCheckerSubtype.aidl \
-	frameworks/base/core/java/android/view/textservice/TextInfo.aidl \
-	frameworks/base/core/java/android/view/textservice/SpellCheckerInfo.aidl \
-	frameworks/base/core/java/android/view/textservice/SentenceSuggestionsInfo.aidl \
-	frameworks/base/core/java/android/view/textservice/SuggestionsInfo.aidl \
-	frameworks/base/core/java/android/service/carrier/CarrierIdentifier.aidl \
-	frameworks/base/core/java/android/service/carrier/MessagePdu.aidl \
-	frameworks/base/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.aidl \
-	frameworks/base/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.aidl \
-	frameworks/base/core/java/android/service/euicc/GetEuiccProfileInfoListResult.aidl \
-	frameworks/base/core/java/android/service/notification/Adjustment.aidl \
-	frameworks/base/core/java/android/service/notification/Condition.aidl \
-	frameworks/base/core/java/android/service/notification/SnoozeCriterion.aidl \
-	frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \
-	frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \
-	frameworks/base/core/java/android/service/resolver/ResolverTarget.aidl \
-	frameworks/base/core/java/android/speech/tts/Voice.aidl \
-	frameworks/base/core/java/android/app/usage/CacheQuotaHint.aidl \
-	frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \
-	frameworks/base/core/java/android/app/usage/StorageStats.aidl \
-	frameworks/base/core/java/android/app/usage/UsageEvents.aidl \
-	frameworks/base/core/java/android/app/Notification.aidl \
-	frameworks/base/core/java/android/app/NotificationManager.aidl \
-	frameworks/base/core/java/android/app/WallpaperInfo.aidl \
-	frameworks/base/core/java/android/app/AppOpsManager.aidl \
-	frameworks/base/core/java/android/app/ActivityManager.aidl \
-	frameworks/base/core/java/android/app/PendingIntent.aidl \
-	frameworks/base/core/java/android/app/AlarmManager.aidl \
-	frameworks/base/core/java/android/app/SearchableInfo.aidl \
-	frameworks/base/core/java/android/app/VoiceInteractor.aidl \
-	frameworks/base/core/java/android/app/assist/AssistContent.aidl \
-	frameworks/base/core/java/android/app/assist/AssistStructure.aidl \
-	frameworks/base/core/java/android/app/job/JobParameters.aidl \
-	frameworks/base/core/java/android/app/job/JobInfo.aidl \
-	frameworks/base/core/java/android/appwidget/AppWidgetProviderInfo.aidl \
-	frameworks/base/core/java/android/content/ClipDescription.aidl \
-	frameworks/base/core/java/android/content/IntentFilter.aidl \
-	frameworks/base/core/java/android/content/Intent.aidl \
-	frameworks/base/core/java/android/content/res/Configuration.aidl \
-	frameworks/base/core/java/android/content/res/ObbInfo.aidl \
-	frameworks/base/core/java/android/content/RestrictionEntry.aidl \
-	frameworks/base/core/java/android/content/ClipData.aidl \
-	frameworks/base/core/java/android/content/SyncAdapterType.aidl \
-	frameworks/base/core/java/android/content/SyncRequest.aidl \
-	frameworks/base/core/java/android/content/PeriodicSync.aidl \
-	frameworks/base/core/java/android/content/SyncResult.aidl \
-	frameworks/base/core/java/android/content/pm/FeatureInfo.aidl \
-	frameworks/base/core/java/android/content/pm/InstrumentationInfo.aidl \
-	frameworks/base/core/java/android/content/pm/PackageInstaller.aidl \
-	frameworks/base/core/java/android/content/pm/ServiceInfo.aidl \
-	frameworks/base/core/java/android/content/pm/Signature.aidl \
-	frameworks/base/core/java/android/content/pm/ApplicationInfo.aidl \
-	frameworks/base/core/java/android/content/pm/PermissionInfo.aidl \
-	frameworks/base/core/java/android/content/pm/ActivityInfo.aidl \
-	frameworks/base/core/java/android/content/pm/ConfigurationInfo.aidl \
-	frameworks/base/core/java/android/content/pm/PackageInfo.aidl \
-	frameworks/base/core/java/android/content/pm/ResolveInfo.aidl \
-	frameworks/base/core/java/android/content/pm/ProviderInfo.aidl \
-	frameworks/base/core/java/android/content/pm/PackageStats.aidl \
-	frameworks/base/core/java/android/content/pm/PermissionGroupInfo.aidl \
-	frameworks/base/core/java/android/content/pm/ShortcutInfo.aidl \
-	frameworks/base/core/java/android/content/pm/LabeledIntent.aidl \
-	frameworks/base/core/java/android/content/ComponentName.aidl \
-	frameworks/base/core/java/android/content/SyncStats.aidl \
-	frameworks/base/core/java/android/content/ContentValues.aidl \
-	frameworks/base/core/java/android/content/SyncInfo.aidl \
-	frameworks/base/core/java/android/content/IntentSender.aidl \
-	frameworks/base/core/java/android/widget/RemoteViews.aidl \
-	frameworks/base/core/java/android/text/style/SuggestionSpan.aidl \
-	frameworks/base/core/java/android/nfc/Tag.aidl \
-	frameworks/base/core/java/android/nfc/NdefRecord.aidl \
-	frameworks/base/core/java/android/nfc/NdefMessage.aidl \
-	frameworks/base/core/java/android/database/CursorWindow.aidl \
-	frameworks/base/core/java/android/service/quicksettings/Tile.aidl \
-	frameworks/native/aidl/binder/android/os/PersistableBundle.aidl \
-	system/bt/binder/android/bluetooth/BluetoothHealthAppConfiguration.aidl \
-	system/bt/binder/android/bluetooth/le/AdvertiseSettings.aidl \
-	system/bt/binder/android/bluetooth/le/ScanSettings.aidl \
-	system/bt/binder/android/bluetooth/le/AdvertiseData.aidl \
-	system/bt/binder/android/bluetooth/le/ScanFilter.aidl \
-	system/bt/binder/android/bluetooth/le/ScanResult.aidl \
-	system/bt/binder/android/bluetooth/BluetoothDevice.aidl \
-	system/netd/server/binder/android/net/UidRange.aidl \
-	frameworks/base/telephony/java/android/telephony/PcoData.aidl \
+aidl_parcelables :=
+define stubs-to-aidl-parcelables
+  gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/$1.aidl
+  aidl_parcelables += $$(gen)
+  $$(gen): $(call java-lib-header-files,$1) | $(HOST_OUT_EXECUTABLES)/sdkparcelables
+	@echo Extract SDK parcelables: $$@
+	rm -f $$@
+	$(HOST_OUT_EXECUTABLES)/sdkparcelables $$< $$@
+endef
+
+$(foreach stubs,android_stubs_current android_test_stubs_current android_system_stubs_current,\
+  $(eval $(call stubs-to-aidl-parcelables,$(stubs))))
 
 gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl
-$(gen): PRIVATE_SRC_FILES := $(aidl_files)
-ALL_SDK_FILES += $(gen)
-$(gen): $(aidl_files) | $(AIDL)
-		@echo Aidl Preprocess: $@
-		$(hide) $(AIDL) --preprocess $@ $(PRIVATE_SRC_FILES)
+.KATI_RESTAT: $(gen)
+$(gen): $(aidl_parcelables)
+	@echo Combining SDK parcelables: $@
+	rm -f $@.tmp
+	cat $^ | sort -u > $@.tmp
+	$(call commit-change-for-toc,$@)
 
 # the documentation
 # ============================================================
@@ -550,8 +348,6 @@
 
 include $(BUILD_DROIDDOC)
 
-# $(gen), i.e. framework.aidl, is also needed while building against the current stub.
-$(full_target): $(gen)
 $(INTERNAL_PLATFORM_API_FILE): $(full_target)
 $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE))
 
@@ -587,8 +383,6 @@
 
 include $(BUILD_DROIDDOC)
 
-# $(gen), i.e. framework.aidl, is also needed while building against the current stub.
-$(full_target): $(gen)
 $(INTERNAL_PLATFORM_SYSTEM_API_FILE): $(full_target)
 $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_SYSTEM_API_FILE))
 
@@ -625,8 +419,6 @@
 
 include $(BUILD_DROIDDOC)
 
-# $(gen), i.e. framework.aidl, is also needed while building against the current stub.
-$(full_target): $(gen)
 $(INTERNAL_PLATFORM_TEST_API_FILE): $(full_target)
 $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_TEST_API_FILE))
 
@@ -656,9 +448,6 @@
 
 include $(BUILD_DROIDDOC)
 
-# $(gen), i.e. framework.aidl, is also needed while building against the current stub.
-$(full_target): $(gen)
-
 # Run this for checkbuild
 checkbuild: doc-comment-check-docs
 # Check comment when you are updating the API
diff --git a/api/current.txt b/api/current.txt
index 7b203fe..251ed8d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -579,6 +579,7 @@
     field public static final int fadingEdge = 16842975; // 0x10100df
     field public static final int fadingEdgeLength = 16842976; // 0x10100e0
     field public static final int fadingMode = 16843745; // 0x10103e1
+    field public static final int fallbackLineSpacing = 16844155; // 0x101057b
     field public static final int fastScrollAlwaysVisible = 16843573; // 0x1010335
     field public static final int fastScrollEnabled = 16843302; // 0x1010226
     field public static final int fastScrollOverlayPosition = 16843578; // 0x101033a
@@ -6338,7 +6339,7 @@
     method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
     method public void enableSystemApp(android.content.ComponentName, java.lang.String);
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
-    method public android.security.AttestedKeyPair generateKeyPair(android.content.ComponentName, java.lang.String, android.security.keystore.KeyGenParameterSpec);
+    method public android.security.AttestedKeyPair generateKeyPair(android.content.ComponentName, java.lang.String, android.security.keystore.KeyGenParameterSpec, int);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
     method public java.util.List<android.content.ComponentName> getActiveAdmins();
     method public java.util.Set<java.lang.String> getAffiliationIds(android.content.ComponentName);
@@ -6368,6 +6369,7 @@
     method public int getOrganizationColor(android.content.ComponentName);
     method public java.lang.CharSequence getOrganizationName(android.content.ComponentName);
     method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
+    method public java.lang.String getPasswordBlacklistName(android.content.ComponentName);
     method public long getPasswordExpiration(android.content.ComponentName);
     method public long getPasswordExpirationTimeout(android.content.ComponentName);
     method public int getPasswordHistoryLength(android.content.ComponentName);
@@ -6468,6 +6470,7 @@
     method public void setOrganizationColor(android.content.ComponentName, int);
     method public void setOrganizationName(android.content.ComponentName, java.lang.CharSequence);
     method public java.lang.String[] setPackagesSuspended(android.content.ComponentName, java.lang.String[], boolean);
+    method public boolean setPasswordBlacklist(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
     method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
     method public void setPasswordHistoryLength(android.content.ComponentName, int);
     method public void setPasswordMinimumLength(android.content.ComponentName, int);
@@ -6570,6 +6573,10 @@
     field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1
     field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
     field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
+    field public static final int ID_TYPE_BASE_INFO = 1; // 0x1
+    field public static final int ID_TYPE_IMEI = 4; // 0x4
+    field public static final int ID_TYPE_MEID = 8; // 0x8
+    field public static final int ID_TYPE_SERIAL = 2; // 0x2
     field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
     field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
     field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
@@ -6747,6 +6754,7 @@
     method public java.lang.CharSequence getText();
     method public int getTextBackgroundColor();
     method public int getTextColor();
+    method public java.lang.String getTextIdEntry();
     method public int[] getTextLineBaselines();
     method public int[] getTextLineCharOffsets();
     method public int getTextSelectionEnd();
@@ -7040,6 +7048,7 @@
     field public static final java.lang.String HINT_SUMMARY = "summary";
     field public static final java.lang.String HINT_TITLE = "title";
     field public static final java.lang.String SUBTYPE_COLOR = "color";
+    field public static final java.lang.String SUBTYPE_CONTENT_DESCRIPTION = "content_description";
     field public static final java.lang.String SUBTYPE_MESSAGE = "message";
     field public static final java.lang.String SUBTYPE_PRIORITY = "priority";
     field public static final java.lang.String SUBTYPE_SLIDER = "slider";
@@ -9331,6 +9340,7 @@
     field public static final java.lang.String WALLPAPER_SERVICE = "wallpaper";
     field public static final java.lang.String WIFI_AWARE_SERVICE = "wifiaware";
     field public static final java.lang.String WIFI_P2P_SERVICE = "wifip2p";
+    field public static final java.lang.String WIFI_RTT_RANGING_SERVICE = "wifirtt";
     field public static final java.lang.String WIFI_SERVICE = "wifi";
     field public static final java.lang.String WINDOW_SERVICE = "window";
   }
@@ -11092,6 +11102,7 @@
     field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony";
     field public static final java.lang.String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
     field public static final java.lang.String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+    field public static final java.lang.String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
     field public static final deprecated java.lang.String FEATURE_TELEVISION = "android.hardware.type.television";
     field public static final java.lang.String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
     field public static final java.lang.String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
@@ -11112,6 +11123,7 @@
     field public static final java.lang.String FEATURE_WIFI_AWARE = "android.hardware.wifi.aware";
     field public static final java.lang.String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
     field public static final java.lang.String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
+    field public static final java.lang.String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
     field public static final int GET_ACTIVITIES = 1; // 0x1
     field public static final int GET_CONFIGURATIONS = 16384; // 0x4000
     field public static final deprecated int GET_DISABLED_COMPONENTS = 512; // 0x200
@@ -11342,6 +11354,7 @@
     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_UNKNOWN = 3; // 0x3
     field public static final int DISABLED_REASON_VERSION_LOWER = 100; // 0x64
     field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
   }
@@ -11414,7 +11427,7 @@
     method public android.graphics.drawable.Drawable getProfileSwitchingIcon(android.os.UserHandle);
     method public java.lang.CharSequence getProfileSwitchingLabel(android.os.UserHandle);
     method public java.util.List<android.os.UserHandle> getTargetUserProfiles();
-    method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+    method public void startMainActivity(android.content.ComponentName, android.os.UserHandle);
   }
 
 }
@@ -15485,6 +15498,7 @@
     method public <T> T get(android.hardware.camera2.CameraCharacteristics.Key<T>);
     method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableCaptureRequestKeys();
     method public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getAvailableCaptureResultKeys();
+    method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableSessionKeys();
     method public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeys();
     field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES;
@@ -15509,6 +15523,7 @@
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
+    field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> LENS_FACING;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INFO_AVAILABLE_APERTURES;
@@ -15519,6 +15534,7 @@
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> LENS_INFO_HYPERFOCAL_DISTANCE;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INTRINSIC_CALIBRATION;
+    field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> LENS_POSE_REFERENCE;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_ROTATION;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_TRANSLATION;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_RADIAL_DISTORTION;
@@ -15583,6 +15599,7 @@
     method public abstract void close();
     method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
     method public abstract void createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
     method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract void createConstrainedHighSpeedCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
@@ -15590,6 +15607,8 @@
     method public abstract void createReprocessableCaptureSessionByConfigurations(android.hardware.camera2.params.InputConfiguration, java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract java.lang.String getId();
     field public static final int TEMPLATE_MANUAL = 6; // 0x6
+    field public static final int TEMPLATE_MOTION_TRACKING_BEST = 8; // 0x8
+    field public static final int TEMPLATE_MOTION_TRACKING_PREVIEW = 7; // 0x7
     field public static final int TEMPLATE_PREVIEW = 1; // 0x1
     field public static final int TEMPLATE_RECORD = 3; // 0x3
     field public static final int TEMPLATE_STILL_CAPTURE = 2; // 0x2
@@ -15692,6 +15711,7 @@
     field public static final int CONTROL_AWB_STATE_SEARCHING = 1; // 0x1
     field public static final int CONTROL_CAPTURE_INTENT_CUSTOM = 0; // 0x0
     field public static final int CONTROL_CAPTURE_INTENT_MANUAL = 6; // 0x6
+    field public static final int CONTROL_CAPTURE_INTENT_MOTION_TRACKING = 7; // 0x7
     field public static final int CONTROL_CAPTURE_INTENT_PREVIEW = 1; // 0x1
     field public static final int CONTROL_CAPTURE_INTENT_STILL_CAPTURE = 2; // 0x2
     field public static final int CONTROL_CAPTURE_INTENT_VIDEO_RECORD = 3; // 0x3
@@ -15758,6 +15778,8 @@
     field public static final int LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED = 0; // 0x0
     field public static final int LENS_OPTICAL_STABILIZATION_MODE_OFF = 0; // 0x0
     field public static final int LENS_OPTICAL_STABILIZATION_MODE_ON = 1; // 0x1
+    field public static final int LENS_POSE_REFERENCE_GYROSCOPE = 1; // 0x1
+    field public static final int LENS_POSE_REFERENCE_PRIMARY_CAMERA = 0; // 0x0
     field public static final int LENS_STATE_MOVING = 1; // 0x1
     field public static final int LENS_STATE_STATIONARY = 0; // 0x0
     field public static final int NOISE_REDUCTION_MODE_FAST = 1; // 0x1
@@ -15771,6 +15793,7 @@
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8; // 0x8
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2; // 0x2
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1
+    field public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10; // 0xa
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING = 4; // 0x4
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; // 0x3
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; // 0x5
@@ -16126,6 +16149,20 @@
     field public static final int RED = 0; // 0x0
   }
 
+  public final class SessionConfiguration {
+    ctor public SessionConfiguration(int, java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler);
+    method public android.os.Handler getHandler();
+    method public android.hardware.camera2.params.InputConfiguration getInputConfiguration();
+    method public java.util.List<android.hardware.camera2.params.OutputConfiguration> getOutputConfigurations();
+    method public android.hardware.camera2.CaptureRequest getSessionParameters();
+    method public int getSessionType();
+    method public android.hardware.camera2.CameraCaptureSession.StateCallback getStateCallback();
+    method public void setInputConfiguration(android.hardware.camera2.params.InputConfiguration);
+    method public void setSessionParameters(android.hardware.camera2.CaptureRequest);
+    field public static final int SESSION_HIGH_SPEED = 1; // 0x1
+    field public static final int SESSION_REGULAR = 0; // 0x0
+  }
+
   public final class StreamConfigurationMap {
     method public android.util.Size[] getHighResolutionOutputSizes(int);
     method public android.util.Range<java.lang.Integer>[] getHighSpeedVideoFpsRanges();
@@ -20154,6 +20191,13 @@
     ctor public ICUUncheckedIOException(java.lang.String, java.lang.Throwable);
   }
 
+  public class IllformedLocaleException extends java.lang.RuntimeException {
+    ctor public IllformedLocaleException();
+    ctor public IllformedLocaleException(java.lang.String);
+    ctor public IllformedLocaleException(java.lang.String, int);
+    method public int getErrorIndex();
+  }
+
   public class IndianCalendar extends android.icu.util.Calendar {
     ctor public IndianCalendar();
     ctor public IndianCalendar(android.icu.util.TimeZone);
@@ -20238,6 +20282,32 @@
     field public static final int TAISHO;
   }
 
+  public final class LocaleData {
+    method public static android.icu.util.VersionInfo getCLDRVersion();
+    method public java.lang.String getDelimiter(int);
+    method public static final android.icu.util.LocaleData getInstance(android.icu.util.ULocale);
+    method public static final android.icu.util.LocaleData getInstance();
+    method public static final android.icu.util.LocaleData.MeasurementSystem getMeasurementSystem(android.icu.util.ULocale);
+    method public boolean getNoSubstitute();
+    method public static final android.icu.util.LocaleData.PaperSize getPaperSize(android.icu.util.ULocale);
+    method public void setNoSubstitute(boolean);
+    field public static final int ALT_QUOTATION_END = 3; // 0x3
+    field public static final int ALT_QUOTATION_START = 2; // 0x2
+    field public static final int QUOTATION_END = 1; // 0x1
+    field public static final int QUOTATION_START = 0; // 0x0
+  }
+
+  public static final class LocaleData.MeasurementSystem {
+    field public static final android.icu.util.LocaleData.MeasurementSystem SI;
+    field public static final android.icu.util.LocaleData.MeasurementSystem UK;
+    field public static final android.icu.util.LocaleData.MeasurementSystem US;
+  }
+
+  public static final class LocaleData.PaperSize {
+    method public int getHeight();
+    method public int getWidth();
+  }
+
   public class Measure {
     ctor public Measure(java.lang.Number, android.icu.util.MeasureUnit);
     method public java.lang.Number getNumber();
@@ -21357,6 +21427,8 @@
     method public void clearTestProviderStatus(java.lang.String);
     method public java.util.List<java.lang.String> getAllProviders();
     method public java.lang.String getBestProvider(android.location.Criteria, boolean);
+    method public java.lang.String getGnssHardwareModelName();
+    method public int getGnssYearOfHardware();
     method public deprecated android.location.GpsStatus getGpsStatus(android.location.GpsStatus);
     method public android.location.Location getLastKnownLocation(java.lang.String);
     method public android.location.LocationProvider getProvider(java.lang.String);
@@ -21392,6 +21464,7 @@
     method public void unregisterGnssMeasurementsCallback(android.location.GnssMeasurementsEvent.Callback);
     method public void unregisterGnssNavigationMessageCallback(android.location.GnssNavigationMessage.Callback);
     method public void unregisterGnssStatusCallback(android.location.GnssStatus.Callback);
+    field public static final java.lang.String GNSS_HARDWARE_MODEL_NAME_UNKNOWN = "Model Name Unknown";
     field public static final java.lang.String GPS_PROVIDER = "gps";
     field public static final java.lang.String KEY_LOCATION_CHANGED = "location";
     field public static final java.lang.String KEY_PROVIDER_ENABLED = "providerEnabled";
@@ -21644,7 +21717,9 @@
     method public int getRingerMode();
     method public deprecated int getRouting(int);
     method public int getStreamMaxVolume(int);
+    method public int getStreamMinVolume(int);
     method public int getStreamVolume(int);
+    method public float getStreamVolumeDb(int, int, int);
     method public deprecated int getVibrateSetting(int);
     method public deprecated boolean isBluetoothA2dpOn();
     method public boolean isBluetoothScoAvailableOffCall();
@@ -21694,6 +21769,7 @@
     field public static final java.lang.String ACTION_AUDIO_BECOMING_NOISY = "android.media.AUDIO_BECOMING_NOISY";
     field public static final java.lang.String ACTION_HDMI_AUDIO_PLUG = "android.media.action.HDMI_AUDIO_PLUG";
     field public static final java.lang.String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG";
+    field public static final java.lang.String ACTION_MICROPHONE_MUTE_CHANGED = "android.media.action.MICROPHONE_MUTE_CHANGED";
     field public static final deprecated java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED";
     field public static final java.lang.String ACTION_SCO_AUDIO_STATE_UPDATED = "android.media.ACTION_SCO_AUDIO_STATE_UPDATED";
     field public static final int ADJUST_LOWER = -1; // 0xffffffff
@@ -27235,7 +27311,7 @@
     method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
     method public boolean setWifiEnabled(boolean);
     method public void startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, android.os.Handler);
-    method public boolean startScan();
+    method public deprecated boolean startScan();
     method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
     method public int updateNetwork(android.net.wifi.WifiConfiguration);
     field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
@@ -27360,6 +27436,7 @@
     method public void onMessageSendSucceeded(int);
     method public void onPublishStarted(android.net.wifi.aware.PublishDiscoverySession);
     method public void onServiceDiscovered(android.net.wifi.aware.PeerHandle, byte[], java.util.List<byte[]>);
+    method public void onServiceDiscoveredWithinRange(android.net.wifi.aware.PeerHandle, byte[], java.util.List<byte[]>, int);
     method public void onSessionConfigFailed();
     method public void onSessionConfigUpdated();
     method public void onSessionTerminated();
@@ -27387,6 +27464,7 @@
     method public android.net.wifi.aware.PublishConfig build();
     method public android.net.wifi.aware.PublishConfig.Builder setMatchFilter(java.util.List<byte[]>);
     method public android.net.wifi.aware.PublishConfig.Builder setPublishType(int);
+    method public android.net.wifi.aware.PublishConfig.Builder setRangingEnabled(boolean);
     method public android.net.wifi.aware.PublishConfig.Builder setServiceName(java.lang.String);
     method public android.net.wifi.aware.PublishConfig.Builder setServiceSpecificInfo(byte[]);
     method public android.net.wifi.aware.PublishConfig.Builder setTerminateNotificationEnabled(boolean);
@@ -27409,6 +27487,8 @@
     ctor public SubscribeConfig.Builder();
     method public android.net.wifi.aware.SubscribeConfig build();
     method public android.net.wifi.aware.SubscribeConfig.Builder setMatchFilter(java.util.List<byte[]>);
+    method public android.net.wifi.aware.SubscribeConfig.Builder setMaxDistanceMm(int);
+    method public android.net.wifi.aware.SubscribeConfig.Builder setMinDistanceMm(int);
     method public android.net.wifi.aware.SubscribeConfig.Builder setServiceName(java.lang.String);
     method public android.net.wifi.aware.SubscribeConfig.Builder setServiceSpecificInfo(byte[]);
     method public android.net.wifi.aware.SubscribeConfig.Builder setSubscribeType(int);
@@ -27744,6 +27824,55 @@
 
 }
 
+package android.net.wifi.rtt {
+
+  public final class RangingRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public static int getMaxPeers();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.rtt.RangingRequest> CREATOR;
+  }
+
+  public static final class RangingRequest.Builder {
+    ctor public RangingRequest.Builder();
+    method public android.net.wifi.rtt.RangingRequest.Builder addAccessPoint(android.net.wifi.ScanResult);
+    method public android.net.wifi.rtt.RangingRequest.Builder addAccessPoints(java.util.List<android.net.wifi.ScanResult>);
+    method public android.net.wifi.rtt.RangingRequest.Builder addWifiAwarePeer(android.net.MacAddress);
+    method public android.net.wifi.rtt.RangingRequest.Builder addWifiAwarePeer(android.net.wifi.aware.PeerHandle);
+    method public android.net.wifi.rtt.RangingRequest build();
+  }
+
+  public final class RangingResult implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getDistanceMm();
+    method public int getDistanceStdDevMm();
+    method public android.net.MacAddress getMacAddress();
+    method public android.net.wifi.aware.PeerHandle getPeerHandle();
+    method public long getRangingTimestampUs();
+    method public int getRssi();
+    method public int getStatus();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.rtt.RangingResult> CREATOR;
+    field public static final int STATUS_FAIL = 1; // 0x1
+    field public static final int STATUS_SUCCESS = 0; // 0x0
+  }
+
+  public abstract class RangingResultCallback {
+    ctor public RangingResultCallback();
+    method public abstract void onRangingFailure(int);
+    method public abstract void onRangingResults(java.util.List<android.net.wifi.rtt.RangingResult>);
+    field public static final int STATUS_CODE_FAIL = 1; // 0x1
+    field public static final int STATUS_CODE_FAIL_RTT_NOT_AVAILABLE = 2; // 0x2
+  }
+
+  public class WifiRttManager {
+    method public boolean isAvailable();
+    method public void startRanging(android.net.wifi.rtt.RangingRequest, android.net.wifi.rtt.RangingResultCallback, android.os.Handler);
+    field public static final java.lang.String ACTION_WIFI_RTT_STATE_CHANGED = "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
+  }
+
+}
+
 package android.nfc {
 
   public class FormatException extends java.lang.Exception {
@@ -31545,6 +31674,7 @@
     method public final boolean postAtTime(java.lang.Runnable, long);
     method public final boolean postAtTime(java.lang.Runnable, java.lang.Object, long);
     method public final boolean postDelayed(java.lang.Runnable, long);
+    method public final boolean postDelayed(java.lang.Runnable, java.lang.Object, long);
     method public final void removeCallbacks(java.lang.Runnable);
     method public final void removeCallbacks(java.lang.Runnable, java.lang.Object);
     method public final void removeCallbacksAndMessages(java.lang.Object);
@@ -32270,6 +32400,7 @@
     field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
     field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
     field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
+    field public static final java.lang.String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
     field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
     field public static final java.lang.String DISALLOW_AUTOFILL = "no_autofill";
     field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth";
@@ -32279,6 +32410,7 @@
     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_LOCALE = "no_config_locale";
+    field public static final java.lang.String DISALLOW_CONFIG_LOCATION_MODE = "no_config_location_mode";
     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";
@@ -33787,6 +33919,7 @@
     field public static final java.lang.String FEATURES = "features";
     field public static final int FEATURES_HD_CALL = 4; // 0x4
     field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2
+    field public static final int FEATURES_RTT = 16; // 0x10
     field public static final int FEATURES_VIDEO = 1; // 0x1
     field public static final int FEATURES_WIFI = 8; // 0x8
     field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
@@ -35918,7 +36051,7 @@
   public static final class Telephony.Carriers implements android.provider.BaseColumns {
     field public static final java.lang.String APN = "apn";
     field public static final java.lang.String AUTH_TYPE = "authtype";
-    field public static final java.lang.String BEARER = "bearer";
+    field public static final deprecated java.lang.String BEARER = "bearer";
     field public static final java.lang.String CARRIER_ENABLED = "carrier_enabled";
     field public static final android.net.Uri CONTENT_URI;
     field public static final java.lang.String CURRENT = "current";
@@ -35931,6 +36064,7 @@
     field public static final java.lang.String MVNO_MATCH_DATA = "mvno_match_data";
     field public static final java.lang.String MVNO_TYPE = "mvno_type";
     field public static final java.lang.String NAME = "name";
+    field public static final java.lang.String NETWORK_TYPE_BITMASK = "network_type_bitmask";
     field public static final java.lang.String NUMERIC = "numeric";
     field public static final java.lang.String PASSWORD = "password";
     field public static final java.lang.String PORT = "port";
@@ -37694,18 +37828,12 @@
     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 EditDistanceScorer implements android.os.Parcelable android.service.autofill.Scorer {
-    method public int describeContents();
-    method public static android.service.autofill.EditDistanceScorer getInstance();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.autofill.EditDistanceScorer> CREATOR;
-  }
-
   public final class FieldClassification {
     method public java.util.List<android.service.autofill.FieldClassification.Match> getMatches();
   }
 
   public static final class FieldClassification.Match {
+    method public java.lang.String getAlgorithm();
     method public java.lang.String getRemoteId();
     method public float getScore();
   }
@@ -37857,9 +37985,6 @@
     field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
   }
 
-  public abstract interface Scorer {
-  }
-
   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();
@@ -37872,6 +37997,7 @@
 
   public final class UserData implements android.os.Parcelable {
     method public int describeContents();
+    method public java.lang.String getFieldClassificationAlgorithm();
     method public static int getMaxFieldClassificationIdsSize();
     method public static int getMaxUserDataSize();
     method public static int getMaxValueLength();
@@ -37881,9 +38007,10 @@
   }
 
   public static final class UserData.Builder {
-    ctor public UserData.Builder(android.service.autofill.Scorer, java.lang.String, java.lang.String);
+    ctor public UserData.Builder(java.lang.String, java.lang.String);
     method public android.service.autofill.UserData.Builder add(java.lang.String, java.lang.String);
     method public android.service.autofill.UserData build();
+    method public android.service.autofill.UserData.Builder setFieldClassificationAlgorithm(java.lang.String, android.os.Bundle);
   }
 
   public abstract interface Validator {
@@ -37901,6 +38028,7 @@
 
   public class CarrierIdentifier implements android.os.Parcelable {
     ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
+    ctor public CarrierIdentifier(byte[], java.lang.String, java.lang.String);
     method public int describeContents();
     method public java.lang.String getGid1();
     method public java.lang.String getGid2();
@@ -40347,6 +40475,113 @@
 
 package android.telephony {
 
+  public final class AccessNetworkConstants {
+    ctor public AccessNetworkConstants();
+  }
+
+  public static final class AccessNetworkConstants.AccessNetworkType {
+    ctor public AccessNetworkConstants.AccessNetworkType();
+    field public static final int CDMA2000 = 4; // 0x4
+    field public static final int EUTRAN = 3; // 0x3
+    field public static final int GERAN = 1; // 0x1
+    field public static final int IWLAN = 5; // 0x5
+    field public static final int UTRAN = 2; // 0x2
+  }
+
+  public static final class AccessNetworkConstants.EutranBand {
+    ctor public AccessNetworkConstants.EutranBand();
+    field public static final int BAND_1 = 1; // 0x1
+    field public static final int BAND_10 = 10; // 0xa
+    field public static final int BAND_11 = 11; // 0xb
+    field public static final int BAND_12 = 12; // 0xc
+    field public static final int BAND_13 = 13; // 0xd
+    field public static final int BAND_14 = 14; // 0xe
+    field public static final int BAND_17 = 17; // 0x11
+    field public static final int BAND_18 = 18; // 0x12
+    field public static final int BAND_19 = 19; // 0x13
+    field public static final int BAND_2 = 2; // 0x2
+    field public static final int BAND_20 = 20; // 0x14
+    field public static final int BAND_21 = 21; // 0x15
+    field public static final int BAND_22 = 22; // 0x16
+    field public static final int BAND_23 = 23; // 0x17
+    field public static final int BAND_24 = 24; // 0x18
+    field public static final int BAND_25 = 25; // 0x19
+    field public static final int BAND_26 = 26; // 0x1a
+    field public static final int BAND_27 = 27; // 0x1b
+    field public static final int BAND_28 = 28; // 0x1c
+    field public static final int BAND_3 = 3; // 0x3
+    field public static final int BAND_30 = 30; // 0x1e
+    field public static final int BAND_31 = 31; // 0x1f
+    field public static final int BAND_33 = 33; // 0x21
+    field public static final int BAND_34 = 34; // 0x22
+    field public static final int BAND_35 = 35; // 0x23
+    field public static final int BAND_36 = 36; // 0x24
+    field public static final int BAND_37 = 37; // 0x25
+    field public static final int BAND_38 = 38; // 0x26
+    field public static final int BAND_39 = 39; // 0x27
+    field public static final int BAND_4 = 4; // 0x4
+    field public static final int BAND_40 = 40; // 0x28
+    field public static final int BAND_41 = 41; // 0x29
+    field public static final int BAND_42 = 42; // 0x2a
+    field public static final int BAND_43 = 43; // 0x2b
+    field public static final int BAND_44 = 44; // 0x2c
+    field public static final int BAND_45 = 45; // 0x2d
+    field public static final int BAND_46 = 46; // 0x2e
+    field public static final int BAND_47 = 47; // 0x2f
+    field public static final int BAND_48 = 48; // 0x30
+    field public static final int BAND_5 = 5; // 0x5
+    field public static final int BAND_6 = 6; // 0x6
+    field public static final int BAND_65 = 65; // 0x41
+    field public static final int BAND_66 = 66; // 0x42
+    field public static final int BAND_68 = 68; // 0x44
+    field public static final int BAND_7 = 7; // 0x7
+    field public static final int BAND_70 = 70; // 0x46
+    field public static final int BAND_8 = 8; // 0x8
+    field public static final int BAND_9 = 9; // 0x9
+  }
+
+  public static final class AccessNetworkConstants.GeranBand {
+    ctor public AccessNetworkConstants.GeranBand();
+    field public static final int BAND_450 = 3; // 0x3
+    field public static final int BAND_480 = 4; // 0x4
+    field public static final int BAND_710 = 5; // 0x5
+    field public static final int BAND_750 = 6; // 0x6
+    field public static final int BAND_850 = 8; // 0x8
+    field public static final int BAND_DCS1800 = 12; // 0xc
+    field public static final int BAND_E900 = 10; // 0xa
+    field public static final int BAND_ER900 = 14; // 0xe
+    field public static final int BAND_P900 = 9; // 0x9
+    field public static final int BAND_PCS1900 = 13; // 0xd
+    field public static final int BAND_R900 = 11; // 0xb
+    field public static final int BAND_T380 = 1; // 0x1
+    field public static final int BAND_T410 = 2; // 0x2
+    field public static final int BAND_T810 = 7; // 0x7
+  }
+
+  public static final class AccessNetworkConstants.UtranBand {
+    ctor public AccessNetworkConstants.UtranBand();
+    field public static final int BAND_1 = 1; // 0x1
+    field public static final int BAND_10 = 10; // 0xa
+    field public static final int BAND_11 = 11; // 0xb
+    field public static final int BAND_12 = 12; // 0xc
+    field public static final int BAND_13 = 13; // 0xd
+    field public static final int BAND_14 = 14; // 0xe
+    field public static final int BAND_19 = 19; // 0x13
+    field public static final int BAND_2 = 2; // 0x2
+    field public static final int BAND_20 = 20; // 0x14
+    field public static final int BAND_21 = 21; // 0x15
+    field public static final int BAND_22 = 22; // 0x16
+    field public static final int BAND_25 = 25; // 0x19
+    field public static final int BAND_26 = 26; // 0x1a
+    field public static final int BAND_3 = 3; // 0x3
+    field public static final int BAND_4 = 4; // 0x4
+    field public static final int BAND_5 = 5; // 0x5
+    field public static final int BAND_6 = 6; // 0x6
+    field public static final int BAND_7 = 7; // 0x7
+    field public static final int BAND_8 = 8; // 0x8
+    field public static final int BAND_9 = 9; // 0x9
+  }
+
   public class CarrierConfigManager {
     method public android.os.PersistableBundle getConfig();
     method public android.os.PersistableBundle getConfigForSubId(int);
@@ -40681,19 +40916,19 @@
   }
 
   public class MbmsDownloadSession implements java.lang.AutoCloseable {
-    method public void cancelDownload(android.telephony.mbms.DownloadRequest);
+    method public int cancelDownload(android.telephony.mbms.DownloadRequest);
     method public void close();
     method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, android.os.Handler);
     method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, int, android.os.Handler);
-    method public void download(android.telephony.mbms.DownloadRequest);
+    method public int download(android.telephony.mbms.DownloadRequest);
     method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo);
     method public java.io.File getTempFileRootDirectory();
     method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads();
-    method public void registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler);
+    method public int registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler);
     method public void requestUpdateFileServices(java.util.List<java.lang.String>);
     method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest);
     method public void setTempFileRootDirectory(java.io.File);
-    method public void unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback);
+    method public int unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback);
     field public static final java.lang.String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
     field public static final java.lang.String EXTRA_MBMS_COMPLETED_FILE_URI = "android.telephony.extra.MBMS_COMPLETED_FILE_URI";
     field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_REQUEST = "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
@@ -40741,6 +40976,34 @@
     field public static final int UNKNOWN_RSSI = 99; // 0x63
   }
 
+  public class NetworkScan {
+    method public void stop() throws android.os.RemoteException;
+    field public static final int ERROR_INTERRUPTED = 10002; // 0x2712
+    field public static final int ERROR_INVALID_SCAN = 2; // 0x2
+    field public static final int ERROR_INVALID_SCANID = 10001; // 0x2711
+    field public static final int ERROR_MODEM_ERROR = 1; // 0x1
+    field public static final int ERROR_MODEM_UNAVAILABLE = 3; // 0x3
+    field public static final int ERROR_RADIO_INTERFACE_ERROR = 10000; // 0x2710
+    field public static final int ERROR_UNSUPPORTED = 4; // 0x4
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public final class NetworkScanRequest implements android.os.Parcelable {
+    ctor public NetworkScanRequest(int, android.telephony.RadioAccessSpecifier[], int, int, boolean, int, java.util.ArrayList<java.lang.String>);
+    method public int describeContents();
+    method public boolean getIncrementalResults();
+    method public int getIncrementalResultsPeriodicity();
+    method public int getMaxSearchTime();
+    method public java.util.ArrayList<java.lang.String> getPlmns();
+    method public int getScanType();
+    method public int getSearchPeriodicity();
+    method public android.telephony.RadioAccessSpecifier[] getSpecifiers();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.NetworkScanRequest> CREATOR;
+    field public static final int SCAN_TYPE_ONE_SHOT = 0; // 0x0
+    field public static final int SCAN_TYPE_PERIODIC = 1; // 0x1
+  }
+
   public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher {
     ctor public PhoneNumberFormattingTextWatcher();
     ctor public PhoneNumberFormattingTextWatcher(java.lang.String);
@@ -40833,6 +41096,16 @@
     field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
   }
 
+  public final class RadioAccessSpecifier implements android.os.Parcelable {
+    ctor public RadioAccessSpecifier(int, int[], int[]);
+    method public int describeContents();
+    method public int[] getBands();
+    method public int[] getChannels();
+    method public int getRadioAccessNetwork();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.RadioAccessSpecifier> CREATOR;
+  }
+
   public class ServiceState implements android.os.Parcelable {
     ctor public ServiceState();
     ctor public ServiceState(android.telephony.ServiceState);
@@ -40840,11 +41113,13 @@
     method protected void copyFrom(android.telephony.ServiceState);
     method public int describeContents();
     method public boolean getIsManualSelection();
+    method public int getNetworkId();
     method public java.lang.String getOperatorAlphaLong();
     method public java.lang.String getOperatorAlphaShort();
     method public java.lang.String getOperatorNumeric();
     method public boolean getRoaming();
     method public int getState();
+    method public int getSystemId();
     method public void setIsManualSelection(boolean);
     method public void setOperatorName(java.lang.String, java.lang.String, java.lang.String);
     method public void setRoaming(boolean);
@@ -40857,6 +41132,7 @@
     field public static final int STATE_IN_SERVICE = 0; // 0x0
     field public static final int STATE_OUT_OF_SERVICE = 1; // 0x1
     field public static final int STATE_POWER_OFF = 3; // 0x3
+    field public static final int UNKNOWN_ID = -1; // 0xffffffff
   }
 
   public class SignalStrength implements android.os.Parcelable {
@@ -41024,7 +41300,7 @@
 
   public class SubscriptionManager {
     method public void addOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
-    method public static android.telephony.SubscriptionManager from(android.content.Context);
+    method public static deprecated android.telephony.SubscriptionManager from(android.content.Context);
     method public android.telephony.SubscriptionInfo getActiveSubscriptionInfo(int);
     method public int getActiveSubscriptionInfoCount();
     method public int getActiveSubscriptionInfoCountMax();
@@ -41117,12 +41393,15 @@
     method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
     method public boolean isWorldPhone();
     method public void listen(android.telephony.PhoneStateListener, int);
+    method public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback);
     method public void sendDialerSpecialCode(java.lang.String);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
     method public void sendVisualVoicemailSms(java.lang.String, int, java.lang.String, android.app.PendingIntent);
     method public deprecated void setDataEnabled(boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
+    method public void setNetworkSelectionModeAutomatic();
+    method public boolean setNetworkSelectionModeManual(java.lang.String, boolean);
     method public boolean setOperatorBrandOverride(java.lang.String);
     method public boolean setPreferredNetworkTypeToGlobal();
     method public void setUserMobileDataEnabled(boolean);
@@ -41220,6 +41499,17 @@
     method public void onReceiveUssdResponseFailed(android.telephony.TelephonyManager, java.lang.String, int);
   }
 
+  public final class TelephonyScanManager {
+    ctor public TelephonyScanManager();
+  }
+
+  public static abstract class TelephonyScanManager.NetworkScanCallback {
+    ctor public TelephonyScanManager.NetworkScanCallback();
+    method public void onComplete();
+    method public void onError(int);
+    method public void onResults(java.util.List<android.telephony.CellInfo>);
+  }
+
   public abstract class VisualVoicemailService extends android.app.Service {
     ctor public VisualVoicemailService();
     method public android.os.IBinder onBind(android.content.Intent);
@@ -44096,6 +44386,7 @@
     method public int indexOfValue(boolean);
     method public int keyAt(int);
     method public void put(int, boolean);
+    method public void removeAt(int);
     method public int size();
     method public boolean valueAt(int);
   }
@@ -47071,6 +47362,7 @@
     method public abstract void setSelected(boolean);
     method public abstract void setText(java.lang.CharSequence);
     method public abstract void setText(java.lang.CharSequence, int, int);
+    method public void setTextIdEntry(java.lang.String);
     method public abstract void setTextLines(int[], int[]);
     method public abstract void setTextStyle(float, int, int, int);
     method public abstract void setTransformation(android.graphics.Matrix);
@@ -47590,6 +47882,7 @@
     method public java.lang.CharSequence getPackageName();
     method public android.view.accessibility.AccessibilityRecord getRecord(int);
     method public int getRecordCount();
+    method public int getWindowChanges();
     method public void initFromParcel(android.os.Parcel);
     method public static android.view.accessibility.AccessibilityEvent obtain(int);
     method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
@@ -47634,6 +47927,17 @@
     field public static final int TYPE_WINDOWS_CHANGED = 4194304; // 0x400000
     field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800
     field public static final int TYPE_WINDOW_STATE_CHANGED = 32; // 0x20
+    field public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 128; // 0x80
+    field public static final int WINDOWS_CHANGE_ACTIVE = 32; // 0x20
+    field public static final int WINDOWS_CHANGE_ADDED = 1; // 0x1
+    field public static final int WINDOWS_CHANGE_BOUNDS = 8; // 0x8
+    field public static final int WINDOWS_CHANGE_CHILDREN = 512; // 0x200
+    field public static final int WINDOWS_CHANGE_FOCUSED = 64; // 0x40
+    field public static final int WINDOWS_CHANGE_LAYER = 16; // 0x10
+    field public static final int WINDOWS_CHANGE_PARENT = 256; // 0x100
+    field public static final int WINDOWS_CHANGE_PIP = 1024; // 0x400
+    field public static final int WINDOWS_CHANGE_REMOVED = 2; // 0x2
+    field public static final int WINDOWS_CHANGE_TITLE = 4; // 0x4
   }
 
   public abstract interface AccessibilityEventSource {
@@ -48345,6 +48649,8 @@
     method public void commit();
     method public void disableAutofillServices();
     method public android.content.ComponentName getAutofillServiceComponentName();
+    method public java.util.List<java.lang.String> getAvailableFieldClassificationAlgorithms();
+    method public java.lang.String getDefaultFieldClassificationAlgorithm();
     method public android.service.autofill.UserData getUserData();
     method public boolean hasEnabledAutofillServices();
     method public boolean isAutofillSupported();
@@ -52104,6 +52410,7 @@
     method public boolean isAllCaps();
     method public boolean isCursorVisible();
     method public boolean isElegantTextHeight();
+    method public boolean isFallbackLineSpacing();
     method public boolean isInputMethodTarget();
     method public boolean isSuggestionsEnabled();
     method public boolean isTextSelectable();
@@ -52147,6 +52454,7 @@
     method public void setError(java.lang.CharSequence);
     method public void setError(java.lang.CharSequence, android.graphics.drawable.Drawable);
     method public void setExtractedText(android.view.inputmethod.ExtractedText);
+    method public void setFallbackLineSpacing(boolean);
     method public void setFilters(android.text.InputFilter[]);
     method public void setFontFeatureSettings(java.lang.String);
     method public boolean setFontVariationSettings(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index a6f4314..b2d5a49 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -137,6 +137,7 @@
     field public static final java.lang.String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
     field public static final java.lang.String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE";
     field public static final java.lang.String RECOVERY = "android.permission.RECOVERY";
+    field public static final java.lang.String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
     field public static final java.lang.String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
     field public static final java.lang.String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
     field public static final java.lang.String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
@@ -173,6 +174,7 @@
     field public static final java.lang.String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
     field public static final java.lang.String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
     field public static final java.lang.String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
+    field public static final java.lang.String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
     field public static final java.lang.String WRITE_APN_SETTINGS = "android.permission.WRITE_APN_SETTINGS";
     field public static final java.lang.String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
     field public static final java.lang.String WRITE_GSERVICES = "android.permission.WRITE_GSERVICES";
@@ -247,8 +249,48 @@
   }
 
   public class AppOpsManager {
+    method public static java.lang.String[] getOpStrs();
     method public void setUidMode(java.lang.String, int, int);
+    field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
     field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
+    field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
+    field public static final java.lang.String OPSTR_ASSIST_STRUCTURE = "android:assist_structure";
+    field public static final java.lang.String OPSTR_AUDIO_ACCESSIBILITY_VOLUME = "android:audio_accessibility_volume";
+    field public static final java.lang.String OPSTR_AUDIO_ALARM_VOLUME = "android:audio_alarm_volume";
+    field public static final java.lang.String OPSTR_AUDIO_BLUETOOTH_VOLUME = "android:audio_bluetooth_volume";
+    field public static final java.lang.String OPSTR_AUDIO_MASTER_VOLUME = "android:audio_master_volume";
+    field public static final java.lang.String OPSTR_AUDIO_MEDIA_VOLUME = "android:audio_media_volume";
+    field public static final java.lang.String OPSTR_AUDIO_NOTIFICATION_VOLUME = "android:audio_notification_volume";
+    field public static final java.lang.String OPSTR_AUDIO_RING_VOLUME = "android:audio_ring_volume";
+    field public static final java.lang.String OPSTR_AUDIO_VOICE_VOLUME = "android:audio_voice_volume";
+    field public static final java.lang.String OPSTR_BIND_ACCESSIBILITY_SERVICE = "bind_accessibility_service";
+    field public static final java.lang.String OPSTR_CHANGE_WIFI_STATE = "change_wifi_state";
+    field public static final java.lang.String OPSTR_GET_ACCOUNTS = "android:get_accounts";
+    field public static final java.lang.String OPSTR_GPS = "android:gps";
+    field public static final java.lang.String OPSTR_INSTANT_APP_START_FOREGROUND = "android:instant_app_start_foreground";
+    field public static final java.lang.String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
+    field public static final java.lang.String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
+    field public static final java.lang.String OPSTR_PLAY_AUDIO = "android:play_audio";
+    field public static final java.lang.String OPSTR_POST_NOTIFICATION = "android:post_notification";
+    field public static final java.lang.String OPSTR_PROJECT_MEDIA = "android:project_media";
+    field public static final java.lang.String OPSTR_READ_CLIPBOARD = "android:read_clipboard";
+    field public static final java.lang.String OPSTR_READ_ICC_SMS = "android:read_icc_sms";
+    field public static final java.lang.String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
+    field public static final java.lang.String OPSTR_REQUEST_DELETE_PACKAGES = "request_delete_packages";
+    field public static final java.lang.String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
+    field public static final java.lang.String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background";
+    field public static final java.lang.String OPSTR_RUN_IN_BACKGROUND = "android:run_in_background";
+    field public static final java.lang.String OPSTR_TAKE_AUDIO_FOCUS = "android:take_audio_focus";
+    field public static final java.lang.String OPSTR_TAKE_MEDIA_BUTTONS = "android:take_media_buttons";
+    field public static final java.lang.String OPSTR_TOAST_WINDOW = "android:toast_window";
+    field public static final java.lang.String OPSTR_TURN_SCREEN_ON = "android:turn_screen_on";
+    field public static final java.lang.String OPSTR_VIBRATE = "android:vibrate";
+    field public static final java.lang.String OPSTR_WAKE_LOCK = "android:wake_lock";
+    field public static final java.lang.String OPSTR_WIFI_SCAN = "android:wifi_scan";
+    field public static final java.lang.String OPSTR_WRITE_CLIPBOARD = "android:write_clipboard";
+    field public static final java.lang.String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms";
+    field public static final java.lang.String OPSTR_WRITE_SMS = "android:write_sms";
+    field public static final java.lang.String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper";
   }
 
   public class BroadcastOptions {
@@ -1340,9 +1382,30 @@
 
 package android.hardware.location {
 
+  public class ContextHubClient implements java.io.Closeable {
+    method public void close();
+    method public android.hardware.location.ContextHubInfo getAttachedHub();
+    method public int sendMessageToNanoApp(android.hardware.location.NanoAppMessage);
+  }
+
+  public class ContextHubClientCallback {
+    ctor public ContextHubClientCallback();
+    method public void onHubReset(android.hardware.location.ContextHubClient);
+    method public void onMessageFromNanoApp(android.hardware.location.ContextHubClient, android.hardware.location.NanoAppMessage);
+    method public void onNanoAppAborted(android.hardware.location.ContextHubClient, long, int);
+    method public void onNanoAppDisabled(android.hardware.location.ContextHubClient, long);
+    method public void onNanoAppEnabled(android.hardware.location.ContextHubClient, long);
+    method public void onNanoAppLoaded(android.hardware.location.ContextHubClient, long);
+    method public void onNanoAppUnloaded(android.hardware.location.ContextHubClient, long);
+  }
+
   public class ContextHubInfo implements android.os.Parcelable {
     ctor public ContextHubInfo();
     method public int describeContents();
+    method public byte getChreApiMajorVersion();
+    method public byte getChreApiMinorVersion();
+    method public short getChrePatchVersion();
+    method public long getChrePlatformId();
     method public int getId();
     method public int getMaxPacketLengthBytes();
     method public android.hardware.location.MemoryRegion[] getMemoryRegions();
@@ -1362,24 +1425,32 @@
   }
 
   public final class ContextHubManager {
-    method public int[] findNanoAppOnHub(int, android.hardware.location.NanoAppFilter);
-    method public int[] getContextHubHandles();
-    method public android.hardware.location.ContextHubInfo getContextHubInfo(int);
-    method public android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int);
-    method public int loadNanoApp(int, android.hardware.location.NanoApp);
-    method public int registerCallback(android.hardware.location.ContextHubManager.Callback);
-    method public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
-    method public int sendMessage(int, int, android.hardware.location.ContextHubMessage);
-    method public int unloadNanoApp(int);
-    method public int unregisterCallback(android.hardware.location.ContextHubManager.Callback);
+    method public android.hardware.location.ContextHubClient createClient(android.hardware.location.ContextHubInfo, android.hardware.location.ContextHubClientCallback, java.util.concurrent.Executor);
+    method public android.hardware.location.ContextHubClient createClient(android.hardware.location.ContextHubInfo, android.hardware.location.ContextHubClientCallback);
+    method public android.hardware.location.ContextHubTransaction<java.lang.Void> disableNanoApp(android.hardware.location.ContextHubInfo, long);
+    method public android.hardware.location.ContextHubTransaction<java.lang.Void> enableNanoApp(android.hardware.location.ContextHubInfo, long);
+    method public deprecated int[] findNanoAppOnHub(int, android.hardware.location.NanoAppFilter);
+    method public deprecated int[] getContextHubHandles();
+    method public deprecated android.hardware.location.ContextHubInfo getContextHubInfo(int);
+    method public java.util.List<android.hardware.location.ContextHubInfo> getContextHubs();
+    method public deprecated android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int);
+    method public deprecated int loadNanoApp(int, android.hardware.location.NanoApp);
+    method public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(android.hardware.location.ContextHubInfo, android.hardware.location.NanoAppBinary);
+    method public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(android.hardware.location.ContextHubInfo);
+    method public deprecated int registerCallback(android.hardware.location.ContextHubManager.Callback);
+    method public deprecated int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
+    method public deprecated int sendMessage(int, int, android.hardware.location.ContextHubMessage);
+    method public deprecated int unloadNanoApp(int);
+    method public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(android.hardware.location.ContextHubInfo, long);
+    method public deprecated int unregisterCallback(android.hardware.location.ContextHubManager.Callback);
   }
 
-  public static abstract class ContextHubManager.Callback {
+  public static abstract deprecated class ContextHubManager.Callback {
     ctor protected ContextHubManager.Callback();
     method public abstract void onMessageReceipt(int, int, android.hardware.location.ContextHubMessage);
   }
 
-  public class ContextHubMessage {
+  public deprecated class ContextHubMessage {
     ctor public ContextHubMessage(int, int, byte[]);
     method public int describeContents();
     method public byte[] getData();
@@ -1392,6 +1463,37 @@
     field public static final android.os.Parcelable.Creator<android.hardware.location.ContextHubMessage> CREATOR;
   }
 
+  public class ContextHubTransaction<T> {
+    method public int getType();
+    method public void setOnCompleteListener(android.hardware.location.ContextHubTransaction.OnCompleteListener<T>, java.util.concurrent.Executor);
+    method public void setOnCompleteListener(android.hardware.location.ContextHubTransaction.OnCompleteListener<T>);
+    method public static java.lang.String typeToString(int, boolean);
+    method public android.hardware.location.ContextHubTransaction.Response<T> waitForResponse(long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException;
+    field public static final int RESULT_FAILED_AT_HUB = 5; // 0x5
+    field public static final int RESULT_FAILED_BAD_PARAMS = 2; // 0x2
+    field public static final int RESULT_FAILED_BUSY = 4; // 0x4
+    field public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8; // 0x8
+    field public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7; // 0x7
+    field public static final int RESULT_FAILED_TIMEOUT = 6; // 0x6
+    field public static final int RESULT_FAILED_UNINITIALIZED = 3; // 0x3
+    field public static final int RESULT_FAILED_UNKNOWN = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+    field public static final int TYPE_DISABLE_NANOAPP = 3; // 0x3
+    field public static final int TYPE_ENABLE_NANOAPP = 2; // 0x2
+    field public static final int TYPE_LOAD_NANOAPP = 0; // 0x0
+    field public static final int TYPE_QUERY_NANOAPPS = 4; // 0x4
+    field public static final int TYPE_UNLOAD_NANOAPP = 1; // 0x1
+  }
+
+  public static abstract interface ContextHubTransaction.OnCompleteListener<L> {
+    method public abstract void onComplete(android.hardware.location.ContextHubTransaction<L>, android.hardware.location.ContextHubTransaction.Response<L>);
+  }
+
+  public static class ContextHubTransaction.Response<R> {
+    method public R getContents();
+    method public int getResult();
+  }
+
   public final class GeofenceHardware {
     method public boolean addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback);
     method public int[] getMonitoringTypes();
@@ -1479,7 +1581,7 @@
     field public static final android.os.Parcelable.Creator<android.hardware.location.MemoryRegion> CREATOR;
   }
 
-  public class NanoApp {
+  public deprecated class NanoApp {
     ctor public NanoApp();
     ctor public deprecated NanoApp(int, byte[]);
     ctor public NanoApp(long, byte[]);
@@ -1508,7 +1610,26 @@
     field public static final android.os.Parcelable.Creator<android.hardware.location.NanoApp> CREATOR;
   }
 
-  public class NanoAppFilter {
+  public final class NanoAppBinary implements android.os.Parcelable {
+    ctor public NanoAppBinary(byte[]);
+    method public int describeContents();
+    method public byte[] getBinary();
+    method public byte[] getBinaryNoHeader();
+    method public int getFlags();
+    method public int getHeaderVersion();
+    method public long getHwHubType();
+    method public long getNanoAppId();
+    method public int getNanoAppVersion();
+    method public byte getTargetChreApiMajorVersion();
+    method public byte getTargetChreApiMinorVersion();
+    method public boolean hasValidHeader();
+    method public boolean isEncrypted();
+    method public boolean isSigned();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppBinary> CREATOR;
+  }
+
+  public deprecated class NanoAppFilter {
     ctor public NanoAppFilter(long, int, int, long);
     method public int describeContents();
     method public boolean testMatch(android.hardware.location.NanoAppInstanceInfo);
@@ -1523,7 +1644,7 @@
     field public static final int VENDOR_ANY = -1; // 0xffffffff
   }
 
-  public class NanoAppInstanceInfo {
+  public deprecated class NanoAppInstanceInfo {
     ctor public NanoAppInstanceInfo();
     method public int describeContents();
     method public long getAppId();
@@ -1541,6 +1662,28 @@
     field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppInstanceInfo> CREATOR;
   }
 
+  public final class NanoAppMessage implements android.os.Parcelable {
+    method public static android.hardware.location.NanoAppMessage createMessageFromNanoApp(long, int, byte[], boolean);
+    method public static android.hardware.location.NanoAppMessage createMessageToNanoApp(long, int, byte[]);
+    method public int describeContents();
+    method public byte[] getMessageBody();
+    method public int getMessageType();
+    method public long getNanoAppId();
+    method public boolean isBroadcastMessage();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR;
+  }
+
+  public final class NanoAppState implements android.os.Parcelable {
+    ctor public NanoAppState(long, int, boolean);
+    method public int describeContents();
+    method public long getNanoAppId();
+    method public long getNanoAppVersion();
+    method public boolean isEnabled();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppState> CREATOR;
+  }
+
 }
 
 package android.hardware.radio {
@@ -1610,6 +1753,15 @@
     field public static final int CLASS_AM_FM = 0; // 0x0
     field public static final int CLASS_DT = 2; // 0x2
     field public static final int CLASS_SAT = 1; // 0x1
+    field public static final int CONFIG_DAB_DAB_LINKING = 6; // 0x6
+    field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
+    field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
+    field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
+    field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+    field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3
+    field public static final int CONFIG_FORCE_MONO = 1; // 0x1
+    field public static final int CONFIG_RDS_AF = 4; // 0x4
+    field public static final int CONFIG_RDS_REG = 5; // 0x5
     field public static final int REGION_ITU_1 = 0; // 0x0
     field public static final int REGION_ITU_2 = 1; // 0x1
     field public static final int REGION_JAPAN = 3; // 0x3
@@ -1789,15 +1941,20 @@
     method public abstract void close();
     method public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
     method public abstract boolean getMute();
+    method public java.util.Map<java.lang.String, java.lang.String> getParameters(java.util.List<java.lang.String>);
     method public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
     method public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>);
     method public abstract boolean hasControl();
-    method public abstract boolean isAnalogForced();
+    method public abstract deprecated boolean isAnalogForced();
     method public abstract boolean isAntennaConnected();
+    method public boolean isConfigFlagSet(int);
+    method public boolean isConfigFlagSupported(int);
     method public abstract int scan(int, boolean);
-    method public abstract void setAnalogForced(boolean);
+    method public abstract deprecated void setAnalogForced(boolean);
+    method public void setConfigFlag(int, boolean);
     method public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig);
     method public abstract int setMute(boolean);
+    method public java.util.Map<java.lang.String, java.lang.String> setParameters(java.util.Map<java.lang.String, java.lang.String>);
     method public abstract boolean startBackgroundScan();
     method public abstract int step(int, boolean);
     method public abstract deprecated int tune(int, int);
@@ -1823,6 +1980,7 @@
     method public void onEmergencyAnnouncement(boolean);
     method public void onError(int);
     method public deprecated void onMetadataChanged(android.hardware.radio.RadioMetadata);
+    method public void onParametersUpdated(java.util.Map<java.lang.String, java.lang.String>);
     method public void onProgramInfoChanged(android.hardware.radio.RadioManager.ProgramInfo);
     method public void onProgramListChanged();
     method public void onTrafficAnnouncement(boolean);
@@ -3115,6 +3273,52 @@
 
 }
 
+package android.net.wifi.rtt {
+
+  public static final class RangingRequest.Builder {
+    method public android.net.wifi.rtt.RangingRequest.Builder addResponder(android.net.wifi.rtt.ResponderConfig);
+  }
+
+  public final class ResponderConfig implements android.os.Parcelable {
+    ctor public ResponderConfig(android.net.MacAddress, int, boolean, int, int, int, int, int);
+    ctor public ResponderConfig(android.net.wifi.aware.PeerHandle, int, boolean, int, int, int, int, int);
+    method public int describeContents();
+    method public static android.net.wifi.rtt.ResponderConfig fromScanResult(android.net.wifi.ScanResult);
+    method public static android.net.wifi.rtt.ResponderConfig fromWifiAwarePeerHandleWithDefaults(android.net.wifi.aware.PeerHandle);
+    method public static android.net.wifi.rtt.ResponderConfig fromWifiAwarePeerMacAddressWithDefaults(android.net.MacAddress);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CHANNEL_WIDTH_160MHZ = 3; // 0x3
+    field public static final int CHANNEL_WIDTH_20MHZ = 0; // 0x0
+    field public static final int CHANNEL_WIDTH_40MHZ = 1; // 0x1
+    field public static final int CHANNEL_WIDTH_80MHZ = 2; // 0x2
+    field public static final int CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4; // 0x4
+    field public static final android.os.Parcelable.Creator<android.net.wifi.rtt.ResponderConfig> CREATOR;
+    field public static final int PREAMBLE_HT = 1; // 0x1
+    field public static final int PREAMBLE_LEGACY = 0; // 0x0
+    field public static final int PREAMBLE_VHT = 2; // 0x2
+    field public static final int RESPONDER_AP = 0; // 0x0
+    field public static final int RESPONDER_AWARE = 4; // 0x4
+    field public static final int RESPONDER_P2P_CLIENT = 3; // 0x3
+    field public static final int RESPONDER_P2P_GO = 2; // 0x2
+    field public static final int RESPONDER_STA = 1; // 0x1
+    field public final int centerFreq0;
+    field public final int centerFreq1;
+    field public final int channelWidth;
+    field public final int frequency;
+    field public final android.net.MacAddress macAddress;
+    field public final android.net.wifi.aware.PeerHandle peerHandle;
+    field public final int preamble;
+    field public final int responderType;
+    field public final boolean supports80211mc;
+  }
+
+  public class WifiRttManager {
+    method public void cancelRanging(android.os.WorkSource);
+    method public void startRanging(android.os.WorkSource, android.net.wifi.rtt.RangingRequest, android.net.wifi.rtt.RangingResultCallback, android.os.Handler);
+  }
+
+}
+
 package android.nfc {
 
   public final class NfcAdapter {
@@ -4070,6 +4274,7 @@
   public class SubscriptionManager {
     method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
     method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
+    field public static final java.lang.String ACTION_MANAGE_SUBSCRIPTION_PLANS = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS";
   }
 
   public final class SubscriptionPlan implements android.os.Parcelable {
@@ -4144,11 +4349,13 @@
     method public java.lang.String getCdmaPrlVersion();
     method public int getCurrentPhoneType();
     method public int getCurrentPhoneType(int);
+    method public int getDataActivationState();
     method public deprecated boolean getDataEnabled();
     method public deprecated boolean getDataEnabled(int);
     method public boolean getEmergencyCallbackMode();
     method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
     method public android.os.Bundle getVisualVoicemailSettings();
+    method public int getVoiceActivationState();
     method public boolean handlePinMmi(java.lang.String);
     method public boolean handlePinMmiForSubscriber(int, java.lang.String);
     method public boolean isDataConnectivityPossible();
@@ -4160,10 +4367,12 @@
     method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
     method public boolean needsOtaServiceProvisioning();
     method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
+    method public void setDataActivationState(int);
     method public deprecated void setDataEnabled(int, boolean);
     method public boolean setRadio(boolean);
     method public boolean setRadioPower(boolean);
     method public deprecated void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
+    method public void setVoiceActivationState(int);
     method public deprecated void silenceRinger();
     method public boolean supplyPin(java.lang.String);
     method public int[] supplyPinReportResult(java.lang.String);
@@ -4177,6 +4386,11 @@
     field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
     field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL";
     field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING";
+    field public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2; // 0x2
+    field public static final int SIM_ACTIVATION_STATE_ACTIVATING = 1; // 0x1
+    field public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3; // 0x3
+    field public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4; // 0x4
+    field public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0; // 0x0
   }
 
   public abstract class VisualVoicemailService extends android.app.Service {
@@ -4359,10 +4573,10 @@
   }
 
   public final class StatsManager {
-    method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String);
-    method public byte[] getData(java.lang.String);
+    method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String);
+    method public byte[] getData(long);
     method public byte[] getMetadata();
-    method public boolean removeConfiguration(java.lang.String);
+    method public boolean removeConfiguration(long);
   }
 
 }
diff --git a/api/system-removed.txt b/api/system-removed.txt
index f98d011..a3bf576 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -142,10 +142,7 @@
   }
 
   public class WifiManager {
-    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 startLocationRestrictedScan(android.os.WorkSource);
   }
 
 }
diff --git a/api/test-current.txt b/api/test-current.txt
index d67e997..b16e700 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -45,6 +45,51 @@
     method public void setTaskOverlay(boolean, boolean);
   }
 
+  public class AppOpsManager {
+    method public static java.lang.String[] getOpStrs();
+    method public void setMode(int, int, java.lang.String, int);
+    field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
+    field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
+    field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
+    field public static final java.lang.String OPSTR_ASSIST_STRUCTURE = "android:assist_structure";
+    field public static final java.lang.String OPSTR_AUDIO_ACCESSIBILITY_VOLUME = "android:audio_accessibility_volume";
+    field public static final java.lang.String OPSTR_AUDIO_ALARM_VOLUME = "android:audio_alarm_volume";
+    field public static final java.lang.String OPSTR_AUDIO_BLUETOOTH_VOLUME = "android:audio_bluetooth_volume";
+    field public static final java.lang.String OPSTR_AUDIO_MASTER_VOLUME = "android:audio_master_volume";
+    field public static final java.lang.String OPSTR_AUDIO_MEDIA_VOLUME = "android:audio_media_volume";
+    field public static final java.lang.String OPSTR_AUDIO_NOTIFICATION_VOLUME = "android:audio_notification_volume";
+    field public static final java.lang.String OPSTR_AUDIO_RING_VOLUME = "android:audio_ring_volume";
+    field public static final java.lang.String OPSTR_AUDIO_VOICE_VOLUME = "android:audio_voice_volume";
+    field public static final java.lang.String OPSTR_BIND_ACCESSIBILITY_SERVICE = "bind_accessibility_service";
+    field public static final java.lang.String OPSTR_CHANGE_WIFI_STATE = "change_wifi_state";
+    field public static final java.lang.String OPSTR_GET_ACCOUNTS = "android:get_accounts";
+    field public static final java.lang.String OPSTR_GPS = "android:gps";
+    field public static final java.lang.String OPSTR_INSTANT_APP_START_FOREGROUND = "android:instant_app_start_foreground";
+    field public static final java.lang.String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
+    field public static final java.lang.String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
+    field public static final java.lang.String OPSTR_PLAY_AUDIO = "android:play_audio";
+    field public static final java.lang.String OPSTR_POST_NOTIFICATION = "android:post_notification";
+    field public static final java.lang.String OPSTR_PROJECT_MEDIA = "android:project_media";
+    field public static final java.lang.String OPSTR_READ_CLIPBOARD = "android:read_clipboard";
+    field public static final java.lang.String OPSTR_READ_ICC_SMS = "android:read_icc_sms";
+    field public static final java.lang.String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
+    field public static final java.lang.String OPSTR_REQUEST_DELETE_PACKAGES = "request_delete_packages";
+    field public static final java.lang.String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
+    field public static final java.lang.String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background";
+    field public static final java.lang.String OPSTR_RUN_IN_BACKGROUND = "android:run_in_background";
+    field public static final java.lang.String OPSTR_TAKE_AUDIO_FOCUS = "android:take_audio_focus";
+    field public static final java.lang.String OPSTR_TAKE_MEDIA_BUTTONS = "android:take_media_buttons";
+    field public static final java.lang.String OPSTR_TOAST_WINDOW = "android:toast_window";
+    field public static final java.lang.String OPSTR_TURN_SCREEN_ON = "android:turn_screen_on";
+    field public static final java.lang.String OPSTR_VIBRATE = "android:vibrate";
+    field public static final java.lang.String OPSTR_WAKE_LOCK = "android:wake_lock";
+    field public static final java.lang.String OPSTR_WIFI_SCAN = "android:wifi_scan";
+    field public static final java.lang.String OPSTR_WRITE_CLIPBOARD = "android:write_clipboard";
+    field public static final java.lang.String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms";
+    field public static final java.lang.String OPSTR_WRITE_SMS = "android:write_sms";
+    field public static final java.lang.String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper";
+  }
+
   public final class NotificationChannelGroup implements android.os.Parcelable {
     method public void setBlocked(boolean);
   }
@@ -165,6 +210,8 @@
     method public abstract int getInstallReason(java.lang.String, android.os.UserHandle);
     method public abstract java.lang.String getPermissionControllerPackageName();
     method public abstract boolean isPermissionReviewModeEnabled();
+    field public static final java.lang.String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
+    field public static final java.lang.String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
   }
 
   public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -318,10 +365,6 @@
     method public void setType(int);
   }
 
-  public class LocationManager {
-    method public int getGnssYearOfHardware();
-  }
-
 }
 
 package android.net {
@@ -439,6 +482,10 @@
     field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
   }
 
+  public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+    field public static final java.lang.String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
+  }
+
   public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
     field public static final java.lang.String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled";
     field public static final java.lang.String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification";
@@ -477,7 +524,8 @@
     method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception;
   }
 
-  public final class EditDistanceScorer extends android.service.autofill.InternalScorer implements android.os.Parcelable android.service.autofill.Scorer {
+  public final class EditDistanceScorer {
+    method public static android.service.autofill.EditDistanceScorer getInstance();
     method public float getScore(android.view.autofill.AutofillValue, java.lang.String);
   }
 
@@ -493,11 +541,6 @@
     ctor public InternalSanitizer();
   }
 
-  public abstract class InternalScorer implements android.os.Parcelable android.service.autofill.Scorer {
-    ctor public InternalScorer();
-    method public abstract float getScore(android.view.autofill.AutofillValue, java.lang.String);
-  }
-
   public abstract class InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
     ctor public InternalTransformation();
   }
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index b61cdec..e87a78e 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -23,8 +23,8 @@
 import android.app.backup.IBackupObserver;
 import android.app.backup.IRestoreObserver;
 import android.app.backup.IRestoreSession;
-import android.app.backup.RestoreSet;
 import android.app.backup.ISelectBackupTransportCallback;
+import android.app.backup.RestoreSet;
 import android.content.ComponentName;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
@@ -37,6 +37,7 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -339,18 +340,16 @@
             System.err.println(PM_NOT_RUNNING_ERR);
         }
         if (installedPackages != null) {
-            List<String> packages = new ArrayList<>();
-            for (PackageInfo pi : installedPackages) {
-                try {
-                    if (mBmgr.isAppEligibleForBackup(pi.packageName)) {
-                        packages.add(pi.packageName);
-                    }
-                } catch (RemoteException e) {
-                    System.err.println(e.toString());
-                    System.err.println(BMGR_NOT_RUNNING_ERR);
-                }
+            String[] packages =
+                    installedPackages.stream().map(p -> p.packageName).toArray(String[]::new);
+            String[] filteredPackages = {};
+            try {
+                filteredPackages = mBmgr.filterAppsEligibleForBackup(packages);
+            } catch (RemoteException e) {
+                System.err.println(e.toString());
+                System.err.println(BMGR_NOT_RUNNING_ERR);
             }
-            backupNowPackages(packages, nonIncrementalBackup);
+            backupNowPackages(Arrays.asList(filteredPackages), nonIncrementalBackup);
         }
     }
 
diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java
index 716bc5f..f75678b 100644
--- a/cmds/content/src/com/android/commands/content/Content.java
+++ b/cmds/content/src/com/android/commands/content/Content.java
@@ -32,12 +32,10 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import libcore.io.Streams;
 
-import libcore.io.IoUtils;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 
 /**
  * This class is a command line utility for manipulating content. A client
@@ -122,13 +120,14 @@
                     + "\n"
                     + "usage: adb shell content read --uri <URI> [--user <USER_ID>]\n"
                     + "  Example:\n"
-                    + "  # cat default ringtone to a file, then pull to host\n"
-                    + "  adb shell 'content read --uri content://settings/system/ringtone >"
-                    + " /mnt/sdcard/tmp.ogg' && adb pull /mnt/sdcard/tmp.ogg\n"
+                    + "  adb shell 'content read --uri content://settings/system/ringtone_cache' > host.ogg\n"
+                    + "\n"
+                    + "usage: adb shell content write --uri <URI> [--user <USER_ID>]\n"
+                    + "  Example:\n"
+                    + "  adb shell 'content write --uri content://settings/system/ringtone_cache' < host.ogg\n"
                     + "\n"
                     + "usage: adb shell content gettype --uri <URI> [--user <USER_ID>]\n"
                     + "  Example:\n"
-                    + "  # Show the mime-type of the URI\n"
                     + "  adb shell content gettype --uri content://media/internal/audio/media/\n"
                     + "\n";
 
@@ -139,6 +138,7 @@
         private static final String ARGUMENT_QUERY = "query";
         private static final String ARGUMENT_CALL = "call";
         private static final String ARGUMENT_READ = "read";
+        private static final String ARGUMENT_WRITE = "write";
         private static final String ARGUMENT_GET_TYPE = "gettype";
         private static final String ARGUMENT_WHERE = "--where";
         private static final String ARGUMENT_BIND = "--bind";
@@ -179,6 +179,8 @@
                     return parseCallCommand();
                 } else if (ARGUMENT_READ.equals(operation)) {
                     return parseReadCommand();
+                } else if (ARGUMENT_WRITE.equals(operation)) {
+                    return parseWriteCommand();
                 } else if (ARGUMENT_GET_TYPE.equals(operation)) {
                     return parseGetTypeCommand();
                 } else {
@@ -339,6 +341,25 @@
             return new ReadCommand(uri, userId);
         }
 
+        private WriteCommand parseWriteCommand() {
+            Uri uri = null;
+            int userId = UserHandle.USER_SYSTEM;
+            for (String argument; (argument = mTokenizer.nextArg())!= null;) {
+                if (ARGUMENT_URI.equals(argument)) {
+                    uri = Uri.parse(argumentValueRequired(argument));
+                } else if (ARGUMENT_USER.equals(argument)) {
+                    userId = Integer.parseInt(argumentValueRequired(argument));
+                } else {
+                    throw new IllegalArgumentException("Unsupported argument: " + argument);
+                }
+            }
+            if (uri == null) {
+                throw new IllegalArgumentException("Content provider URI not specified."
+                        + " Did you specify --uri argument?");
+            }
+            return new WriteCommand(uri, userId);
+        }
+
         public QueryCommand parseQueryCommand() {
             Uri uri = null;
             int userId = UserHandle.USER_SYSTEM;
@@ -561,20 +582,21 @@
 
         @Override
         public void onExecute(IContentProvider provider) throws Exception {
-            final ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null);
-            copy(new FileInputStream(fd.getFileDescriptor()), System.out);
+            try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null)) {
+                Streams.copy(new FileInputStream(fd.getFileDescriptor()), System.out);
+            }
+        }
+    }
+
+    private static class WriteCommand extends Command {
+        public WriteCommand(Uri uri, int userId) {
+            super(uri, userId);
         }
 
-        private static void copy(InputStream is, OutputStream os) throws IOException {
-            final byte[] buffer = new byte[8 * 1024];
-            int read;
-            try {
-                while ((read = is.read(buffer)) > -1) {
-                    os.write(buffer, 0, read);
-                }
-            } finally {
-                IoUtils.closeQuietly(is);
-                IoUtils.closeQuietly(os);
+        @Override
+        public void onExecute(IContentProvider provider) throws Exception {
+            try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "w", null, null)) {
+                Streams.copy(System.in, new FileOutputStream(fd.getFileDescriptor()));
             }
         }
     }
diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
index 4bf956a..847b26a 100644
--- a/cmds/incident_helper/src/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -52,6 +52,12 @@
     return toLowerStr(trimDefault(s));
 }
 
+static inline bool isNumber(const std::string& s) {
+    std::string::const_iterator it = s.begin();
+    while (it != s.end() && std::isdigit(*it)) ++it;
+    return !s.empty() && it == s.end();
+}
+
 // This is similiar to Split in android-base/file.h, but it won't add empty string
 static void split(const std::string& line, std::vector<std::string>& words,
         const trans_func& func, const std::string& delimiters) {
@@ -86,24 +92,80 @@
     return record;
 }
 
+bool getColumnIndices(std::vector<int>& indices, const char** headerNames, const std::string& line) {
+    indices.clear();
+
+    size_t lastIndex = 0;
+    int i = 0;
+    while (headerNames[i] != NULL) {
+        string s = headerNames[i];
+        lastIndex = line.find(s, lastIndex);
+        if (lastIndex == string::npos) {
+            fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
+            return false;
+        }
+        lastIndex += s.length();
+        indices.push_back(lastIndex);
+        i++;
+    }
+
+    return true;
+}
+
 record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) {
     record_t record;
     int lastIndex = 0;
+    int lastBeginning = 0;
     int lineSize = (int)line.size();
     for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) {
         int idx = *it;
-        if (lastIndex > idx || idx > lineSize) {
-            record.clear(); // The indices is wrong, return empty;
+        if (idx <= lastIndex) {
+            // We saved up until lastIndex last time, so we should start at
+            // lastIndex + 1 this time.
+            idx = lastIndex + 1;
+        }
+        if (idx > lineSize) {
+            if (lastIndex < idx && lastIndex < lineSize) {
+                // There's a little bit more for us to save, which we'll do
+                // outside of the loop.
+                break;
+            }
+            // If we're past the end of the line AND we've already saved everything up to the end.
+            fprintf(stderr, "index wrong: lastIndex: %d, idx: %d, lineSize: %d\n", lastIndex, idx, lineSize);
+            record.clear(); // The indices are wrong, return empty.
             return record;
         }
         while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos);
         record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex)));
+        lastBeginning = lastIndex;
         lastIndex = idx;
     }
-    record.push_back(trimDefault(line.substr(lastIndex, lineSize - lastIndex)));
+    if (lineSize - lastIndex > 0) {
+        int beginning = lastIndex;
+        if (record.size() == indices.size()) {
+            // We've already encountered all of the columns...put whatever is
+            // left in the last column.
+            record.pop_back();
+            beginning = lastBeginning;
+        }
+        record.push_back(trimDefault(line.substr(beginning, lineSize - beginning)));
+    }
     return record;
 }
 
+void printRecord(const record_t& record) {
+    fprintf(stderr, "Record: { ");
+    if (record.size() == 0) {
+        fprintf(stderr, "}\n");
+        return;
+    }
+    for(size_t i = 0; i < record.size(); ++i) {
+        if(i != 0) fprintf(stderr, "\", ");
+        fprintf(stderr, "\"%s", record[i].c_str());
+    }
+    fprintf(stderr, "\" }\n");
+}
+
 bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) {
     const auto head = line->find_first_not_of(DEFAULT_WHITESPACE);
     if (head == std::string::npos) return false;
@@ -146,6 +208,19 @@
     return true;
 }
 
+std::string behead(std::string* line, const char cut) {
+    auto found = line->find_first_of(cut);
+    if (found == std::string::npos) {
+        std::string head = line->substr(0);
+        line->assign("");
+        return head;
+    }
+    std::string head = line->substr(0, found);
+    while(line->at(found) == cut) found++; // trim more cut of the rest
+    line->assign(line->substr(found));
+    return head;
+}
+
 int toInt(const std::string& s) {
     return atoi(s.c_str());
 }
@@ -210,7 +285,10 @@
 void
 Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize)
 {
-    if (mFields.find(field) == mFields.end()) return;
+    if (mFields.find(field) == mFields.end()) {
+        fprintf(stderr, "Field '%s' not found", string(field).c_str());
+        return;
+    }
 
     map<std::string, int> enu;
     for (int i = 0; i < enumSize; i++) {
@@ -268,6 +346,8 @@
                 }
             } else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) {
                 proto->write(found, mEnumValuesByName[value]);
+            } else if (isNumber(value)) {
+                proto->write(found, toInt(value));
             } else {
                 return false;
             }
diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h
index 58ef290..53f4438 100644
--- a/cmds/incident_helper/src/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -34,6 +34,8 @@
 const std::string DEFAULT_NEWLINE = "\r\n";
 const std::string TAB_DELIMITER = "\t";
 const std::string COMMA_DELIMITER = ",";
+const std::string PIPE_DELIMITER = "|";
+const std::string PARENTHESES_DELIMITER = "()";
 
 // returns true if c is a-zA-Z0-9 or underscore
 bool isValidChar(char c);
@@ -56,12 +58,23 @@
 record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
 
 /**
+ * Gets the list of end indices of each word in the line and places it in the given vector,
+ * clearing out the vector beforehand. These indices can be used with parseRecordByColumns.
+ * Will return false if there was a problem getting the indices. headerNames
+ * must be NULL terminated.
+ */
+bool getColumnIndices(std::vector<int>& indices, const char* headerNames[], const std::string& line);
+
+/**
  * When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters.
  * This function allows to parse record by its header's column position' indices, must in ascending order.
  * At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters.
  */
 record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE);
 
+/** Prints record_t to stderr */
+void printRecord(const record_t& record);
+
 /**
  * When the line starts/ends with the given key, the function returns true
  * as well as the line argument is changed to the rest trimmed part of the original.
@@ -78,6 +91,11 @@
 bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter = false);
 
 /**
+ * behead the given line by the cut, return the head and reassign the line to be the rest.
+ */
+std::string behead(std::string* line, const char cut);
+
+/**
  * Converts string to the desired type
  */
 int toInt(const std::string& s);
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index c8a0883..418dc3f 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,11 +16,14 @@
 
 #define LOG_TAG "incident_helper"
 
+#include "parsers/BatteryTypeParser.h"
 #include "parsers/CpuFreqParser.h"
 #include "parsers/CpuInfoParser.h"
+#include "parsers/EventLogTagsParser.h"
 #include "parsers/KernelWakesParser.h"
 #include "parsers/PageTypeInfoParser.h"
 #include "parsers/ProcrankParser.h"
+#include "parsers/PsParser.h"
 #include "parsers/SystemPropertiesParser.h"
 
 #include <android-base/file.h>
@@ -53,6 +56,8 @@
         // IDs larger than 1 are section ids reserved in incident.proto
         case 1000:
             return new SystemPropertiesParser();
+        case 1100:
+            return new EventLogTagsParser();
         case 2000:
             return new ProcrankParser();
         case 2001:
@@ -63,6 +68,10 @@
             return new CpuInfoParser();
         case 2004:
             return new CpuFreqParser();
+        case 2005:
+            return new PsParser();
+        case 2006:
+            return new BatteryTypeParser();
         default:
             return NULL;
     }
diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
new file mode 100644
index 0000000..ced6cf8
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/batterytype.proto.h"
+#include "ih_util.h"
+#include "BatteryTypeParser.h"
+
+using namespace android::os;
+
+status_t
+BatteryTypeParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    bool readLine = false;
+
+    ProtoOutputStream proto;
+
+    // parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+
+        if (readLine) {
+            fprintf(stderr, "Multiple lines in file. Unsure what to do.\n");
+            break;
+        }
+
+        proto.write(BatteryTypeProto::TYPE, line);
+
+        readLine = true;
+    }
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.h b/cmds/incident_helper/src/parsers/BatteryTypeParser.h
new file mode 100644
index 0000000..ac0c098
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BATTERY_TYPE_PARSER_H
+#define BATTERY_TYPE_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Battery type parser, parses text in file
+ * /sys/class/power_supply/bms/battery_type.
+ */
+class BatteryTypeParser : public TextParserBase {
+public:
+    BatteryTypeParser() : TextParserBase(String8("BatteryTypeParser")) {};
+    ~BatteryTypeParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // BATTERY_TYPE_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
index 3faca00..d73de54 100644
--- a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
@@ -49,6 +49,7 @@
     vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
     record_t record;
     int nline = 0;
+    int diff = 0;
     bool nextToSwap = false;
     bool nextToUsage = false;
 
@@ -107,18 +108,10 @@
             header = parseHeader(line, "[ %]");
             nextToUsage = false;
 
-            // NAME is not in the list since the last split index is default to the end of line.
-            const char* headerNames[11] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD" };
-            size_t lastIndex = 0;
-            for (int i = 0; i < 11; i++) {
-                string s = headerNames[i];
-                lastIndex = line.find(s, lastIndex);
-                if (lastIndex == string::npos) {
-                    fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
-                    return -1;
-                }
-                lastIndex += s.length();
-                columnIndices.push_back(lastIndex);
+            // NAME is not in the list since we need to modify the end of the CMD index.
+            const char* headerNames[] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD", NULL };
+            if (!getColumnIndices(columnIndices, headerNames, line)) {
+                return -1;
             }
             // Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces.
             // for example: ... CMD             NAME
@@ -128,12 +121,20 @@
             int endCMD = columnIndices.back();
             columnIndices.pop_back();
             columnIndices.push_back(line.find("NAME", endCMD) - 1);
+            // Add NAME index to complete the column list.
+            columnIndices.push_back(columnIndices.back() + 4);
             continue;
         }
 
         record = parseRecordByColumns(line, columnIndices);
-        if (record.size() != header.size()) {
-            fprintf(stderr, "[%s]Line %d has missing fields:\n%s\n", this->name.string(), nline, line.c_str());
+        diff = record.size() - header.size();
+        if (diff < 0) {
+            fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+            printRecord(record);
+            continue;
+        } else if (diff > 0) {
+            fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+            printRecord(record);
             continue;
         }
 
diff --git a/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp b/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp
new file mode 100644
index 0000000..73e37bd
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/util/event_log_tags.proto.h"
+#include "ih_util.h"
+#include "EventLogTagsParser.h"
+
+status_t
+EventLogTagsParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+
+    ProtoOutputStream proto;
+
+    // parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+        string debug = line;
+        string tagNumber = behead(&line, ' ');
+        string tagName = behead(&line, ' ');
+        if (tagNumber == "" || tagName == "") {
+            fprintf(stderr, "Bad line, expect at least two parts: %s[%s, %s]\n",
+                debug.c_str(), tagNumber.c_str(), tagName.c_str());
+            continue;
+        }
+
+        long long token = proto.start(EventLogTagMapProto::EVENT_LOG_TAGS);
+        proto.write(EventLogTag::TAG_NUMBER, toInt(tagNumber));
+        proto.write(EventLogTag::TAG_NAME, tagName);
+
+        record_t valueDescriptors = parseRecord(line, PARENTHESES_DELIMITER);
+        for (size_t i = 0; i < valueDescriptors.size(); i++) {
+            record_t valueDescriptor = parseRecord(valueDescriptors[i], PIPE_DELIMITER);
+            if (valueDescriptor.size() != 2 && valueDescriptor.size() != 3) {
+                // If the parts doesn't contains pipe, then skips it.
+                continue;
+            }
+            long long descriptorToken = proto.start(EventLogTag::VALUE_DESCRIPTORS);
+            proto.write(EventLogTag::ValueDescriptor::NAME, valueDescriptor[0]);
+            proto.write(EventLogTag::ValueDescriptor::TYPE, toInt(valueDescriptor[1]));
+            if (valueDescriptor.size() == 3) {
+                char c = valueDescriptor[2][0];
+                int unit = 0;
+                if (c < '0' || c > '9') {
+                    unit = (int) c;
+                } else {
+                    unit = toInt(valueDescriptor[2]);
+                }
+                proto.write(EventLogTag::ValueDescriptor::UNIT, unit);
+            }
+            proto.end(descriptorToken);
+        }
+        proto.end(token);
+    }
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/EventLogTagsParser.h b/cmds/incident_helper/src/parsers/EventLogTagsParser.h
new file mode 100644
index 0000000..79057ce
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/EventLogTagsParser.h
@@ -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.
+ */
+
+#ifndef EVENT_LOG_TAGS_PARSER_H
+#define EVENT_LOG_TAGS_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * event.logtags parser, parse file in /system/etc/event-log-tags
+ */
+class EventLogTagsParser : public TextParserBase {
+public:
+    EventLogTagsParser() : TextParserBase(String8("EventLogTagsParser")) {};
+    ~EventLogTagsParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // EVENT_LOG_TAGS_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
index ada4a5d..cae51ab 100644
--- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -47,10 +47,14 @@
         // parse for each record, the line delimiter is \t only!
         record = parseRecord(line, TAB_DELIMITER);
 
-        if (record.size() != header.size()) {
+        if (record.size() < header.size()) {
             // TODO: log this to incident report!
             fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
             continue;
+        } else if (record.size() > header.size()) {
+            // TODO: log this to incident report!
+            fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.string(), nline, line.c_str());
+            continue;
         }
 
         long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES);
diff --git a/cmds/incident_helper/src/parsers/PsParser.cpp b/cmds/incident_helper/src/parsers/PsParser.cpp
new file mode 100644
index 0000000..e9014ca
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PsParser.cpp
@@ -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.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/ps.proto.h"
+#include "ih_util.h"
+#include "PsParser.h"
+
+using namespace android::os;
+
+status_t PsParser::Parse(const int in, const int out) const {
+    Reader reader(in);
+    string line;
+    header_t header;  // the header of /d/wakeup_sources
+    vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
+    record_t record;  // retain each record
+    int nline = 0;
+    int diff = 0;
+
+    ProtoOutputStream proto;
+    Table table(PsDumpProto::Process::_FIELD_NAMES, PsDumpProto::Process::_FIELD_IDS, PsDumpProto::Process::_FIELD_COUNT);
+    const char* pcyNames[] = { "fg", "bg", "ta" };
+    const int pcyValues[] = {PsDumpProto::Process::POLICY_FG, PsDumpProto::Process::POLICY_BG, PsDumpProto::Process::POLICY_TA};
+    table.addEnumTypeMap("pcy", pcyNames, pcyValues, 3);
+    const char* sNames[] = { "D", "R", "S", "T", "t", "X", "Z" };
+    const int sValues[] = {PsDumpProto::Process::STATE_D, PsDumpProto::Process::STATE_R, PsDumpProto::Process::STATE_S, PsDumpProto::Process::STATE_T, PsDumpProto::Process::STATE_TRACING, PsDumpProto::Process::STATE_X, PsDumpProto::Process::STATE_Z};
+    table.addEnumTypeMap("s", sNames, sValues, 7);
+
+    // Parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+
+        if (nline++ == 0) {
+            header = parseHeader(line, DEFAULT_WHITESPACE);
+
+            const char* headerNames[] = { "LABEL", "USER", "PID", "TID", "PPID", "VSZ", "RSS", "WCHAN", "ADDR", "S", "PRI", "NI", "RTPRIO", "SCH", "PCY", "TIME", "CMD", NULL };
+            if (!getColumnIndices(columnIndices, headerNames, line)) {
+                return -1;
+            }
+
+            continue;
+        }
+
+        record = parseRecordByColumns(line, columnIndices);
+
+        diff = record.size() - header.size();
+        if (diff < 0) {
+            // TODO: log this to incident report!
+            fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+            printRecord(record);
+            continue;
+        } else if (diff > 0) {
+            // TODO: log this to incident report!
+            fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+            printRecord(record);
+            continue;
+        }
+
+        long long token = proto.start(PsDumpProto::PROCESSES);
+        for (int i=0; i<(int)record.size(); i++) {
+            if (!table.insertField(&proto, header[i], record[i])) {
+                fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
+                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
+            }
+        }
+        proto.end(token);
+    }
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
diff --git a/core/java/android/service/autofill/Scorer.java b/cmds/incident_helper/src/parsers/PsParser.h
similarity index 62%
rename from core/java/android/service/autofill/Scorer.java
rename to cmds/incident_helper/src/parsers/PsParser.h
index c401855..9488e40 100644
--- a/core/java/android/service/autofill/Scorer.java
+++ b/cmds/incident_helper/src/parsers/PsParser.h
@@ -13,16 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.service.autofill;
+
+#ifndef PS_PARSER_H
+#define PS_PARSER_H
+
+#include "TextParserBase.h"
 
 /**
- * Helper class used to calculate a score.
- *
- * <p>Typically used to calculate the
- * <a href="AutofillService.html#FieldClassification">field classification</a> score between an
- * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value
- * predicted by an autofill service.
+ * PS parser, parses output of 'ps' command to protobuf.
  */
-public interface Scorer {
+class PsParser : public TextParserBase {
+public:
+    PsParser() : TextParserBase(String8("Ps")) {};
+    ~PsParser() {};
 
-}
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // PS_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
index 23393da0..7b0ac0b 100644
--- a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
+++ b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
@@ -149,12 +149,25 @@
                    SystemPropertiesProto::Ro::Product::_FIELD_COUNT);
     Message product(&productTable);
 
-    Table vendorTable(SystemPropertiesProto::Ro::Product::Vendor::_FIELD_NAMES,
+    Table pVendorTable(SystemPropertiesProto::Ro::Product::Vendor::_FIELD_NAMES,
             SystemPropertiesProto::Ro::Product::Vendor::_FIELD_IDS,
             SystemPropertiesProto::Ro::Product::Vendor::_FIELD_COUNT);
-    Message vendor(&vendorTable);
-    product.addSubMessage(SystemPropertiesProto::Ro::Product::VENDOR, &vendor);
+    Message pVendor(&pVendorTable);
+    product.addSubMessage(SystemPropertiesProto::Ro::Product::VENDOR, &pVendor);
     ro.addSubMessage(SystemPropertiesProto::Ro::PRODUCT, &product);
+
+    Table telephonyTable(SystemPropertiesProto::Ro::Telephony::_FIELD_NAMES,
+                   SystemPropertiesProto::Ro::Telephony::_FIELD_IDS,
+                   SystemPropertiesProto::Ro::Telephony::_FIELD_COUNT);
+    Message telephony(&telephonyTable);
+    ro.addSubMessage(SystemPropertiesProto::Ro::TELEPHONY, &telephony);
+
+    Table vendorTable(SystemPropertiesProto::Ro::Vendor::_FIELD_NAMES,
+                   SystemPropertiesProto::Ro::Vendor::_FIELD_IDS,
+                   SystemPropertiesProto::Ro::Vendor::_FIELD_COUNT);
+    Message vendor(&vendorTable);
+    ro.addSubMessage(SystemPropertiesProto::Ro::VENDOR, &vendor);
+
     sysProp.addSubMessage(SystemPropertiesProto::RO, &ro);
 
     Table sysTable(SystemPropertiesProto::Sys::_FIELD_NAMES,
diff --git a/cmds/incident_helper/testdata/batterytype.txt b/cmds/incident_helper/testdata/batterytype.txt
new file mode 100644
index 0000000..c763d36
--- /dev/null
+++ b/cmds/incident_helper/testdata/batterytype.txt
@@ -0,0 +1 @@
+random_battery_type_string
diff --git a/cmds/incident_helper/testdata/event-log-tags.txt b/cmds/incident_helper/testdata/event-log-tags.txt
new file mode 100644
index 0000000..35396bf
--- /dev/null
+++ b/cmds/incident_helper/testdata/event-log-tags.txt
@@ -0,0 +1,6 @@
+42 answer (to life the universe etc|3)
+314 pi
+1004 chatty (dropped|3)
+1005 tag_def (tag|1),(name|3),(format|3)
+2747 contacts_aggregation (aggregation time|2|3), (count|1|1)
+1397638484 snet_event_log (subtag|3) (uid|1) (message|3|s)
\ No newline at end of file
diff --git a/cmds/incident_helper/testdata/ps.txt b/cmds/incident_helper/testdata/ps.txt
new file mode 100644
index 0000000..72dafc2c4
--- /dev/null
+++ b/cmds/incident_helper/testdata/ps.txt
@@ -0,0 +1,9 @@
+LABEL                          USER           PID   TID  PPID     VSZ    RSS WCHAN            ADDR S PRI  NI RTPRIO SCH PCY     TIME CMD
+u:r:init:s0                    root             1     1     0   15816   2636 SyS_epoll_wait      0 S  19   0      -   0  fg 00:00:01 init
+u:r:kernel:s0                  root             2     2     0       0      0 kthreadd            0 S  19   0      -   0  fg 00:00:00 kthreadd
+u:r:surfaceflinger:s0          system         499   534     1   73940  22024 futex_wait_queue_me 0 S  42  -9      2   1  fg 00:00:00 EventThread
+u:r:hal_gnss_default:s0        gps            670  2004     1   43064   7272 poll_schedule_timeout 0 S 19  0      -   0  fg 00:00:00 Loc_hal_worker
+u:r:platform_app:s0:c512,c768  u0_a48        1660  1976   806 4468612 138328 binder_thread_read  0 S  35 -16      -   0  ta 00:00:00 HwBinder:1660_1
+u:r:perfd:s0                   root          1939  1946     1   18132   2088 __skb_recv_datagram 7b9782fd14 S 19 0 -  0     00:00:00 perfd
+u:r:perfd:s0                   root          1939  1955     1   18132   2088 do_sigtimedwait 7b9782ff6c S 19 0    -   0     00:00:00 POSIX timer 0
+u:r:shell:s0                   shell         2645  2645   802   11664   2972 0          7f67a2f8b4 R  19   0      -   0  fg 00:00:00 ps
diff --git a/cmds/incident_helper/tests/BatteryTypeParser_test.cpp b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp
new file mode 100644
index 0000000..7fbe22d
--- /dev/null
+++ b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BatteryTypeParser.h"
+
+#include "frameworks/base/core/proto/android/os/batterytype.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message_lite.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class BatteryTypeParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(BatteryTypeParserTest, Success) {
+    const string testFile = kTestDataPath + "batterytype.txt";
+    BatteryTypeParser parser;
+    BatteryTypeProto expected;
+
+    expected.set_type("random_battery_type_string");
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), expected.SerializeAsString());
+    close(fd);
+}
diff --git a/cmds/incident_helper/tests/EventLogTagsParser_test.cpp b/cmds/incident_helper/tests/EventLogTagsParser_test.cpp
new file mode 100644
index 0000000..d0d1f1e
--- /dev/null
+++ b/cmds/incident_helper/tests/EventLogTagsParser_test.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 "EventLogTagsParser.h"
+
+#include "frameworks/base/core/proto/android/util/event_log_tags.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message_lite.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::util;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class EventLogTagsParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(EventLogTagsParserTest, Success) {
+    const string testFile = kTestDataPath + "event-log-tags.txt";
+
+    EventLogTagsParser parser;
+    EventLogTagMapProto expected;
+
+    EventLogTag* eventLogTag;
+    EventLogTag::ValueDescriptor* desp;
+
+    eventLogTag = expected.add_event_log_tags();
+    eventLogTag->set_tag_number(42);
+    eventLogTag->set_tag_name("answer");
+    desp = eventLogTag->add_value_descriptors();
+    desp->set_name("to life the universe etc");
+    desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING);
+
+    eventLogTag = expected.add_event_log_tags();
+    eventLogTag->set_tag_number(314);
+    eventLogTag->set_tag_name("pi");
+
+    eventLogTag = expected.add_event_log_tags();
+    eventLogTag->set_tag_number(1004);
+    eventLogTag->set_tag_name("chatty");
+    desp = eventLogTag->add_value_descriptors();
+    desp->set_name("dropped");
+    desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING);
+
+    eventLogTag = expected.add_event_log_tags();
+    eventLogTag->set_tag_number(1005);
+    eventLogTag->set_tag_name("tag_def");
+    desp = eventLogTag->add_value_descriptors();
+    desp->set_name("tag");
+    desp->set_type(EventLogTag_ValueDescriptor_DataType_INT);
+    desp = eventLogTag->add_value_descriptors();
+    desp->set_name("name");
+    desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING);
+    desp = eventLogTag->add_value_descriptors();
+    desp->set_name("format");
+    desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING);
+
+    eventLogTag = expected.add_event_log_tags();
+    eventLogTag->set_tag_number(2747);
+    eventLogTag->set_tag_name("contacts_aggregation");
+    desp = eventLogTag->add_value_descriptors();
+    desp->set_name("aggregation time");
+    desp->set_type(EventLogTag_ValueDescriptor_DataType_LONG);
+    desp->set_unit(EventLogTag_ValueDescriptor_DataUnit_MILLISECONDS);
+    desp = eventLogTag->add_value_descriptors();
+    desp->set_name("count");
+    desp->set_type(EventLogTag_ValueDescriptor_DataType_INT);
+    desp->set_unit(EventLogTag_ValueDescriptor_DataUnit_OBJECTS);
+
+    eventLogTag = expected.add_event_log_tags();
+    eventLogTag->set_tag_number(1397638484);
+    eventLogTag->set_tag_name("snet_event_log");
+    desp = eventLogTag->add_value_descriptors();
+    desp->set_name("subtag");
+    desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING);
+    desp = eventLogTag->add_value_descriptors();
+    desp->set_name("uid");
+    desp->set_type(EventLogTag_ValueDescriptor_DataType_INT);
+    desp = eventLogTag->add_value_descriptors();
+    desp->set_name("message");
+    desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING);
+    desp->set_unit(EventLogTag_ValueDescriptor_DataUnit_SECONDS);
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), expected.SerializeAsString());
+    close(fd);
+}
diff --git a/cmds/incident_helper/tests/PsParser_test.cpp b/cmds/incident_helper/tests/PsParser_test.cpp
new file mode 100644
index 0000000..1f03a7f
--- /dev/null
+++ b/cmds/incident_helper/tests/PsParser_test.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "PsParser.h"
+
+#include "frameworks/base/core/proto/android/os/ps.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message_lite.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class PsParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(PsParserTest, Normal) {
+    const string testFile = kTestDataPath + "ps.txt";
+    PsParser parser;
+    PsDumpProto expected;
+    PsDumpProto got;
+
+    PsDumpProto::Process* record1 = expected.add_processes();
+    record1->set_label("u:r:init:s0");
+    record1->set_user("root");
+    record1->set_pid(1);
+    record1->set_tid(1);
+    record1->set_ppid(0);
+    record1->set_vsz(15816);
+    record1->set_rss(2636);
+    record1->set_wchan("SyS_epoll_wait");
+    record1->set_addr("0");
+    record1->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record1->set_pri(19);
+    record1->set_ni(0);
+    record1->set_rtprio("-");
+    record1->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record1->set_pcy(PsDumpProto::Process::POLICY_FG);
+    record1->set_time("00:00:01");
+    record1->set_cmd("init");
+
+    PsDumpProto::Process* record2 = expected.add_processes();
+    record2->set_label("u:r:kernel:s0");
+    record2->set_user("root");
+    record2->set_pid(2);
+    record2->set_tid(2);
+    record2->set_ppid(0);
+    record2->set_vsz(0);
+    record2->set_rss(0);
+    record2->set_wchan("kthreadd");
+    record2->set_addr("0");
+    record2->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record2->set_pri(19);
+    record2->set_ni(0);
+    record2->set_rtprio("-");
+    record2->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record2->set_pcy(PsDumpProto::Process::POLICY_FG);
+    record2->set_time("00:00:00");
+    record2->set_cmd("kthreadd");
+
+    PsDumpProto::Process* record3 = expected.add_processes();
+    record3->set_label("u:r:surfaceflinger:s0");
+    record3->set_user("system");
+    record3->set_pid(499);
+    record3->set_tid(534);
+    record3->set_ppid(1);
+    record3->set_vsz(73940);
+    record3->set_rss(22024);
+    record3->set_wchan("futex_wait_queue_me");
+    record3->set_addr("0");
+    record3->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record3->set_pri(42);
+    record3->set_ni(-9);
+    record3->set_rtprio("2");
+    record3->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_FIFO);
+    record3->set_pcy(PsDumpProto::Process::POLICY_FG);
+    record3->set_time("00:00:00");
+    record3->set_cmd("EventThread");
+
+    PsDumpProto::Process* record4 = expected.add_processes();
+    record4->set_label("u:r:hal_gnss_default:s0");
+    record4->set_user("gps");
+    record4->set_pid(670);
+    record4->set_tid(2004);
+    record4->set_ppid(1);
+    record4->set_vsz(43064);
+    record4->set_rss(7272);
+    record4->set_wchan("poll_schedule_timeout");
+    record4->set_addr("0");
+    record4->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record4->set_pri(19);
+    record4->set_ni(0);
+    record4->set_rtprio("-");
+    record4->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record4->set_pcy(PsDumpProto::Process::POLICY_FG);
+    record4->set_time("00:00:00");
+    record4->set_cmd("Loc_hal_worker");
+
+    PsDumpProto::Process* record5 = expected.add_processes();
+    record5->set_label("u:r:platform_app:s0:c512,c768");
+    record5->set_user("u0_a48");
+    record5->set_pid(1660);
+    record5->set_tid(1976);
+    record5->set_ppid(806);
+    record5->set_vsz(4468612);
+    record5->set_rss(138328);
+    record5->set_wchan("binder_thread_read");
+    record5->set_addr("0");
+    record5->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record5->set_pri(35);
+    record5->set_ni(-16);
+    record5->set_rtprio("-");
+    record5->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record5->set_pcy(PsDumpProto::Process::POLICY_TA);
+    record5->set_time("00:00:00");
+    record5->set_cmd("HwBinder:1660_1");
+
+    PsDumpProto::Process* record6 = expected.add_processes();
+    record6->set_label("u:r:perfd:s0");
+    record6->set_user("root");
+    record6->set_pid(1939);
+    record6->set_tid(1946);
+    record6->set_ppid(1);
+    record6->set_vsz(18132);
+    record6->set_rss(2088);
+    record6->set_wchan("__skb_recv_datagram");
+    record6->set_addr("7b9782fd14");
+    record6->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record6->set_pri(19);
+    record6->set_ni(0);
+    record6->set_rtprio("-");
+    record6->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record6->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN);
+    record6->set_time("00:00:00");
+    record6->set_cmd("perfd");
+
+    PsDumpProto::Process* record7 = expected.add_processes();
+    record7->set_label("u:r:perfd:s0");
+    record7->set_user("root");
+    record7->set_pid(1939);
+    record7->set_tid(1955);
+    record7->set_ppid(1);
+    record7->set_vsz(18132);
+    record7->set_rss(2088);
+    record7->set_wchan("do_sigtimedwait");
+    record7->set_addr("7b9782ff6c");
+    record7->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record7->set_pri(19);
+    record7->set_ni(0);
+    record7->set_rtprio("-");
+    record7->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record7->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN);
+    record7->set_time("00:00:00");
+    record7->set_cmd("POSIX timer 0");
+
+    PsDumpProto::Process* record8 = expected.add_processes();
+    record8->set_label("u:r:shell:s0");
+    record8->set_user("shell");
+    record8->set_pid(2645);
+    record8->set_tid(2645);
+    record8->set_ppid(802);
+    record8->set_vsz(11664);
+    record8->set_rss(2972);
+    record8->set_wchan("0");
+    record8->set_addr("7f67a2f8b4");
+    record8->set_s(PsDumpProto_Process_ProcessStateCode_STATE_R);
+    record8->set_pri(19);
+    record8->set_ni(0);
+    record8->set_rtprio("-");
+    record8->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record8->set_pcy(PsDumpProto::Process::POLICY_FG);
+    record8->set_time("00:00:00");
+    record8->set_cmd("ps");
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    got.ParseFromString(GetCapturedStdout());
+    bool matches = true;
+
+    if (got.processes_size() != expected.processes_size()) {
+        fprintf(stderr, "Got %d processes, want %d\n", got.processes_size(), expected.processes_size());
+        matches = false;
+    } else {
+        int n = got.processes_size();
+        for (int i = 0; i < n; i++) {
+            PsDumpProto::Process g = got.processes(i);
+            PsDumpProto::Process e = expected.processes(i);
+
+            if (g.label() != e.label()) {
+                fprintf(stderr, "prcs[%d]: Invalid label. Got %s, want %s\n", i, g.label().c_str(), e.label().c_str());
+                matches = false;
+            }
+            if (g.user() != e.user()) {
+                fprintf(stderr, "prcs[%d]: Invalid user. Got %s, want %s\n", i, g.user().c_str(), e.user().c_str());
+                matches = false;
+            }
+            if (g.pid() != e.pid()) {
+                fprintf(stderr, "prcs[%d]: Invalid pid. Got %d, want %d\n", i, g.pid(), e.pid());
+                matches = false;
+            }
+            if (g.tid() != e.tid()) {
+                fprintf(stderr, "prcs[%d]: Invalid tid. Got %d, want %d\n", i, g.tid(), e.tid());
+                matches = false;
+            }
+            if (g.ppid() != e.ppid()) {
+                fprintf(stderr, "prcs[%d]: Invalid ppid. Got %d, want %d\n", i, g.ppid(), e.ppid());
+                matches = false;
+            }
+            if (g.vsz() != e.vsz()) {
+                fprintf(stderr, "prcs[%d]: Invalid vsz. Got %d, want %d\n", i, g.vsz(), e.vsz());
+                matches = false;
+            }
+            if (g.rss() != e.rss()) {
+                fprintf(stderr, "prcs[%d]: Invalid rss. Got %d, want %d\n", i, g.rss(), e.rss());
+                matches = false;
+            }
+            if (g.wchan() != e.wchan()) {
+                fprintf(stderr, "prcs[%d]: Invalid wchan. Got %s, want %s\n", i, g.wchan().c_str(), e.wchan().c_str());
+                matches = false;
+            }
+            if (g.addr() != e.addr()) {
+                fprintf(stderr, "prcs[%d]: Invalid addr. Got %s, want %s\n", i, g.addr().c_str(), e.addr().c_str());
+                matches = false;
+            }
+            if (g.s() != e.s()) {
+                fprintf(stderr, "prcs[%d]: Invalid s. Got %u, want %u\n", i, g.s(), e.s());
+                matches = false;
+            }
+            if (g.pri() != e.pri()) {
+                fprintf(stderr, "prcs[%d]: Invalid pri. Got %d, want %d\n", i, g.pri(), e.pri());
+                matches = false;
+            }
+            if (g.ni() != e.ni()) {
+                fprintf(stderr, "prcs[%d]: Invalid ni. Got %d, want %d\n", i, g.ni(), e.ni());
+                matches = false;
+            }
+            if (g.rtprio() != e.rtprio()) {
+                fprintf(stderr, "prcs[%d]: Invalid rtprio. Got %s, want %s\n", i, g.rtprio().c_str(), e.rtprio().c_str());
+                matches = false;
+            }
+            if (g.sch() != e.sch()) {
+                fprintf(stderr, "prcs[%d]: Invalid sch. Got %u, want %u\n", i, g.sch(), e.sch());
+                matches = false;
+            }
+            if (g.pcy() != e.pcy()) {
+                fprintf(stderr, "prcs[%d]: Invalid pcy. Got %u, want %u\n", i, g.pcy(), e.pcy());
+                matches = false;
+            }
+            if (g.time() != e.time()) {
+                fprintf(stderr, "prcs[%d]: Invalid time. Got %s, want %s\n", i, g.time().c_str(), e.time().c_str());
+                matches = false;
+            }
+            if (g.cmd() != e.cmd()) {
+                fprintf(stderr, "prcs[%d]: Invalid cmd. Got %s, want %s\n", i, g.cmd().c_str(), e.cmd().c_str());
+                matches = false;
+            }
+        }
+    }
+
+    EXPECT_TRUE(matches);
+    close(fd);
+}
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
index 5740b33..efe714d 100644
--- a/cmds/incident_helper/tests/ih_util_test.cpp
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -60,6 +60,9 @@
     result = parseRecord("123,456,78_9", ",");
     expected = { "123", "456", "78_9" };
     EXPECT_EQ(expected, result);
+
+    result = parseRecord("", " ");
+    EXPECT_TRUE(result.empty());
 }
 
 TEST(IhUtilTest, ParseRecordByColumns) {
@@ -71,11 +74,29 @@
     EXPECT_EQ(expected, result);
 
     result = parseRecordByColumns("abc \t2345  6789 ", indices);
-    expected = { "abc", "2345", "6789" };
+    expected = { "abc", "2345  6789" };
     EXPECT_EQ(expected, result);
 
-    result = parseRecordByColumns("abc \t23456789 bob", indices);
-    expected = { "abc", "23456789", "bob" };
+    std::string extraColumn1 = "abc \t23456789 bob";
+    std::string emptyMidColm = "abc \t         bob";
+    std::string longFirstClm = "abcdefgt\t6789 bob";
+    std::string lngFrstEmpty = "abcdefgt\t     bob";
+
+    result = parseRecordByColumns(extraColumn1, indices);
+    expected = { "abc", "23456789 bob" };
+    EXPECT_EQ(expected, result);
+
+    // 2nd column should be treated as an empty entry.
+    result = parseRecordByColumns(emptyMidColm, indices);
+    expected = { "abc", "bob" };
+    EXPECT_EQ(expected, result);
+
+    result = parseRecordByColumns(longFirstClm, indices);
+    expected = { "abcdefgt", "6789 bob" };
+    EXPECT_EQ(expected, result);
+
+    result = parseRecordByColumns(lngFrstEmpty, indices);
+    expected = { "abcdefgt", "bob" };
     EXPECT_EQ(expected, result);
 }
 
@@ -115,6 +136,22 @@
     EXPECT_THAT(data4, StrEq(" 243%abc"));
 }
 
+TEST(IhUtilTest, behead) {
+    string testcase1 = "81002 dropbox_file_copy (a)(b)";
+    EXPECT_THAT(behead(&testcase1, ' '), StrEq("81002"));
+    EXPECT_THAT(behead(&testcase1, ' '), StrEq("dropbox_file_copy"));
+    EXPECT_THAT(testcase1, "(a)(b)");
+
+    string testcase2 = "adbce,erwqr";
+    EXPECT_THAT(behead(&testcase2, ' '), StrEq("adbce,erwqr"));
+    EXPECT_THAT(testcase2, "");
+
+    string testcase3 = "first second";
+    EXPECT_THAT(behead(&testcase3, ' '), StrEq("first"));
+    EXPECT_THAT(behead(&testcase3, ' '), StrEq("second"));
+    EXPECT_THAT(testcase3, "");
+}
+
 TEST(IhUtilTest, Reader) {
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index fb8ef63..8420bc8 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -14,6 +14,9 @@
 
 LOCAL_PATH:= $(call my-dir)
 
+# proto files used in incidentd to generate cppstream proto headers.
+PROTO_FILES:= frameworks/base/core/proto/android/util/log.proto
+
 # ========= #
 # incidentd #
 # ========= #
@@ -59,18 +62,34 @@
         libutils
 
 LOCAL_MODULE_CLASS := EXECUTABLES
+
 gen_src_dir := $(local-generated-sources-dir)
 
-GEN := $(gen_src_dir)/src/section_list.cpp
-$(GEN): $(HOST_OUT_EXECUTABLES)/incident-section-gen
-$(GEN): PRIVATE_CUSTOM_TOOL = \
+# generate section_list.cpp
+GEN_LIST := $(gen_src_dir)/src/section_list.cpp
+$(GEN_LIST): $(HOST_OUT_EXECUTABLES)/incident-section-gen
+$(GEN_LIST): PRIVATE_CUSTOM_TOOL = \
     $(HOST_OUT_EXECUTABLES)/incident-section-gen incidentd > $@
-$(GEN): $(HOST_OUT_EXECUTABLES)/incident-section-gen
+$(GEN_LIST): $(HOST_OUT_EXECUTABLES)/incident-section-gen
 	$(transform-generated-source)
-LOCAL_GENERATED_SOURCES += $(GEN)
+LOCAL_GENERATED_SOURCES += $(GEN_LIST)
+GEN_LIST:=
+
+# generate cppstream proto, add proto files to PROTO_FILES
+GEN_PROTO := $(gen_src_dir)/proto.timestamp
+$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES)
+$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir)
+$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \
+    $(HOST_OUT_EXECUTABLES)/aprotoc --plugin=protoc-gen-cppstream=$(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream \
+        --cppstream_out=$(PRIVATE_GEN_SRC_DIR) -Iexternal/protobuf/src -I . \
+        $(PROTO_FILES) \
+    && touch $@
+$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc
+	$(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(GEN_PROTO)
+GEN_PROTO:=
 
 gen_src_dir:=
-GEN:=
 
 ifeq ($(BUILD_WITH_INCIDENTD_RC), true)
 LOCAL_INIT_RC := incidentd.rc
@@ -122,4 +141,22 @@
 
 LOCAL_TEST_DATA := $(call find-test-data-in-subdirs, $(LOCAL_PATH), *, testdata)
 
+LOCAL_MODULE_CLASS := NATIVE_TESTS
+gen_src_dir := $(local-generated-sources-dir)
+# generate cppstream proto for testing
+GEN_PROTO := $(gen_src_dir)/log.proto.timestamp
+$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES)
+$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir)
+$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \
+    $(HOST_OUT_EXECUTABLES)/aprotoc --plugin=protoc-gen-cppstream=$(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream \
+        --cppstream_out=$(PRIVATE_GEN_SRC_DIR) -Iexternal/protobuf/src -I . \
+        $(PROTO_FILES) \
+    && touch $@
+$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc
+	$(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(GEN_PROTO)
+GEN_PROTO:=
+
+gen_src_dir:=
+
 include $(BUILD_NATIVE_TEST)
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 1bf795b..61d16f8 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -16,21 +16,29 @@
 
 #define LOG_TAG "incidentd"
 
-#include "FdBuffer.h"
-#include "Privacy.h"
-#include "PrivacyBuffer.h"
 #include "Section.h"
 
-#include "io_util.h"
-#include "section_list.h"
+#include <errno.h>
+#include <unistd.h>
+#include <wait.h>
+
+#include <memory>
+#include <mutex>
 
 #include <android/util/protobuf.h>
-#include <private/android_filesystem_config.h>
 #include <binder/IServiceManager.h>
-#include <map>
-#include <mutex>
-#include <wait.h>
-#include <unistd.h>
+#include <log/log_event_list.h>
+#include <log/logprint.h>
+#include <log/log_read.h>
+#include <private/android_filesystem_config.h> // for AID_NOBODY
+#include <private/android_logger.h>
+
+#include "FdBuffer.h"
+#include "frameworks/base/core/proto/android/util/log.proto.h"
+#include "io_util.h"
+#include "Privacy.h"
+#include "PrivacyBuffer.h"
+#include "section_list.h"
 
 using namespace android::util;
 using namespace std;
@@ -41,7 +49,7 @@
 // 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";
+const char INCIDENT_HELPER[] = "/system/bin/incident_helper";
 
 static pid_t
 fork_execute_incident_helper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe)
@@ -521,7 +529,7 @@
             ALOGW("CommandSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno));
             _exit(EXIT_FAILURE);
         }
-        execv(this->mCommand[0], (char *const *) this->mCommand);
+        execvp(this->mCommand[0], (char *const *) this->mCommand);
         int err = errno; // record command error code
         ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno));
         _exit(err); // exit with command error code
@@ -609,3 +617,160 @@
 
     return NO_ERROR;
 }
+
+// ================================================================================
+// initialization only once in Section.cpp.
+map<log_id_t, log_time> LogSection::gLastLogsRetrieved;
+
+LogSection::LogSection(int id, log_id_t logID)
+    :WorkerThreadSection(id),
+     mLogID(logID)
+{
+    name += "logcat ";
+    name += android_log_id_to_name(logID);
+    switch (logID) {
+    case LOG_ID_EVENTS:
+    case LOG_ID_STATS:
+    case LOG_ID_SECURITY:
+        mBinary = true;
+        break;
+    default:
+        mBinary = false;
+    }
+}
+
+LogSection::~LogSection()
+{
+}
+
+static size_t
+trimTail(char const* buf, size_t len)
+{
+    while (len > 0) {
+        char c = buf[len - 1];
+        if (c == '\0' || c == ' ' || c == '\n' || c == '\r' || c == ':') {
+            len--;
+        } else {
+            break;
+        }
+    }
+    return len;
+}
+
+static inline int32_t get4LE(uint8_t const* src) {
+    return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
+}
+
+status_t
+LogSection::BlockingCall(int pipeWriteFd) const
+{
+    status_t err = NO_ERROR;
+    // Open log buffer and getting logs since last retrieved time if any.
+    unique_ptr<logger_list, void (*)(logger_list*)> loggers(
+        gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end() ?
+        android_logger_list_alloc(ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, 0) :
+        android_logger_list_alloc_time(ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK,
+            gLastLogsRetrieved[mLogID], 0),
+        android_logger_list_free);
+
+    if (android_logger_open(loggers.get(), mLogID) == NULL) {
+        ALOGW("LogSection %s: Can't get logger.", this->name.string());
+        return err;
+    }
+
+    log_msg msg;
+    log_time lastTimestamp(0);
+
+    ProtoOutputStream proto;
+    while (true) { // keeps reading until logd buffer is fully read.
+        status_t err = android_logger_list_read(loggers.get(), &msg);
+        // err = 0 - no content, unexpected connection drop or EOF.
+        // err = +ive number - size of retrieved data from logger
+        // err = -ive number, OS supplied error _except_ for -EAGAIN
+        // err = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end of data.
+        if (err <= 0) {
+            if (err != -EAGAIN) {
+                ALOGE("LogSection %s: fails to read a log_msg.\n", this->name.string());
+            }
+            break;
+        }
+        if (mBinary) {
+            // remove the first uint32 which is tag's index in event log tags
+            android_log_context context = create_android_log_parser(msg.msg() + sizeof(uint32_t),
+                    msg.len() - sizeof(uint32_t));;
+            android_log_list_element elem;
+
+            lastTimestamp.tv_sec = msg.entry_v1.sec;
+            lastTimestamp.tv_nsec = msg.entry_v1.nsec;
+
+            // format a BinaryLogEntry
+            long long token = proto.start(LogProto::BINARY_LOGS);
+            proto.write(BinaryLogEntry::SEC, msg.entry_v1.sec);
+            proto.write(BinaryLogEntry::NANOSEC, msg.entry_v1.nsec);
+            proto.write(BinaryLogEntry::UID, (int) msg.entry_v4.uid);
+            proto.write(BinaryLogEntry::PID, msg.entry_v1.pid);
+            proto.write(BinaryLogEntry::TID, msg.entry_v1.tid);
+            proto.write(BinaryLogEntry::TAG_INDEX, get4LE(reinterpret_cast<uint8_t const*>(msg.msg())));
+            do {
+                elem = android_log_read_next(context);
+                long long elemToken = proto.start(BinaryLogEntry::ELEMS);
+                switch (elem.type) {
+                    case EVENT_TYPE_INT:
+                        proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_INT);
+                        proto.write(BinaryLogEntry::Elem::VAL_INT32, (int) elem.data.int32);
+                        break;
+                    case EVENT_TYPE_LONG:
+                        proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LONG);
+                        proto.write(BinaryLogEntry::Elem::VAL_INT64, (long long) elem.data.int64);
+                        break;
+                    case EVENT_TYPE_STRING:
+                        proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_STRING);
+                        proto.write(BinaryLogEntry::Elem::VAL_STRING, elem.data.string, elem.len);
+                        break;
+                    case EVENT_TYPE_FLOAT:
+                        proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_FLOAT);
+                        proto.write(BinaryLogEntry::Elem::VAL_FLOAT, elem.data.float32);
+                        break;
+                    case EVENT_TYPE_LIST:
+                        proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LIST);
+                        break;
+                    case EVENT_TYPE_LIST_STOP:
+                        proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LIST_STOP);
+                        break;
+                    case EVENT_TYPE_UNKNOWN:
+                        proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_UNKNOWN);
+                        break;
+                }
+                proto.end(elemToken);
+            } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete);
+            proto.end(token);
+            if (context) {
+                android_log_destroy(&context);
+            }
+        } else {
+            AndroidLogEntry entry;
+            err = android_log_processLogBuffer(&msg.entry_v1, &entry);
+            if (err != NO_ERROR) {
+                ALOGE("LogSection %s: fails to process to an entry.\n", this->name.string());
+                break;
+            }
+            lastTimestamp.tv_sec = entry.tv_sec;
+            lastTimestamp.tv_nsec = entry.tv_nsec;
+
+            // format a TextLogEntry
+            long long token = proto.start(LogProto::TEXT_LOGS);
+            proto.write(TextLogEntry::SEC, (long long)entry.tv_sec);
+            proto.write(TextLogEntry::NANOSEC, (long long)entry.tv_nsec);
+            proto.write(TextLogEntry::PRIORITY, (int)entry.priority);
+            proto.write(TextLogEntry::UID, entry.uid);
+            proto.write(TextLogEntry::PID, entry.pid);
+            proto.write(TextLogEntry::TID, entry.tid);
+            proto.write(TextLogEntry::TAG, entry.tag, trimTail(entry.tag, entry.tagLen));
+            proto.write(TextLogEntry::LOG, entry.message, trimTail(entry.message, entry.messageLen));
+            proto.end(token);
+        }
+    }
+    gLastLogsRetrieved[mLogID] = lastTimestamp;
+    proto.flush(pipeWriteFd);
+    return err;
+}
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index 64558a6..d440ee9 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -19,7 +19,9 @@
 
 #include "Reporter.h"
 
+#include <map>
 #include <stdarg.h>
+
 #include <utils/String8.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
@@ -122,5 +124,24 @@
     Vector<String16> mArgs;
 };
 
+/**
+ * Section that reads from logd.
+ */
+class LogSection : public WorkerThreadSection
+{
+    // global last log retrieved timestamp for each log_id_t.
+    static map<log_id_t, log_time> gLastLogsRetrieved;
+
+public:
+    LogSection(int id, log_id_t logID);
+    virtual ~LogSection();
+
+    virtual status_t BlockingCall(int pipeWriteFd) const;
+
+private:
+    log_id_t mLogID;
+    bool mBinary;
+};
+
 #endif // SECTIONS_H
 
diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h
index dfd2312..ddc0505 100644
--- a/cmds/incidentd/src/section_list.h
+++ b/cmds/incidentd/src/section_list.h
@@ -17,6 +17,8 @@
 #ifndef SECTION_LIST_H
 #define SECTION_LIST_H
 
+#include <log/log_event_list.h> // include log_id_t enums.
+
 #include "Privacy.h"
 #include "Section.h"
 
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 0c7876c..cbfb896 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -155,7 +155,7 @@
 }
 
 TEST(SectionTest, CommandSectionBadCommand) {
-    CommandSection cs(NOOP_PARSER, "echo", "about", NULL);
+    CommandSection cs(NOOP_PARSER, "echoo", "about", NULL);
     ReportRequestSet requests;
     ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests));
 }
@@ -167,6 +167,26 @@
     ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
 }
 
+TEST(SectionTest, LogSectionBinary) {
+    LogSection ls(1, LOG_ID_EVENTS);
+    ReportRequestSet requests;
+    requests.setMainFd(STDOUT_FILENO);
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, ls.Execute(&requests));
+    string results = GetCapturedStdout();
+    EXPECT_FALSE(results.empty());
+}
+
+TEST(SectionTest, LogSectionSystem) {
+    LogSection ls(1, LOG_ID_SYSTEM);
+    ReportRequestSet requests;
+    requests.setMainFd(STDOUT_FILENO);
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, ls.Execute(&requests));
+    string results = GetCapturedStdout();
+    EXPECT_FALSE(results.empty());
+}
+
 TEST(SectionTest, TestFilterPiiTaggedFields) {
     TemporaryFile tf;
     FileSection fs(NOOP_PARSER, tf.path);
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 77e8efa..2bb7edc 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -16,16 +16,10 @@
 
 package com.android.commands.sm;
 
-import static android.os.storage.StorageManager.PROP_ADOPTABLE_FBE;
-import static android.os.storage.StorageManager.PROP_HAS_ADOPTABLE;
-import static android.os.storage.StorageManager.PROP_VIRTUAL_DISK;
-
-import android.os.IBinder;
 import android.os.IVoldTaskListener;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
 import android.os.storage.DiskInfo;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
@@ -145,15 +139,7 @@
     }
 
     public void runHasAdoptable() {
-        final boolean hasHardware = SystemProperties.getBoolean(PROP_HAS_ADOPTABLE, false)
-                || SystemProperties.getBoolean(PROP_VIRTUAL_DISK, false);
-        final boolean hasSoftware;
-        if (StorageManager.isFileEncryptedNativeOnly()) {
-            hasSoftware = SystemProperties.getBoolean(PROP_ADOPTABLE_FBE, false);
-        } else {
-            hasSoftware = true;
-        }
-        System.out.println(hasHardware && hasSoftware);
+        System.out.println(StorageManager.hasAdoptable());
     }
 
     public void runGetPrimaryStorageUuid() throws RemoteException {
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index f98ee3d..ffe652f 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -19,9 +19,14 @@
     ../../core/java/android/os/IStatsManager.aidl \
     src/stats_log.proto \
     src/statsd_config.proto \
+    src/statsd_internal.proto \
     src/atoms.proto \
+    src/field_util.cpp \
+    src/stats_log_util.cpp \
+    src/dimension.cpp \
     src/anomaly/AnomalyMonitor.cpp \
     src/anomaly/AnomalyTracker.cpp \
+    src/anomaly/DurationAnomalyTracker.cpp \
     src/condition/CombinationConditionTracker.cpp \
     src/condition/condition_util.cpp \
     src/condition/SimpleConditionTracker.cpp \
@@ -29,6 +34,7 @@
     src/config/ConfigKey.cpp \
     src/config/ConfigListener.cpp \
     src/config/ConfigManager.cpp \
+    src/external/StatsPuller.cpp \
     src/external/StatsCompanionServicePuller.cpp \
     src/external/ResourcePowerManagerPuller.cpp \
     src/external/CpuTimePerUidPuller.cpp \
@@ -162,6 +168,7 @@
     tests/indexed_priority_queue_test.cpp \
     tests/LogEntryMatcher_test.cpp \
     tests/LogReader_test.cpp \
+    tests/LogEvent_test.cpp \
     tests/MetricsManager_test.cpp \
     tests/StatsLogProcessor_test.cpp \
     tests/UidMap_test.cpp \
@@ -175,7 +182,11 @@
     tests/metrics/ValueMetricProducer_test.cpp \
     tests/metrics/GaugeMetricProducer_test.cpp \
     tests/guardrail/StatsdStats_test.cpp \
-    tests/metrics/metrics_test_helper.cpp
+    tests/metrics/metrics_test_helper.cpp \
+    tests/statsd_test_util.cpp \
+    tests/e2e/WakelockDuration_e2e_test.cpp \
+    tests/e2e/MetricConditionLink_e2e_test.cpp \
+    tests/e2e/Attribution_e2e_test.cpp
 
 LOCAL_STATIC_LIBRARIES := \
     $(statsd_common_static_libraries) \
@@ -197,4 +208,4 @@
 
 ##############################
 
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 0b6f8f2..288ebe9 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -13,92 +13,108 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #include "HashableDimensionKey.h"
+#include "dimension.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
+android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value) {
+    android::hash_t hash = seed;
+    hash = android::JenkinsHashMix(hash, android::hash_type(value.field()));
+
+    hash = android::JenkinsHashMix(hash, android::hash_type((int)value.value_case()));
+    switch (value.value_case()) {
+        case DimensionsValue::ValueCase::kValueStr:
+            hash = android::JenkinsHashMix(
+                    hash,
+                    static_cast<uint32_t>(std::hash<std::string>()(value.value_str())));
+            break;
+        case DimensionsValue::ValueCase::kValueInt:
+            hash = android::JenkinsHashMix(hash, android::hash_type(value.value_int()));
+            break;
+        case DimensionsValue::ValueCase::kValueLong:
+            hash = android::JenkinsHashMix(
+                    hash, android::hash_type(static_cast<int64_t>(value.value_long())));
+            break;
+        case DimensionsValue::ValueCase::kValueBool:
+            hash = android::JenkinsHashMix(hash, android::hash_type(value.value_bool()));
+            break;
+        case DimensionsValue::ValueCase::kValueFloat: {
+            float floatVal = value.value_float();
+            hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float));
+            break;
+        }
+        case DimensionsValue::ValueCase::kValueTuple: {
+            hash = android::JenkinsHashMix(hash, android::hash_type(
+                value.value_tuple().dimensions_value_size()));
+            for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) {
+                hash = android::JenkinsHashMix(
+                    hash,
+                    hashDimensionsValue(value.value_tuple().dimensions_value(i)));
+            }
+            break;
+        }
+        case DimensionsValue::ValueCase::VALUE_NOT_SET:
+            break;
+    }
+    return JenkinsHashWhiten(hash);
+}
+
+android::hash_t hashDimensionsValue(const DimensionsValue& value) {
+    return hashDimensionsValue(0, value);
+}
+
 using std::string;
 
+
 string HashableDimensionKey::toString() const {
     string flattened;
-    for (const auto& pair : mKeyValuePairs) {
-        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::kValueLong:
-                flattened += std::to_string(pair.value_long());
-                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 += "|";
-    }
+    DimensionsValueToString(getDimensionsValue(), &flattened);
     return flattened;
 }
 
-bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
-    const auto& keyValue2 = that.getKeyValuePairs();
-    if (mKeyValuePairs.size() != keyValue2.size()) {
+bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2) {
+    if (s1.field() != s2.field()) {
         return false;
     }
-
-    for (size_t i = 0; i < keyValue2.size(); i++) {
-        const auto& kv1 = mKeyValuePairs[i];
-        const auto& kv2 = keyValue2[i];
-        if (kv1.key() != kv2.key()) {
-            return false;
-        }
-
-        if (kv1.value_case() != kv2.value_case()) {
-            return false;
-        }
-
-        switch (kv1.value_case()) {
-            case KeyValuePair::ValueCase::kValueStr:
-                if (kv1.value_str() != kv2.value_str()) {
-                    return false;
-                }
-                break;
-            case KeyValuePair::ValueCase::kValueInt:
-                if (kv1.value_int() != kv2.value_int()) {
-                    return false;
-                }
-                break;
-            case KeyValuePair::ValueCase::kValueLong:
-                if (kv1.value_long() != kv2.value_long()) {
-                    return false;
-                }
-                break;
-            case KeyValuePair::ValueCase::kValueBool:
-                if (kv1.value_bool() != kv2.value_bool()) {
-                    return false;
-                }
-                break;
-            case KeyValuePair::ValueCase::kValueFloat: {
-                if (kv1.value_float() != kv2.value_float()) {
-                    return false;
-                }
-                break;
-            }
-            case KeyValuePair::ValueCase::VALUE_NOT_SET:
-                break;
-        }
+    if (s1.value_case() != s1.value_case()) {
+        return false;
     }
-    return true;
+    switch (s1.value_case()) {
+        case DimensionsValue::ValueCase::kValueStr:
+            return (s1.value_str() == s2.value_str());
+        case DimensionsValue::ValueCase::kValueInt:
+            return s1.value_int() == s2.value_int();
+        case DimensionsValue::ValueCase::kValueLong:
+            return s1.value_long() == s2.value_long();
+        case DimensionsValue::ValueCase::kValueBool:
+            return s1.value_bool() == s2.value_bool();
+        case DimensionsValue::ValueCase::kValueFloat:
+            return s1.value_float() == s2.value_float();
+        case DimensionsValue::ValueCase::kValueTuple:
+            {
+                if (s1.value_tuple().dimensions_value_size() !=
+                        s2.value_tuple().dimensions_value_size()) {
+                    return false;
+                }
+                bool allMatched = true;
+                for (int i = 0; allMatched && i < s1.value_tuple().dimensions_value_size(); ++i) {
+                    allMatched &= compareDimensionsValue(s1.value_tuple().dimensions_value(i),
+                                                    s2.value_tuple().dimensions_value(i));
+                }
+                return allMatched;
+            }
+        case DimensionsValue::ValueCase::VALUE_NOT_SET:
+        default:
+            return true;
+    }
+}
+
+bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
+    return compareDimensionsValue(getDimensionsValue(), that.getDimensionsValue());
 };
 
 bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const {
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 85215552..85c317f 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -25,20 +25,20 @@
 
 class HashableDimensionKey {
 public:
-    explicit HashableDimensionKey(const std::vector<KeyValuePair>& keyValuePairs)
-        : mKeyValuePairs(keyValuePairs){};
+    explicit HashableDimensionKey(const DimensionsValue& dimensionsValue)
+        : mDimensionsValue(dimensionsValue){};
 
     HashableDimensionKey(){};
 
     HashableDimensionKey(const HashableDimensionKey& that)
-        : mKeyValuePairs(that.getKeyValuePairs()){};
+        : mDimensionsValue(that.getDimensionsValue()){};
 
     HashableDimensionKey& operator=(const HashableDimensionKey& from) = default;
 
     std::string toString() const;
 
-    inline const std::vector<KeyValuePair>& getKeyValuePairs() const {
-        return mKeyValuePairs;
+    inline const DimensionsValue& getDimensionsValue() const {
+        return mDimensionsValue;
     }
 
     bool operator==(const HashableDimensionKey& that) const;
@@ -50,9 +50,12 @@
     }
 
 private:
-    std::vector<KeyValuePair> mKeyValuePairs;
+    DimensionsValue mDimensionsValue;
 };
 
+android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value);
+android::hash_t hashDimensionsValue(const DimensionsValue& value);
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
@@ -60,42 +63,11 @@
 namespace std {
 
 using android::os::statsd::HashableDimensionKey;
-using android::os::statsd::KeyValuePair;
 
 template <>
 struct hash<HashableDimensionKey> {
     std::size_t operator()(const HashableDimensionKey& key) const {
-        android::hash_t hash = 0;
-        for (const auto& pair : key.getKeyValuePairs()) {
-            hash = android::JenkinsHashMix(hash, android::hash_type(pair.key()));
-            hash = android::JenkinsHashMix(
-                    hash, android::hash_type(static_cast<int32_t>(pair.value_case())));
-            switch (pair.value_case()) {
-                case KeyValuePair::ValueCase::kValueStr:
-                    hash = android::JenkinsHashMix(
-                            hash,
-                            static_cast<uint32_t>(std::hash<std::string>()(pair.value_str())));
-                    break;
-                case KeyValuePair::ValueCase::kValueInt:
-                    hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_int()));
-                    break;
-                case KeyValuePair::ValueCase::kValueLong:
-                    hash = android::JenkinsHashMix(
-                            hash, android::hash_type(static_cast<int64_t>(pair.value_long())));
-                    break;
-                case KeyValuePair::ValueCase::kValueBool:
-                    hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_bool()));
-                    break;
-                case KeyValuePair::ValueCase::kValueFloat: {
-                    float floatVal = pair.value_float();
-                    hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float));
-                    break;
-                }
-                case KeyValuePair::ValueCase::VALUE_NOT_SET:
-                    break;
-            }
-        }
-        return hash;
+        return hashDimensionsValue(key.getDimensionsValue());
     }
 };
 
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 0c078d5..a9e0f23 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -54,7 +54,7 @@
 const int FIELD_ID_REPORTS = 2;
 // for ConfigKey
 const int FIELD_ID_UID = 1;
-const int FIELD_ID_NAME = 2;
+const int FIELD_ID_ID = 2;
 // for ConfigMetricsReport
 const int FIELD_ID_METRICS = 1;
 const int FIELD_ID_UID_MAP = 2;
@@ -63,11 +63,12 @@
 
 StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
                                      const sp<AnomalyMonitor>& anomalyMonitor,
+                                     const long timeBaseSec,
                                      const std::function<void(const ConfigKey&)>& sendBroadcast)
     : mUidMap(uidMap),
       mAnomalyMonitor(anomalyMonitor),
       mSendBroadcast(sendBroadcast),
-      mTimeBaseSec(time(nullptr)) {
+      mTimeBaseSec(timeBaseSec) {
     // On each initialization of StatsLogProcessor, check stats-data directory to see if there is
     // any left over data to be read.
     StorageManager::sendBroadcast(STATS_DATA_DIR, mSendBroadcast);
@@ -81,6 +82,7 @@
 void StatsLogProcessor::onAnomalyAlarmFired(
         const uint64_t timestampNs,
         unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
     for (const auto& itr : mMetricsManagers) {
         itr.second->onAnomalyAlarmFired(timestampNs, anomalySet);
     }
@@ -88,13 +90,14 @@
 
 // TODO: what if statsd service restarts? How do we know what logs are already processed before?
 void StatsLogProcessor::OnLogEvent(const LogEvent& msg) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+
     StatsdStats::getInstance().noteAtomLogged(msg.GetTagId(), msg.GetTimestampNs() / NS_PER_SEC);
     // pass the event to metrics managers.
     for (auto& pair : mMetricsManagers) {
         pair.second->onLogEvent(msg);
-        flushIfNecessary(msg.GetTimestampNs(), pair.first, *(pair.second));
+        flushIfNecessaryLocked(msg.GetTimestampNs(), pair.first, *(pair.second));
     }
-
     // Hard-coded logic to update the isolated uid's in the uid-map.
     // The field numbers need to be currently updated by hand with atoms.proto
     if (msg.GetTagId() == android::util::ISOLATED_UID_CHANGED) {
@@ -113,10 +116,9 @@
 }
 
 void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
     ALOGD("Updated configuration for key %s", key.ToString().c_str());
-
     sp<MetricsManager> newMetricsManager = new MetricsManager(key, config, mTimeBaseSec, mUidMap);
-
     auto it = mMetricsManagers.find(key);
     if (it == mMetricsManagers.end() && mMetricsManagers.size() > StatsdStats::kMaxConfigCount) {
         ALOGE("Can't accept more configs!");
@@ -126,7 +128,7 @@
     if (newMetricsManager->isConfigValid()) {
         mUidMap->OnConfigUpdated(key);
         newMetricsManager->setAnomalyMonitor(mAnomalyMonitor);
-        if (config.log_source().package().size() > 0) {
+        if (newMetricsManager->shouldAddUidMapListener()) {
             // We have to add listener after the MetricsManager is constructed because it's
             // not safe to create wp or sp from this pointer inside its constructor.
             mUidMap->addListener(newMetricsManager.get());
@@ -141,6 +143,7 @@
 }
 
 size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) const {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
     auto it = mMetricsManagers.find(key);
     if (it == mMetricsManagers.end()) {
         ALOGW("Config source %s does not exist", key.ToString().c_str());
@@ -149,7 +152,23 @@
     return it->second->byteSize();
 }
 
+void StatsLogProcessor::onDumpReport(const ConfigKey& key, const uint64_t& dumpTimeStampNs,
+                                     ConfigMetricsReportList* report) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    auto it = mMetricsManagers.find(key);
+    if (it == mMetricsManagers.end()) {
+        ALOGW("Config source %s does not exist", key.ToString().c_str());
+        return;
+    }
+    report->mutable_config_key()->set_uid(key.GetUid());
+    report->mutable_config_key()->set_id(key.GetId());
+    ConfigMetricsReport* configMetricsReport = report->add_reports();
+    it->second->onDumpReport(dumpTimeStampNs, configMetricsReport);
+    // TODO: dump uid mapping.
+}
+
 void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector<uint8_t>* outData) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
     auto it = mMetricsManagers.find(key);
     if (it == mMetricsManagers.end()) {
         ALOGW("Config source %s does not exist", key.ToString().c_str());
@@ -158,16 +177,14 @@
 
     // This allows another broadcast to be sent within the rate-limit period if we get close to
     // filling the buffer again soon.
-    mBroadcastTimesMutex.lock();
     mLastBroadcastTimes.erase(key);
-    mBroadcastTimesMutex.unlock();
 
     ProtoOutputStream proto;
 
     // Start of ConfigKey.
     long long configKeyToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY);
     proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid());
-    proto.write(FIELD_TYPE_STRING | FIELD_ID_NAME, key.GetName());
+    proto.write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)key.GetId());
     proto.end(configKeyToken);
     // End of ConfigKey.
 
@@ -209,6 +226,7 @@
 }
 
 void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
     auto it = mMetricsManagers.find(key);
     if (it != mMetricsManagers.end()) {
         mMetricsManagers.erase(it);
@@ -216,14 +234,11 @@
     }
     StatsdStats::getInstance().noteConfigRemoved(key);
 
-    std::lock_guard<std::mutex> lock(mBroadcastTimesMutex);
     mLastBroadcastTimes.erase(key);
 }
 
-void StatsLogProcessor::flushIfNecessary(uint64_t timestampNs, const ConfigKey& key,
-                                         MetricsManager& metricsManager) {
-    std::lock_guard<std::mutex> lock(mBroadcastTimesMutex);
-
+void StatsLogProcessor::flushIfNecessaryLocked(
+    uint64_t timestampNs, const ConfigKey& key, MetricsManager& metricsManager) {
     auto lastCheckTime = mLastByteSizeTimes.find(key);
     if (lastCheckTime != mLastByteSizeTimes.end()) {
         if (timestampNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) {
@@ -259,13 +274,14 @@
 
 void StatsLogProcessor::WriteDataToDisk() {
     mkdir(STATS_DATA_DIR, S_IRWXU);
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
     for (auto& pair : mMetricsManagers) {
         const ConfigKey& key = pair.first;
         vector<uint8_t> data;
         onDumpReport(key, &data);
         // TODO: Add a guardrail to prevent accumulation of file on disk.
-        string file_name = StringPrintf("%s/%d-%s-%ld", STATS_DATA_DIR, key.GetUid(),
-                                        key.GetName().c_str(), time(nullptr));
+        string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_DATA_DIR, key.GetUid(),
+                                        (long long)key.GetId(), time(nullptr));
         StorageManager::writeFile(file_name.c_str(), &data[0], data.size());
     }
 }
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 1e5c426..adc9161 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -13,9 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef STATS_LOG_PROCESSOR_H
-#define STATS_LOG_PROCESSOR_H
 
+#pragma once
+
+#include <gtest/gtest_prod.h>
 #include "config/ConfigListener.h"
 #include "logd/LogReader.h"
 #include "metrics/MetricsManager.h"
@@ -33,6 +34,7 @@
 class StatsLogProcessor : public ConfigListener {
 public:
     StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor,
+                      const long timeBaseSec,
                       const std::function<void(const ConfigKey&)>& sendBroadcast);
     virtual ~StatsLogProcessor();
 
@@ -44,6 +46,7 @@
     size_t GetMetricsSize(const ConfigKey& key) const;
 
     void onDumpReport(const ConfigKey& key, vector<uint8_t>* outData);
+    void onDumpReport(const ConfigKey& key, const uint64_t& dumpTimeStampNs, ConfigMetricsReportList* report);
 
     /* Tells MetricsManager that the alarms in anomalySet have fired. Modifies anomalySet. */
     void onAnomalyAlarmFired(
@@ -53,8 +56,12 @@
     /* Flushes data to disk. Data on memory will be gone after written to disk. */
     void WriteDataToDisk();
 
+    inline sp<UidMap> getUidMap() {
+        return mUidMap;
+    }
+
 private:
-    mutable mutex mBroadcastTimesMutex;
+    mutable mutex mMetricsMutex;
 
     std::unordered_map<ConfigKey, sp<MetricsManager>> mMetricsManagers;
 
@@ -69,8 +76,8 @@
 
     /* Check if we should send a broadcast if approaching memory limits and if we're over, we
      * actually delete the data. */
-    void flushIfNecessary(uint64_t timestampNs, const ConfigKey& key,
-                          MetricsManager& metricsManager);
+    void flushIfNecessaryLocked(uint64_t timestampNs, const ConfigKey& key,
+                                MetricsManager& metricsManager);
 
     // Function used to send a broadcast so that receiver for the config key can call getData
     // to retrieve the stored data.
@@ -81,10 +88,13 @@
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
     FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
+    FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
+    FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
+    FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
+    FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
+
 };
 
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-
-#endif  // STATS_LOG_PROCESSOR_H
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index dab3880..45f1ea1 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -75,7 +75,7 @@
 {
     mUidMap = new UidMap();
     mConfigManager = new ConfigManager();
-    mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, [this](const ConfigKey& key) {
+    mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, time(nullptr), [this](const ConfigKey& key) {
         sp<IStatsCompanionService> sc = getStatsCompanionService();
         auto receiver = mConfigManager->GetConfigReceiver(key);
         if (sc == nullptr) {
@@ -335,7 +335,7 @@
         print_cmd_help(out);
         return UNKNOWN_ERROR;
     }
-    auto receiver = mConfigManager->GetConfigReceiver(ConfigKey(uid, name));
+    auto receiver = mConfigManager->GetConfigReceiver(ConfigKey(uid, StrToInt64(name)));
     sp<IStatsCompanionService> sc = getStatsCompanionService();
     if (sc != nullptr) {
         sc->sendBroadcast(String16(receiver.first.c_str()), String16(receiver.second.c_str()));
@@ -404,13 +404,13 @@
                 }
 
                 // Add / update the config.
-                mConfigManager->UpdateConfig(ConfigKey(uid, name), config);
+                mConfigManager->UpdateConfig(ConfigKey(uid, StrToInt64(name)), config);
             } else {
                 if (argCount == 2) {
                     cmd_remove_all_configs(out);
                 } else {
                     // Remove the config.
-                    mConfigManager->RemoveConfig(ConfigKey(uid, name));
+                    mConfigManager->RemoveConfig(ConfigKey(uid, StrToInt64(name)));
                 }
             }
 
@@ -459,7 +459,7 @@
         }
         if (good) {
             vector<uint8_t> data;
-            mProcessor->onDumpReport(ConfigKey(uid, name), &data);
+            mProcessor->onDumpReport(ConfigKey(uid, StrToInt64(name)), &data);
             // TODO: print the returned StatsLogReport to file instead of printing to logcat.
             if (proto) {
                 for (size_t i = 0; i < data.size(); i ++) {
@@ -699,12 +699,11 @@
     mProcessor->OnLogEvent(event);
 }
 
-Status StatsService::getData(const String16& key, vector<uint8_t>* output) {
+Status StatsService::getData(int64_t key, vector<uint8_t>* output) {
     IPCThreadState* ipc = IPCThreadState::self();
     VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
     if (checkCallingPermission(String16(kPermissionDump))) {
-        string keyStr = string(String8(key).string());
-        ConfigKey configKey(ipc->getCallingUid(), keyStr);
+        ConfigKey configKey(ipc->getCallingUid(), key);
         mProcessor->onDumpReport(configKey, output);
         return Status::ok();
     } else {
@@ -724,16 +723,18 @@
     }
 }
 
-Status StatsService::addConfiguration(const String16& key,
+Status StatsService::addConfiguration(int64_t key,
                                       const vector <uint8_t>& config,
                                       const String16& package, const String16& cls,
                                       bool* success) {
     IPCThreadState* ipc = IPCThreadState::self();
     if (checkCallingPermission(String16(kPermissionDump))) {
-        string keyString = string(String8(key).string());
-        ConfigKey configKey(ipc->getCallingUid(), keyString);
+        ConfigKey configKey(ipc->getCallingUid(), key);
         StatsdConfig cfg;
-        cfg.ParseFromArray(&config[0], config.size());
+        if (!cfg.ParseFromArray(&config[0], config.size())) {
+            *success = false;
+            return Status::ok();
+        }
         mConfigManager->UpdateConfig(configKey, cfg);
         mConfigManager->SetConfigReceiver(configKey, string(String8(package).string()),
                                           string(String8(cls).string()));
@@ -745,11 +746,10 @@
     }
 }
 
-Status StatsService::removeConfiguration(const String16& key, bool* success) {
+Status StatsService::removeConfiguration(int64_t key, bool* success) {
     IPCThreadState* ipc = IPCThreadState::self();
     if (checkCallingPermission(String16(kPermissionDump))) {
-        string keyStr = string(String8(key).string());
-        mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), keyStr));
+        mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), key));
         *success = true;
         return Status::ok();
     } else {
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 08fcdac..c0424f3 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -77,25 +77,27 @@
     /**
      * Binder call for clients to request data for this configuration key.
      */
-    virtual Status getData(const String16& key, vector<uint8_t>* output) override;
+    virtual Status getData(int64_t key, vector<uint8_t>* output) override;
+
 
     /**
      * Binder call for clients to get metadata across all configs in statsd.
      */
     virtual Status getMetadata(vector<uint8_t>* output) override;
 
+
     /**
      * Binder call to let clients send a configuration and indicate they're interested when they
      * should requestData for this configuration.
      */
-    virtual Status addConfiguration(const String16& key, const vector <uint8_t>& config,
-                                   const String16& package, const String16& cls, bool* success)
+    virtual Status addConfiguration(int64_t key, const vector <uint8_t>& config,
+                                    const String16& package, const String16& cls, bool* success)
     override;
 
     /**
      * Binder call to allow clients to remove the specified configuration.
      */
-    virtual Status removeConfiguration(const String16& key, bool* success) override;
+    virtual Status removeConfiguration(int64_t key, bool* success) override;
 
     // TODO: public for testing since statsd doesn't run when system starts. Change to private
     // later.
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 162a34b..f10b2cf 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -30,17 +30,15 @@
 namespace os {
 namespace statsd {
 
-// TODO: Separate DurationAnomalyTracker as a separate subclass and let each MetricProducer
-//       decide and let which one it wants.
 // TODO: Get rid of bucketNumbers, and return to the original circular array method.
 AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
     : mAlert(alert),
       mConfigKey(configKey),
-      mNumOfPastBuckets(mAlert.number_of_buckets() - 1) {
+      mNumOfPastBuckets(mAlert.num_buckets() - 1) {
     VLOG("AnomalyTracker() called");
-    if (mAlert.number_of_buckets() <= 0) {
+    if (mAlert.num_buckets() <= 0) {
         ALOGE("Cannot create AnomalyTracker with %lld buckets",
-              (long long)mAlert.number_of_buckets());
+              (long long)mAlert.num_buckets());
         return;
     }
     if (!mAlert.has_trigger_if_sum_gt()) {
@@ -52,7 +50,6 @@
 
 AnomalyTracker::~AnomalyTracker() {
     VLOG("~AnomalyTracker() called");
-    stopAllAlarms();
 }
 
 void AnomalyTracker::resetStorage() {
@@ -61,8 +58,6 @@
     // Excludes the current bucket.
     mPastBuckets.resize(mNumOfPastBuckets);
     mSumOverPastBuckets.clear();
-
-    if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!");
 }
 
 size_t AnomalyTracker::index(int64_t bucketNum) const {
@@ -167,81 +162,43 @@
     return 0;
 }
 
-bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
-                                   const DimToValMap& currentBucket) {
-    if (currentBucketNum > mMostRecentBucketNum + 1) {
-        addPastBucket(nullptr, currentBucketNum - 1);
-    }
-    for (auto itr = currentBucket.begin(); itr != currentBucket.end(); itr++) {
-        if (itr->second + getSumOverPastBuckets(itr->first) > mAlert.trigger_if_sum_gt()) {
-            return true;
-        }
-    }
-    // In theory, we also need to check the dimsions not in the current bucket. In single-thread
-    // mode, usually we could avoid the following loops.
-    for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
-        if (itr->second > mAlert.trigger_if_sum_gt()) {
-            return true;
-        }
-    }
-    return false;
-}
-
 bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
                                    const int64_t& currentBucketValue) {
     if (currentBucketNum > mMostRecentBucketNum + 1) {
+        // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this.
         addPastBucket(key, 0, currentBucketNum - 1);
     }
     return mAlert.has_trigger_if_sum_gt()
             && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
 }
 
-void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) {
-    // TODO: This should also take in the const HashableDimensionKey& key, to pass
-    //       more details to incidentd and to make mRefractoryPeriodEndsSec key-specific.
+void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key) {
     // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
-    if (isInRefractoryPeriod(timestampNs)) {
+    if (isInRefractoryPeriod(timestampNs, key)) {
         VLOG("Skipping anomaly declaration since within refractory period");
         return;
     }
-    // TODO(guardrail): Consider guarding against too short refractory periods.
-    mLastAlarmTimestampNs = timestampNs;
-
+    mRefractoryPeriodEndsSec[key] = (timestampNs / NS_PER_SEC) + mAlert.refractory_period_secs();
 
     // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
     // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
 
-    if (mAlert.has_incidentd_details()) {
-        if (mAlert.has_name()) {
-            ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
-                  mAlert.name().c_str());
+    if (!mSubscriptions.empty()) {
+        if (mAlert.has_id()) {
+            ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id());
+            informSubscribers(key);
         } else {
-            // TODO: Can construct a name based on the criteria (and/or relay the criteria).
-            ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
+            ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers.");
         }
-        informIncidentd();
     } else {
-        ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
+        ALOGI("An anomaly has occurred! (But no subscriber for that alert.)");
     }
 
-    StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.name());
+    StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
 
+    // TODO: This should also take in the const HashableDimensionKey& key?
     android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
-                               mConfigKey.GetName().c_str(), mAlert.name().c_str());
-}
-
-void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
-                                                  const uint64_t& timestampNs) {
-    auto itr = mAlarms.find(dimensionKey);
-    if (itr == mAlarms.end()) {
-        return;
-    }
-
-    if (itr->second != nullptr &&
-        static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) {
-        declareAnomaly(timestampNs);
-        stopAlarm(dimensionKey);
-    }
+                               mConfigKey.GetId(), mAlert.id());
 }
 
 void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
@@ -249,105 +206,65 @@
                                              const HashableDimensionKey& key,
                                              const int64_t& currentBucketValue) {
     if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
-        declareAnomaly(timestampNs);
+        declareAnomaly(timestampNs, key);
     }
 }
 
-void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
-                                             const int64_t& currBucketNum,
-                                             const DimToValMap& currentBucket) {
-    if (detectAnomaly(currBucketNum, currentBucket)) {
-        declareAnomaly(timestampNs);
-    }
-}
-
-void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
-                                const uint64_t& timestampNs) {
-    uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
-    if (isInRefractoryPeriod(timestampNs)) {
-        VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period");
-        return;
-    }
-
-    sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec};
-    mAlarms.insert({dimensionKey, alarm});
-    if (mAnomalyMonitor != nullptr) {
-        mAnomalyMonitor->add(alarm);
-    }
-}
-
-void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
-    auto itr = mAlarms.find(dimensionKey);
-    if (itr != mAlarms.end()) {
-        mAlarms.erase(dimensionKey);
-        if (mAnomalyMonitor != nullptr) {
-            mAnomalyMonitor->remove(itr->second);
+bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs,
+                                          const HashableDimensionKey& key) {
+    const auto& it = mRefractoryPeriodEndsSec.find(key);
+    if (it != mRefractoryPeriodEndsSec.end()) {
+        if ((timestampNs / NS_PER_SEC) <= it->second) {
+            return true;
+        } else {
+            mRefractoryPeriodEndsSec.erase(key);
         }
     }
+    return false;
 }
 
-void AnomalyTracker::stopAllAlarms() {
-    std::set<HashableDimensionKey> keys;
-    for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
-        keys.insert(itr->first);
+void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) {
+    VLOG("informSubscribers called.");
+    if (mSubscriptions.empty()) {
+        ALOGE("Attempt to call with no subscribers.");
+        return;
     }
-    for (auto key : keys) {
-        stopAlarm(key);
-    }
-}
 
-bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) {
-    return mLastAlarmTimestampNs >= 0 &&
-            timestampNs - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC;
-}
-
-void AnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
-        unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
-
-    if (firedAlarms.empty() || mAlarms.empty()) return;
-    // Find the intersection of firedAlarms and mAlarms.
-    // The for loop is inefficient, since it loops over all keys, but that's okay since it is very
-    // seldomly called. The alternative would be having AnomalyAlarms store information about the
-    // AnomalyTracker and key, but that's a lot of data overhead to speed up something that is
-    // rarely ever called.
-    unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
-    for (const auto& kv : mAlarms) {
-        if (firedAlarms.count(kv.second) > 0) {
-            matchedAlarms.insert({kv.first, kv.second});
+    std::set<int> incidentdSections;
+    for (const Subscription& subscription : mSubscriptions) {
+        switch (subscription.subscriber_information_case()) {
+            case Subscription::SubscriberInformationCase::kIncidentdDetails:
+                for (int i = 0; i < subscription.incidentd_details().section_size(); i++) {
+                    incidentdSections.insert(subscription.incidentd_details().section(i));
+                }
+                break;
+            case Subscription::SubscriberInformationCase::kPerfettoDetails:
+                ALOGW("Perfetto reports not implemented.");
+                break;
+            default:
+                break;
         }
     }
-
-    // Now declare each of these alarms to have fired.
-    for (const auto& kv : matchedAlarms) {
-        declareAnomaly(timestampNs /* TODO: , kv.first */);
-        mAlarms.erase(kv.first);
-        firedAlarms.erase(kv.second);  // No one else can also own it, so we're done with it.
+    if (!incidentdSections.empty()) {
+        sp<IIncidentManager> service = interface_cast<IIncidentManager>(
+                defaultServiceManager()->getService(android::String16("incident")));
+        if (service != NULL) {
+            IncidentReportArgs incidentReport;
+            for (const auto section : incidentdSections) {
+                incidentReport.addSection(section);
+            }
+            int64_t alertId = mAlert.id();
+            std::vector<uint8_t> header;
+            uint8_t* src = static_cast<uint8_t*>(static_cast<void*>(&alertId));
+            header.insert(header.end(), src, src + sizeof(int64_t));
+            incidentReport.addHeader(header);
+            service->reportIncident(incidentReport);
+        } else {
+            ALOGW("Couldn't get the incident service.");
+        }
     }
 }
 
-void AnomalyTracker::informIncidentd() {
-    VLOG("informIncidentd called.");
-    if (!mAlert.has_incidentd_details()) {
-        ALOGE("Attempted to call incidentd without any incidentd_details.");
-        return;
-    }
-    sp<IIncidentManager> service = interface_cast<IIncidentManager>(
-            defaultServiceManager()->getService(android::String16("incident")));
-    if (service == NULL) {
-        ALOGW("Couldn't get the incident service.");
-        return;
-    }
-
-    IncidentReportArgs incidentReport;
-    const Alert::IncidentdDetails& details = mAlert.incidentd_details();
-    for (int i = 0; i < details.section_size(); i++) {
-        incidentReport.addSection(details.section(i));
-    }
-    // TODO: Pass in mAlert.name() into the addHeader?
-
-    service->reportIncident(incidentReport);
-}
-
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index 874add2..472c02c 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -40,6 +40,11 @@
 
     virtual ~AnomalyTracker();
 
+    // Add subscriptions that depend on this alert.
+    void addSubscription(const Subscription& subscription) {
+        mSubscriptions.push_back(subscription);
+    }
+
     // Adds a bucket.
     // Bucket index starts from 0.
     void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum);
@@ -47,37 +52,22 @@
                        const int64_t& bucketNum);
 
     // Returns true if detected anomaly for the existing buckets on one or more dimension keys.
-    bool detectAnomaly(const int64_t& currBucketNum, const DimToValMap& currentBucket);
     bool detectAnomaly(const int64_t& currBucketNum, const HashableDimensionKey& key,
                        const int64_t& currentBucketValue);
 
     // Informs incidentd about the detected alert.
-    void declareAnomaly(const uint64_t& timestampNs);
+    void declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key);
 
     // Detects the alert and informs the incidentd when applicable.
     void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
-                                 const DimToValMap& currentBucket);
-    void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
                                  const HashableDimensionKey& key,
                                  const int64_t& currentBucketValue);
 
-    // Starts the alarm at the given timestamp.
-    void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime);
-    // Stops the alarm.
-    void stopAlarm(const HashableDimensionKey& dimensionKey);
-
-    // Stop all the alarms owned by this tracker.
-    void stopAllAlarms();
-
-    // Init the anmaly monitor which is shared across anomaly trackers.
-    inline void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
-        mAnomalyMonitor = anomalyMonitor;
+    // Init the AnomalyMonitor which is shared across anomaly trackers.
+    virtual void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
+        return; // Base AnomalyTracker class has no need for the AnomalyMonitor.
     }
 
-    // Declares the anomaly when the alarm expired given the current timestamp.
-    void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
-                                      const uint64_t& timestampNs);
-
     // Helper function to return the sum value of past buckets at given dimension.
     int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const;
 
@@ -89,9 +79,11 @@
         return mAlert.trigger_if_sum_gt();
     }
 
-    // Helper function to return the last alarm timestamp.
-    inline int64_t getLastAlarmTimestampNs() const {
-        return mLastAlarmTimestampNs;
+    // Returns the refractory period timestamp (in seconds) for the given key.
+    // If there is no stored refractory period ending timestamp, returns 0.
+    uint32_t getRefractoryPeriodEndsSec(const HashableDimensionKey& key) const {
+        const auto& it = mRefractoryPeriodEndsSec.find(key);
+        return it != mRefractoryPeriodEndsSec.end() ? it->second : 0;
     }
 
     inline int getNumOfPastBuckets() const {
@@ -100,18 +92,18 @@
 
     // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker,
     // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor.
-    // TODO: This will actually be called from a different thread, so make it thread-safe!
-    // TODO: Consider having AnomalyMonitor have a reference to each relevant MetricProducer
-    //       instead of calling it from a chain starting at StatsLogProcessor.
-    void informAlarmsFired(const uint64_t& timestampNs,
-            unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms);
+    virtual void informAlarmsFired(const uint64_t& timestampNs,
+            unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
+        return; // The base AnomalyTracker class doesn't have alarms.
+    }
 
 protected:
-    void flushPastBuckets(const int64_t& currBucketNum);
-
     // statsd_config.proto Alert message that defines this tracker.
     const Alert mAlert;
 
+    // The subscriptions that depend on this alert.
+    std::vector<Subscription> mSubscriptions;
+
     // A reference to the Alert's config key.
     const ConfigKey& mConfigKey;
 
@@ -119,14 +111,7 @@
     // for the anomaly detection (since the current bucket is not in the past).
     int mNumOfPastBuckets;
 
-    // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
-    // are still active.
-    std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms;
-
-    // Anomaly alarm monitor.
-    sp<AnomalyMonitor> mAnomalyMonitor;
-
-    // The exisiting bucket list.
+    // The existing bucket list.
     std::vector<shared_ptr<DimToValMap>> mPastBuckets;
 
     // Sum over all existing buckets cached in mPastBuckets.
@@ -135,8 +120,13 @@
     // The bucket number of the last added bucket.
     int64_t mMostRecentBucketNum = -1;
 
-    // The timestamp when the last anomaly was declared.
-    int64_t mLastAlarmTimestampNs = -1;
+    // Map from each dimension to the timestamp that its refractory period (if this anomaly was
+    // declared for that dimension) ends, in seconds. Only anomalies that occur after this period
+    // ends will be declared.
+    // Entries may be, but are not guaranteed to be, removed after the period is finished.
+    unordered_map<HashableDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
+
+    void flushPastBuckets(const int64_t& currBucketNum);
 
     // Add the information in the given bucket to mSumOverPastBuckets.
     void addBucketToSum(const shared_ptr<DimToValMap>& bucket);
@@ -145,26 +135,21 @@
     // and remove any items with value 0.
     void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
 
-    bool isInRefractoryPeriod(const uint64_t& timestampNs);
+    bool isInRefractoryPeriod(const uint64_t& timestampNs, const HashableDimensionKey& key);
 
     // Calculates the corresponding bucket index within the circular array.
     size_t index(int64_t bucketNum) const;
 
     // Resets all bucket data. For use when all the data gets stale.
-    void resetStorage();
+    virtual void resetStorage();
 
-    // Informs the incident service that an anomaly has occurred.
-    void informIncidentd();
+    // Informs the subscribers that an anomaly has occurred.
+    void informSubscribers(const HashableDimensionKey& key);
 
     FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
     FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
     FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
-    FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
-    FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
-    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
-    FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
-    FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
-    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+    FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
new file mode 100644
index 0000000..7576a38
--- /dev/null
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.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.
+ */
+
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "DurationAnomalyTracker.h"
+#include "guardrail/StatsdStats.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+DurationAnomalyTracker::DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey)
+    : AnomalyTracker(alert, configKey) {
+}
+
+DurationAnomalyTracker::~DurationAnomalyTracker() {
+    stopAllAlarms();
+}
+
+void DurationAnomalyTracker::resetStorage() {
+    AnomalyTracker::resetStorage();
+    if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!");
+}
+
+void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+                                                  const uint64_t& timestampNs) {
+    auto itr = mAlarms.find(dimensionKey);
+    if (itr == mAlarms.end()) {
+        return;
+    }
+
+    if (itr->second != nullptr &&
+        static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) {
+        declareAnomaly(timestampNs, dimensionKey);
+        stopAlarm(dimensionKey);
+    }
+}
+
+void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
+                                const uint64_t& timestampNs) {
+
+    uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
+    if (isInRefractoryPeriod(timestampNs, dimensionKey)) {
+        VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period");
+        return;
+    }
+    sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec};
+    mAlarms.insert({dimensionKey, alarm});
+    if (mAnomalyMonitor != nullptr) {
+        mAnomalyMonitor->add(alarm);
+    }
+}
+
+void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
+    auto itr = mAlarms.find(dimensionKey);
+    if (itr != mAlarms.end()) {
+        mAlarms.erase(dimensionKey);
+        if (mAnomalyMonitor != nullptr) {
+            mAnomalyMonitor->remove(itr->second);
+        }
+    }
+}
+
+void DurationAnomalyTracker::stopAllAlarms() {
+    std::set<HashableDimensionKey> keys;
+    for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
+        keys.insert(itr->first);
+    }
+    for (auto key : keys) {
+        stopAlarm(key);
+    }
+}
+
+void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
+        unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
+
+    if (firedAlarms.empty() || mAlarms.empty()) return;
+    // Find the intersection of firedAlarms and mAlarms.
+    // The for loop is inefficient, since it loops over all keys, but that's okay since it is very
+    // seldomly called. The alternative would be having AnomalyAlarms store information about the
+    // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is
+    // rarely ever called.
+    unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
+    for (const auto& kv : mAlarms) {
+        if (firedAlarms.count(kv.second) > 0) {
+            matchedAlarms.insert({kv.first, kv.second});
+        }
+    }
+
+    // Now declare each of these alarms to have fired.
+    for (const auto& kv : matchedAlarms) {
+        declareAnomaly(timestampNs, kv.first);
+        mAlarms.erase(kv.first);
+        firedAlarms.erase(kv.second);  // No one else can also own it, so we're done with it.
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
new file mode 100644
index 0000000..de7093d
--- /dev/null
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.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.
+ */
+
+#pragma once
+
+#include "AnomalyMonitor.h"
+#include "AnomalyTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::unordered_map;
+
+class DurationAnomalyTracker : public virtual AnomalyTracker {
+public:
+    DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey);
+
+    virtual ~DurationAnomalyTracker();
+
+    // Starts the alarm at the given timestamp.
+    void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime);
+
+    // Stops the alarm.
+    void stopAlarm(const HashableDimensionKey& dimensionKey);
+
+    // Stop all the alarms owned by this tracker.
+    void stopAllAlarms();
+
+    // Init the AnomalyMonitor which is shared across anomaly trackers.
+    void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) override {
+        mAnomalyMonitor = anomalyMonitor;
+    }
+
+    // Declares the anomaly when the alarm expired given the current timestamp.
+    void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+                                      const uint64_t& timestampNs);
+
+    // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
+    // and removes it from firedAlarms.
+    // TODO: This will actually be called from a different thread, so make it thread-safe!
+    //          This means that almost every function in DurationAnomalyTracker needs to be locked.
+    //          But this should be done at the level of StatsLogProcessor, which needs to lock
+    //          mMetricsMangers anyway.
+    void informAlarmsFired(const uint64_t& timestampNs,
+            unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override;
+
+protected:
+    // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
+    // are still active.
+    std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms;
+
+    // Anomaly alarm monitor.
+    sp<AnomalyMonitor> mAnomalyMonitor;
+
+    // Resets all bucket data. For use when all the data gets stale.
+    void resetStorage() override;
+
+    FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
+    FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
+    FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1c6d9b0..7f77ef7 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -81,47 +81,44 @@
         DropboxErrorChanged dropbox_error_changed = 45;
         AnomalyDetected anomaly_detected = 46;
         AppHook app_hook = 47;
+        AppStartChanged app_start_changed = 48;
+        AppStartCancelChanged app_start_cancel_changed = 49;
+        AppStartFullyDrawnChanged app_start_fully_drawn_changed = 50;
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 
-    // Pulled events will start at field 1000.
+    // Pulled events will start at field 10000.
     oneof pulled {
-        WifiBytesTransfer wifi_bytes_transfer = 1000;
-        WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 1001;
-        MobileBytesTransfer mobile_bytes_transfer = 1002;
-        MobileBytesTransferByFgBg mobile_bytes_transfer_by_fg_bg = 1003;
-        KernelWakelock kernel_wakelock = 1004;
-        PlatformSleepState platform_sleep_state = 1005;
-        SleepStateVoter sleep_state_voter = 1006;
-        SubsystemSleepState subsystem_sleep_state = 1007;
-        CpuTimePerFreq cpu_time_per_freq = 1008;
-        CpuTimePerUid cpu_time_per_uid = 1009;
-        CpuTimePerUidFreq cpu_time_per_uid_freq = 1010;
-        WifiActivityEnergyInfo wifi_activity_energy_info = 1011;
-        ModemActivityInfo modem_activity_info = 1012;
-        AttributionChainDummyAtom attribution_chain_dummy_atom = 10000;
+        WifiBytesTransfer wifi_bytes_transfer = 10000;
+        WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
+        MobileBytesTransfer mobile_bytes_transfer = 10002;
+        MobileBytesTransferByFgBg mobile_bytes_transfer_by_fg_bg = 10003;
+        KernelWakelock kernel_wakelock = 10004;
+        PlatformSleepState platform_sleep_state = 10005;
+        SleepStateVoter sleep_state_voter = 10006;
+        SubsystemSleepState subsystem_sleep_state = 10007;
+        CpuTimePerFreq cpu_time_per_freq = 10008;
+        CpuTimePerUid cpu_time_per_uid = 10009;
+        CpuTimePerUidFreq cpu_time_per_uid_freq = 10010;
+        WifiActivityEnergyInfo wifi_activity_energy_info = 10011;
+        ModemActivityInfo modem_activity_info = 10012;
+        AttributionChainDummyAtom attribution_chain_dummy_atom = 100000;
     }
 }
 
 /**
- * An attribution represents an application or module that is part of process where a particular bit
- * of work is done.
+ * This proto represents a node of an attribution chain.
+ * Note: All attribution chains are represented as a repeated field of type
+ * AttributionNode. It is understood that in such arrays, the order is that
+ * of calls, that is [A, B, C] if A calls B that calls C.
  */
-message Attribution {
-    // The uid for an application or module.
+message AttributionNode {
+    // The uid for a given element in the attribution chain.
     optional int32 uid = 1;
-    // The string tag for the attribution node.
-    optional string tag = 2;
-}
 
-/**
- * An attribution chain represents the chained attributions of applications or modules that
- * resulted in a particular bit of work being done.
- * The ordering of the attributions is that of calls, that is uid = [A, B, C] if A calls B that
- * calls C.
- */
-message AttributionChain {
-    repeated Attribution attribution = 1;
+    // The (optional) string tag for an element in the attribution chain. If the
+    // element has no tag, it is encoded as an empty string.
+    optional string tag = 2;
 }
 
 /*
@@ -151,7 +148,7 @@
  */
 
 message AttributionChainDummyAtom {
-    optional AttributionChain attribution_chain = 1;
+    repeated AttributionNode attribution_node = 1;
     optional int32 value = 2;
 }
 
@@ -421,8 +418,7 @@
  *   TODO
  */
 message WakelockStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     // Type of wakelock.
     enum Type {
@@ -780,13 +776,14 @@
   *   frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java
  */
 message ActivityForegroundStateChanged {
+    optional int32 uid = 1;
+    optional string pkg_name = 2;
+    optional string class_name = 3;
+
     enum Activity {
         MOVE_TO_BACKGROUND = 0;
         MOVE_TO_FOREGROUND = 1;
     }
-    optional int32 uid = 1;
-    optional string pkg_name = 2;
-    optional string class_name = 3;
     optional Activity activity = 4;
 }
 
@@ -806,16 +803,19 @@
     optional string process_name = 3;
 
     // The pid if available. -1 means not available.
-    optional int32 pid = 4;
+    optional sint32 pid = 4;
 
     // 1 indicates is instant app. -1 indicates Not applicable.
-    optional int32 is_instant_app = 5;
+    optional sint32 is_instant_app = 5;
 
     // The activity name if available.
     optional string activity_name = 6;
 
+    // The package name if available.
+    optional string package_name = 7;
+
     // 1 indicates in foreground. -1 indicates not available.
-    optional int32 is_foreground = 7;
+    optional sint32 is_foreground = 8;
 }
 
 /*
@@ -851,11 +851,105 @@
     // Uid that owns the config whose anomaly detection alert fired.
     optional int32 config_uid = 1;
 
-    // Name of the config whose anomaly detection alert fired.
-    optional string config_name = 2;
+    // Id of the config whose anomaly detection alert fired.
+    optional int64 config_id = 2;
 
-    // Name of the alert (i.e. name of the anomaly that was detected).
-    optional string alert_name = 3;
+    // Id of the alert (i.e. name of the anomaly that was detected).
+    optional int64 alert_id = 3;
+}
+
+message AppStartChanged {
+    // The uid if available. -1 means not available.
+    optional int32 uid = 1;
+
+    // The app package name.
+    optional string pkg_name = 2;
+
+    enum TransitionType {
+        APP_START_TRANSITION_TYPE_UNKNOWN = 0;
+        WARM = 1;
+        HOT = 2;
+        COLD = 3;
+    }
+    // The transition type.
+    optional TransitionType type = 3;
+
+    // The activity name.
+    optional string activity_name = 4;
+
+    // The name of the calling app. Empty if not set.
+    optional string calling_pkg_name = 5;
+
+    // Whether the app is an instant app.
+    optional bool is_instant_app = 6;
+
+    // Device uptime when activity started.
+    optional int64 activity_start_msec = 7;
+
+    // TODO: Update android/app/ActivityManagerInternal.java constants to depend on our proto enum.
+    enum TransitionReason {
+        APP_START_TRANSITION_REASON_UNKNOWN = 0;
+        SPLASH_SCREEN = 1;
+        WINDOWS_DRAWN = 2;
+        TIMEOUT = 3;
+        SNAPSHOT = 4;
+    }
+    optional TransitionReason reason = 8;
+
+    optional int32 transition_delay_msec = 9;
+    // -1 if not set.
+    optional int32 starting_window_delay_msec = 10;
+    // -1 if not set.
+    optional int32 bind_application_delay_msec = 11;
+    optional int32 windows_drawn_delay_msec = 12;
+
+    // Empty if not set.
+    optional string launch_token = 13;
+
+}
+
+message AppStartCancelChanged {
+    // The uid if available. -1 means not available.
+    optional int32 uid = 1;
+
+    // The app package name.
+    optional string pkg_name = 2;
+
+    enum TransitionType {
+        APP_START_TRANSITION_TYPE_UNKNOWN = 0;
+        WARM = 1;
+        HOT = 2;
+        COLD = 3;
+    }
+    // The transition type.
+    optional TransitionType type = 3;
+
+    // The activity name.
+    optional string activity_name = 4;
+}
+
+message AppStartFullyDrawnChanged {
+    // The uid if available. -1 means not available.
+    optional int32 uid = 1;
+
+    // The app package name.
+    optional string pkg_name = 2;
+
+    enum TransitionType {
+        APP_START_TRANSITION_TYPE_UNKNOWN = 0;
+        WITH_BUNDLE = 1;
+        WITHOUT_BUNDLE = 2;
+    }
+    // The transition type.
+    optional TransitionType type = 3;
+
+    // The activity name.
+    optional string activity_name = 4;
+
+    optional bool transition_process_running = 5;
+
+    // App startup time (until call to Activity#reportFullyDrawn()).
+    optional int64 app_startup_time_ms = 6;
 }
 
 /**
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index bb4b817..afa26f6 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -30,20 +30,20 @@
 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(const int64_t& id, const int index)
+    : ConditionTracker(id, index) {
+    VLOG("creating CombinationConditionTracker %lld", (long long)mConditionId);
 }
 
 CombinationConditionTracker::~CombinationConditionTracker() {
-    VLOG("~CombinationConditionTracker() %s", mName.c_str());
+    VLOG("~CombinationConditionTracker() %lld", (long long)mConditionId);
 }
 
 bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConfig,
                                        const vector<sp<ConditionTracker>>& allConditionTrackers,
-                                       const unordered_map<string, int>& conditionNameIndexMap,
+                                       const unordered_map<int64_t, int>& conditionIdIndexMap,
                                        vector<bool>& stack) {
-    VLOG("Combination predicate init() %s", mName.c_str());
+    VLOG("Combination predicate init() %lld", (long long)mConditionId);
     if (mInitialized) {
         return true;
     }
@@ -62,11 +62,11 @@
         return false;
     }
 
-    for (string child : combinationCondition.predicate()) {
-        auto it = conditionNameIndexMap.find(child);
+    for (auto child : combinationCondition.predicate()) {
+        auto it = conditionIdIndexMap.find(child);
 
-        if (it == conditionNameIndexMap.end()) {
-            ALOGW("Predicate %s not found in the config", child.c_str());
+        if (it == conditionIdIndexMap.end()) {
+            ALOGW("Predicate %lld not found in the config", (long long)child);
             return false;
         }
 
@@ -79,13 +79,13 @@
         }
 
         bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers,
-                                                     conditionNameIndexMap, stack);
+                                                     conditionIdIndexMap, stack);
 
         if (!initChildSucceeded) {
-            ALOGW("Child initialization failed %s ", child.c_str());
+            ALOGW("Child initialization failed %lld ", (long long)child);
             return false;
         } else {
-            ALOGW("Child initialization success %s ", child.c_str());
+            ALOGW("Child initialization success %lld ", (long long)child);
         }
 
         mChildren.push_back(childIndex);
@@ -103,7 +103,7 @@
 }
 
 void CombinationConditionTracker::isConditionMet(
-        const map<string, HashableDimensionKey>& conditionParameters,
+        const ConditionKey& conditionParameters,
         const vector<sp<ConditionTracker>>& allConditions,
         vector<ConditionState>& conditionCache) const {
     for (const int childIndex : mChildren) {
@@ -154,8 +154,8 @@
             }
         }
         nonSlicedConditionCache[mIndex] = ConditionState::kUnknown;
-        ALOGD("CombinationPredicate %s sliced may changed? %d", mName.c_str(),
-              conditionChangedCache[mIndex] == true);
+        ALOGD("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId,
+            conditionChangedCache[mIndex] == true);
     }
 }
 
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 9336914..dfd3837 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -26,13 +26,13 @@
 
 class CombinationConditionTracker : public virtual ConditionTracker {
 public:
-    CombinationConditionTracker(const std::string& name, const int index);
+    CombinationConditionTracker(const int64_t& id, const int index);
 
     ~CombinationConditionTracker();
 
     bool init(const std::vector<Predicate>& allConditionConfig,
               const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-              const std::unordered_map<std::string, int>& conditionNameIndexMap,
+              const std::unordered_map<int64_t, int>& conditionIdIndexMap,
               std::vector<bool>& stack) override;
 
     void evaluateCondition(const LogEvent& event,
@@ -41,7 +41,7 @@
                            std::vector<ConditionState>& conditionCache,
                            std::vector<bool>& changedCache) override;
 
-    void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+    void isConditionMet(const ConditionKey& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
                         std::vector<ConditionState>& conditionCache) const override;
 
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 6f66ad6..773860f 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -32,8 +32,8 @@
 
 class ConditionTracker : public virtual RefBase {
 public:
-    ConditionTracker(const std::string& name, const int index)
-        : mName(name),
+    ConditionTracker(const int64_t& id, const int index)
+        : mConditionId(id),
           mIndex(index),
           mInitialized(false),
           mTrackerIndex(),
@@ -42,17 +42,19 @@
 
     virtual ~ConditionTracker(){};
 
+    inline const int64_t& getId() { return mConditionId; }
+
     // 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 Predicate 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.
+    // conditionIdIndexMap: the mapping from condition id 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<Predicate>& allConditionConfig,
                       const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-                      const std::unordered_map<std::string, int>& conditionNameIndexMap,
+                      const std::unordered_map<int64_t, int>& conditionIdIndexMap,
                       std::vector<bool>& stack) = 0;
 
     // evaluate current condition given the new event.
@@ -83,7 +85,7 @@
     //                  done recursively
     // [conditionCache]: the cache holding the condition evaluation values.
     virtual void isConditionMet(
-            const std::map<std::string, HashableDimensionKey>& conditionParameters,
+            const ConditionKey& conditionParameters,
             const std::vector<sp<ConditionTracker>>& allConditions,
             std::vector<ConditionState>& conditionCache) const = 0;
 
@@ -97,9 +99,7 @@
     }
 
 protected:
-    // We don't really need the string name, but having a name here makes log messages
-    // easy to debug.
-    const std::string mName;
+    const int64_t mConditionId;
 
     // the index of this condition in the manager's condition list.
     const int mIndex;
diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp
index 411f7e5..d99c2cc 100644
--- a/cmds/statsd/src/condition/ConditionWizard.cpp
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -24,7 +24,7 @@
 using std::vector;
 
 ConditionState ConditionWizard::query(const int index,
-                                      const map<string, HashableDimensionKey>& parameters) {
+                                      const ConditionKey& parameters) {
     vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
 
     mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache);
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
index 30a3684..4ff5c07 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -19,6 +19,7 @@
 
 #include "ConditionTracker.h"
 #include "condition_util.h"
+#include "stats_util.h"
 
 namespace android {
 namespace os {
@@ -40,7 +41,7 @@
     // the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
     virtual ConditionState query(
             const int conditionIndex,
-            const std::map<std::string, HashableDimensionKey>& conditionParameters);
+            const ConditionKey& conditionParameters);
 
 private:
     std::vector<sp<ConditionTracker>> mAllConditions;
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index a63bc04..2525721 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -33,17 +33,17 @@
 using std::vector;
 
 SimpleConditionTracker::SimpleConditionTracker(
-        const ConfigKey& key, const string& name, const int index,
+        const ConfigKey& key, const int64_t& id, const int index,
         const SimplePredicate& simplePredicate,
-        const unordered_map<string, int>& trackerNameIndexMap)
-    : ConditionTracker(name, index), mConfigKey(key) {
-    VLOG("creating SimpleConditionTracker %s", mName.c_str());
+        const unordered_map<int64_t, int>& trackerNameIndexMap)
+    : ConditionTracker(id, index), mConfigKey(key) {
+    VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId);
     mCountNesting = simplePredicate.count_nesting();
 
     if (simplePredicate.has_start()) {
         auto pair = trackerNameIndexMap.find(simplePredicate.start());
         if (pair == trackerNameIndexMap.end()) {
-            ALOGW("Start matcher %s not found in the config", simplePredicate.start().c_str());
+            ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start());
             return;
         }
         mStartLogMatcherIndex = pair->second;
@@ -55,7 +55,7 @@
     if (simplePredicate.has_stop()) {
         auto pair = trackerNameIndexMap.find(simplePredicate.stop());
         if (pair == trackerNameIndexMap.end()) {
-            ALOGW("Stop matcher %s not found in the config", simplePredicate.stop().c_str());
+            ALOGW("Stop matcher %lld not found in the config", (long long)simplePredicate.stop());
             return;
         }
         mStopLogMatcherIndex = pair->second;
@@ -67,7 +67,7 @@
     if (simplePredicate.has_stop_all()) {
         auto pair = trackerNameIndexMap.find(simplePredicate.stop_all());
         if (pair == trackerNameIndexMap.end()) {
-            ALOGW("Stop all matcher %s not found in the config", simplePredicate.stop().c_str());
+            ALOGW("Stop all matcher %lld found in the config", (long long)simplePredicate.stop_all());
             return;
         }
         mStopAllLogMatcherIndex = pair->second;
@@ -76,10 +76,9 @@
         mStopAllLogMatcherIndex = -1;
     }
 
-    mOutputDimension.insert(mOutputDimension.begin(), simplePredicate.dimension().begin(),
-                            simplePredicate.dimension().end());
+    mOutputDimensions = simplePredicate.dimensions();
 
-    if (mOutputDimension.size() > 0) {
+    if (mOutputDimensions.child_size() > 0) {
         mSliced = true;
     }
 
@@ -100,15 +99,15 @@
 
 bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig,
                                   const vector<sp<ConditionTracker>>& allConditionTrackers,
-                                  const unordered_map<string, int>& conditionNameIndexMap,
+                                  const unordered_map<int64_t, int>& conditionIdIndexMap,
                                   vector<bool>& stack) {
     // SimpleConditionTracker does not have dependency on other conditions, thus we just return
     // if the initialization was successful.
     return mInitialized;
 }
 
-void print(map<HashableDimensionKey, int>& conditions, const string& name) {
-    VLOG("%s DUMP:", name.c_str());
+void print(map<HashableDimensionKey, int>& conditions, const int64_t& id) {
+    VLOG("%lld DUMP:", (long long)id);
     for (const auto& pair : conditions) {
         VLOG("\t%s : %d", pair.first.c_str(), pair.second);
     }
@@ -136,10 +135,11 @@
     // 1. Report the tuple count if the tuple count > soft limit
     if (mSlicedConditionState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mSlicedConditionState.size() + 1;
-        StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mName, newTupleCount);
+        StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-            ALOGE("Predicate %s dropping data for dimension key %s", mName.c_str(), newKey.c_str());
+            ALOGE("Predicate %lld dropping data for dimension key %s",
+                (long long)mConditionId, newKey.c_str());
             return true;
         }
     }
@@ -150,6 +150,14 @@
                                                   bool matchStart,
                                                   std::vector<ConditionState>& conditionCache,
                                                   std::vector<bool>& conditionChangedCache) {
+    if ((int)conditionChangedCache.size() <= mIndex) {
+        ALOGE("handleConditionEvent: param conditionChangedCache not initialized.");
+        return;
+    }
+    if ((int)conditionCache.size() <= mIndex) {
+        ALOGE("handleConditionEvent: param conditionCache not initialized.");
+        return;
+    }
     bool changed = false;
     auto outputIt = mSlicedConditionState.find(outputKey);
     ConditionState newCondition;
@@ -215,13 +223,13 @@
 
     // dump all dimensions for debugging
     if (DEBUG) {
-        print(mSlicedConditionState, mName);
+        print(mSlicedConditionState, mConditionId);
     }
 
     conditionChangedCache[mIndex] = changed;
     conditionCache[mIndex] = newCondition;
 
-    VLOG("SimplePredicate %s nonSlicedChange? %d", mName.c_str(),
+    VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId,
          conditionChangedCache[mIndex] == true);
 }
 
@@ -232,7 +240,8 @@
                                                vector<bool>& conditionChangedCache) {
     if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
         // it has been evaluated.
-        VLOG("Yes, already evaluated, %s %d", mName.c_str(), conditionCache[mIndex]);
+        VLOG("Yes, already evaluated, %lld %d",
+            (long long)mConditionId, conditionCache[mIndex]);
         return;
     }
 
@@ -278,37 +287,65 @@
         return;
     }
 
-    // outputKey is the output key values. e.g, uid:1234
-    const HashableDimensionKey outputKey(getDimensionKey(event, mOutputDimension));
-    handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache);
+    // outputKey is the output values. e.g, uid:1234
+    const std::vector<DimensionsValue> outputValues = getDimensionKeys(event, mOutputDimensions);
+    if (outputValues.size() == 0) {
+        // The original implementation would generate an empty string dimension hash when condition
+        // is not sliced.
+        handleConditionEvent(
+            DEFAULT_DIMENSION_KEY, matchedState == 1, conditionCache, conditionChangedCache);
+    } else if (outputValues.size() == 1) {
+        handleConditionEvent(HashableDimensionKey(outputValues[0]), matchedState == 1,
+            conditionCache, conditionChangedCache);
+    } else {
+        // If this event has multiple nodes in the attribution chain,  this log event probably will
+        // generate multiple dimensions. If so, we will find if the condition changes for any
+        // dimension and ask the corresponding metric producer to verify whether the actual sliced
+        // condition has changed or not.
+        // A high level assumption is that a predicate is either sliced or unsliced. We will never
+        // have both sliced and unsliced version of a predicate.
+        for (const DimensionsValue& outputValue : outputValues) {
+            vector<ConditionState> dimensionalConditionCache(conditionCache.size(),
+                                                             ConditionState::kNotEvaluated);
+            vector<bool> dimensionalConditionChangedCache(conditionChangedCache.size(), false);
+
+            handleConditionEvent(HashableDimensionKey(outputValue), matchedState == 1,
+                dimensionalConditionCache, dimensionalConditionChangedCache);
+
+            OrConditionState(dimensionalConditionCache, &conditionCache);
+            OrBooleanVector(dimensionalConditionChangedCache, &conditionChangedCache);
+        }
+    }
 }
 
 void SimpleConditionTracker::isConditionMet(
-        const map<string, HashableDimensionKey>& conditionParameters,
+        const ConditionKey& conditionParameters,
         const vector<sp<ConditionTracker>>& allConditions,
         vector<ConditionState>& conditionCache) const {
-    const auto pair = conditionParameters.find(mName);
-    HashableDimensionKey key =
-            (pair == conditionParameters.end()) ? DEFAULT_DIMENSION_KEY : pair->second;
+    const auto pair = conditionParameters.find(mConditionId);
 
-    if (pair == conditionParameters.end() && mOutputDimension.size() > 0) {
-        ALOGE("Predicate %s output has dimension, but it's not specified in the query!",
-              mName.c_str());
+    if (pair == conditionParameters.end() && mOutputDimensions.child_size() > 0) {
+        ALOGE("Predicate %lld output has dimension, but it's not specified in the query!",
+              (long long)mConditionId);
         conditionCache[mIndex] = mInitialValue;
         return;
     }
+    std::vector<HashableDimensionKey> defaultKeys = {DEFAULT_DIMENSION_KEY};
+    const std::vector<HashableDimensionKey> &keys =
+            (pair == conditionParameters.end()) ? defaultKeys : pair->second;
 
-    VLOG("simplePredicate %s query key: %s", mName.c_str(), key.c_str());
-
-    auto startedCountIt = mSlicedConditionState.find(key);
-    if (startedCountIt == mSlicedConditionState.end()) {
-        conditionCache[mIndex] = mInitialValue;
-    } else {
-        conditionCache[mIndex] =
-                startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+    ConditionState conditionState = ConditionState::kNotEvaluated;
+    for (const auto& key : keys) {
+        auto startedCountIt = mSlicedConditionState.find(key);
+        if (startedCountIt != mSlicedConditionState.end()) {
+            conditionState = conditionState |
+                    (startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse);
+        } else {
+            conditionState = conditionState | mInitialValue;
+        }
     }
-
-    VLOG("Predicate %s return %d", mName.c_str(), conditionCache[mIndex]);
+    conditionCache[mIndex] = conditionState;
+    VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 644d84c..815b445 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -29,15 +29,15 @@
 
 class SimpleConditionTracker : public virtual ConditionTracker {
 public:
-    SimpleConditionTracker(const ConfigKey& key, const std::string& name, const int index,
+    SimpleConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
                            const SimplePredicate& simplePredicate,
-                           const std::unordered_map<std::string, int>& trackerNameIndexMap);
+                           const std::unordered_map<int64_t, int>& trackerNameIndexMap);
 
     ~SimpleConditionTracker();
 
     bool init(const std::vector<Predicate>& allConditionConfig,
               const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-              const std::unordered_map<std::string, int>& conditionNameIndexMap,
+              const std::unordered_map<int64_t, int>& conditionIdIndexMap,
               std::vector<bool>& stack) override;
 
     void evaluateCondition(const LogEvent& event,
@@ -46,7 +46,7 @@
                            std::vector<ConditionState>& conditionCache,
                            std::vector<bool>& changedCache) override;
 
-    void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+    void isConditionMet(const ConditionKey& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
                         std::vector<ConditionState>& conditionCache) const override;
 
@@ -66,7 +66,7 @@
 
     ConditionState mInitialValue;
 
-    std::vector<KeyMatcher> mOutputDimension;
+    FieldMatcher mOutputDimensions;
 
     std::map<HashableDimensionKey, int> mSlicedConditionState;
 
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index 53ef9d5..a5aee73 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -27,6 +27,7 @@
 #include "ConditionTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
+#include "dimension.h"
 
 namespace android {
 namespace os {
@@ -93,23 +94,106 @@
     return newCondition;
 }
 
-HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
-                                                 const MetricConditionLink& link) {
-    vector<KeyMatcher> eventKey;
-    eventKey.reserve(link.key_in_what().size());
+ConditionState operator|(ConditionState l, ConditionState r) {
+    return l >= r ? l : r;
+}
 
-    for (const auto& key : link.key_in_what()) {
-        eventKey.push_back(key);
+void OrConditionState(const std::vector<ConditionState>& ref, vector<ConditionState> * ored) {
+    if (ref.size() != ored->size()) {
+        return;
+    }
+    for (size_t i = 0; i < ored->size(); ++i) {
+        ored->at(i) = ored->at(i) | ref.at(i);
+    }
+}
+
+void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored) {
+    if (ref.size() != ored->size()) {
+        return;
+    }
+    for (size_t i = 0; i < ored->size(); ++i) {
+        ored->at(i) = ored->at(i) | ref.at(i);
+    }
+}
+
+void getFieldsFromFieldMatcher(const FieldMatcher& matcher, const Field& parentField,
+                       std::vector<Field> *allFields) {
+    Field newParent = parentField;
+    Field* leaf = getSingleLeaf(&newParent);
+    leaf->set_field(matcher.field());
+    if (matcher.child_size() == 0) {
+        allFields->push_back(newParent);
+        return;
+    }
+    for (int i = 0; i < matcher.child_size(); ++i) {
+        leaf->add_child();
+        getFieldsFromFieldMatcher(matcher.child(i), newParent, allFields);
+    }
+}
+
+void getFieldsFromFieldMatcher(const FieldMatcher& matcher, std::vector<Field> *allFields) {
+    Field parentField;
+    getFieldsFromFieldMatcher(matcher, parentField, allFields);
+}
+
+void flattenValueLeaves(const DimensionsValue& value,
+                        std::vector<DimensionsValue> *allLaves) {
+    switch (value.value_case()) {
+        case DimensionsValue::ValueCase::kValueStr:
+        case DimensionsValue::ValueCase::kValueInt:
+        case DimensionsValue::ValueCase::kValueLong:
+        case DimensionsValue::ValueCase::kValueBool:
+        case DimensionsValue::ValueCase::kValueFloat:
+        case DimensionsValue::ValueCase::VALUE_NOT_SET:
+            allLaves->push_back(value);
+            break;
+        case DimensionsValue::ValueCase::kValueTuple:
+            for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) {
+                flattenValueLeaves(value.value_tuple().dimensions_value(i), allLaves);
+            }
+            break;
+    }
+}
+
+std::vector<HashableDimensionKey> getDimensionKeysForCondition(
+    const LogEvent& event, const MetricConditionLink& link) {
+    std::vector<Field> whatFields;
+    getFieldsFromFieldMatcher(link.dimensions_in_what(), &whatFields);
+    std::vector<Field> conditionFields;
+    getFieldsFromFieldMatcher(link.dimensions_in_condition(), &conditionFields);
+
+    std::vector<HashableDimensionKey> hashableDimensionKeys;
+
+    // TODO(yanglu): here we could simplify the logic to get the leaf value node in what and
+    // directly construct the full condition value tree.
+    std::vector<DimensionsValue> whatValues = getDimensionKeys(event, link.dimensions_in_what());
+
+    for (size_t i = 0; i < whatValues.size(); ++i) {
+        std::vector<DimensionsValue> whatLeaves;
+        flattenValueLeaves(whatValues[i], &whatLeaves);
+        if (whatLeaves.size() != whatFields.size() ||
+            whatLeaves.size() != conditionFields.size()) {
+            ALOGE("Dimensions between what and condition not equal.");
+            return hashableDimensionKeys;
+        }
+        FieldValueMap conditionValueMap;
+        for (size_t j = 0; j < whatLeaves.size(); ++j) {
+            if (!setFieldInLeafValueProto(conditionFields[j], &whatLeaves[j])) {
+                ALOGE("Not able to reset the field for condition leaf value.");
+                return hashableDimensionKeys;
+            }
+            conditionValueMap.insert(std::make_pair(conditionFields[j], whatLeaves[j]));
+        }
+        std::vector<DimensionsValue> conditionValues;
+        findDimensionsValues(conditionValueMap, link.dimensions_in_condition(), &conditionValues);
+        if (conditionValues.size() != 1) {
+            ALOGE("Not able to find unambiguous field value in condition atom.");
+            continue;
+        }
+        hashableDimensionKeys.push_back(HashableDimensionKey(conditionValues[0]));
     }
 
-    vector<KeyValuePair> dimensionKey = getDimensionKey(event, eventKey);
-
-    for (int i = 0; i < link.key_in_what_size(); i++) {
-        auto& kv = dimensionKey[i];
-        kv.set_key(link.key_in_condition(i).key());
-    }
-
-    return HashableDimensionKey(dimensionKey);
+    return hashableDimensionKeys;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h
index 934c207..598027b 100644
--- a/cmds/statsd/src/condition/condition_util.h
+++ b/cmds/statsd/src/condition/condition_util.h
@@ -32,12 +32,16 @@
     kTrue = 1,
 };
 
+ConditionState operator|(ConditionState l, ConditionState r);
+void OrConditionState(const std::vector<ConditionState>& ref, vector<ConditionState> * ored);
+void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored);
+
 ConditionState evaluateCombinationCondition(const std::vector<int>& children,
                                             const LogicalOperation& operation,
                                             const std::vector<ConditionState>& conditionCache);
 
-HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
-                                                 const MetricConditionLink& link);
+std::vector<HashableDimensionKey> getDimensionKeysForCondition(
+        const LogEvent& event, const MetricConditionLink& link);
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/config/ConfigKey.cpp b/cmds/statsd/src/config/ConfigKey.cpp
index a365dc0..d791f86 100644
--- a/cmds/statsd/src/config/ConfigKey.cpp
+++ b/cmds/statsd/src/config/ConfigKey.cpp
@@ -27,10 +27,10 @@
 ConfigKey::ConfigKey() {
 }
 
-ConfigKey::ConfigKey(const ConfigKey& that) : mName(that.mName), mUid(that.mUid) {
+ConfigKey::ConfigKey(const ConfigKey& that) : mId(that.mId), mUid(that.mUid) {
 }
 
-ConfigKey::ConfigKey(int uid, const string& name) : mName(name), mUid(uid) {
+ConfigKey::ConfigKey(int uid, const int64_t& id) : mId(id), mUid(uid) {
 }
 
 ConfigKey::~ConfigKey() {
@@ -38,10 +38,21 @@
 
 string ConfigKey::ToString() const {
     ostringstream out;
-    out << '(' << mUid << ',' << mName << ')';
+    out << '(' << mUid << ',' << mId << ')';
     return out.str();
 }
 
+
+int64_t StrToInt64(const string& str) {
+    char* endp;
+    int64_t value;
+    value = strtoll(str.c_str(), &endp, 0);
+    if (endp == str.c_str() || *endp != '\0') {
+        value = 0;
+    }
+    return value;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/config/ConfigKey.h b/cmds/statsd/src/config/ConfigKey.h
index 3489c43..3ad0eed 100644
--- a/cmds/statsd/src/config/ConfigKey.h
+++ b/cmds/statsd/src/config/ConfigKey.h
@@ -37,14 +37,14 @@
 public:
     ConfigKey();
     explicit ConfigKey(const ConfigKey& that);
-    ConfigKey(int uid, const string& name);
+    ConfigKey(int uid, const int64_t& id);
     ~ConfigKey();
 
     inline int GetUid() const {
         return mUid;
     }
-    inline const string& GetName() const {
-        return mName;
+    inline const int64_t& GetId() const {
+        return mId;
     }
 
     inline bool operator<(const ConfigKey& that) const {
@@ -54,17 +54,17 @@
         if (mUid > that.mUid) {
             return false;
         }
-        return mName < that.mName;
+        return mId < that.mId;
     };
 
     inline bool operator==(const ConfigKey& that) const {
-        return mUid == that.mUid && mName == that.mName;
+        return mUid == that.mUid && mId == that.mId;
     };
 
     string ToString() const;
 
 private:
-    string mName;
+    int64_t mId;
     int mUid;
 };
 
@@ -72,6 +72,8 @@
     return os << config.ToString();
 }
 
+int64_t StrToInt64(const string& str);
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
@@ -87,7 +89,7 @@
 template <>
 struct hash<ConfigKey> {
     std::size_t operator()(const ConfigKey& key) const {
-        return (7 * key.GetUid()) ^ ((hash<string>()(key.GetName())));
+        return (7 * key.GetUid()) ^ ((hash<long long>()(key.GetId())));
     }
 };
 
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index cb3f3d6..184f69e 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -49,15 +49,13 @@
 void ConfigManager::Startup() {
     map<ConfigKey, StatsdConfig> configsFromDisk;
     StorageManager::readConfigFromDisk(configsFromDisk);
-    // TODO(b/70667694): Make the configs from disk be used. And remove the fake config,
-    // and tests shouldn't call this Startup(), maybe call StartupForTest() so we don't read
-    // configs from disk for tests.
-    // for (const auto& pair : configsFromDisk) {
-    //    UpdateConfig(pair.first, pair.second);
-    //}
+    for (const auto& pair : configsFromDisk) {
+        UpdateConfig(pair.first, pair.second);
+    }
+}
 
-    // Uncomment the following line and use the hard coded config for development.
-    // UpdateConfig(ConfigKey(1000, "fake"), build_fake_config());
+void ConfigManager::StartupForTest() {
+    // Dummy function to avoid reading configs from disks for tests.
 }
 
 void ConfigManager::AddListener(const sp<ConfigListener>& listener) {
@@ -103,7 +101,7 @@
 }
 
 void ConfigManager::remove_saved_configs(const ConfigKey& key) {
-    string prefix = StringPrintf("%d-%s", key.GetUid(), key.GetName().c_str());
+    string prefix = StringPrintf("%d-%lld", key.GetUid(), (long long)key.GetId());
     StorageManager::deletePrefixedFiles(STATS_SERVICE_DIR, prefix.c_str());
 }
 
@@ -173,7 +171,7 @@
     fprintf(out, "CONFIGURATIONS (%d)\n", (int)mConfigs.size());
     fprintf(out, "     uid name\n");
     for (const auto& key : mConfigs) {
-        fprintf(out, "  %6d %s\n", key.GetUid(), key.GetName().c_str());
+        fprintf(out, "  %6d %lld\n", key.GetUid(), (long long)key.GetId());
         auto receiverIt = mConfigReceivers.find(key);
         if (receiverIt != mConfigReceivers.end()) {
             fprintf(out, "    -> received by %s, %s\n", receiverIt->second.first.c_str(),
@@ -189,8 +187,8 @@
     remove_saved_configs(key);
 
     // Then we save the latest config.
-    string file_name = StringPrintf("%s/%d-%s-%ld", STATS_SERVICE_DIR, key.GetUid(),
-                                    key.GetName().c_str(), time(nullptr));
+    string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_SERVICE_DIR, key.GetUid(),
+                                    (long long)key.GetId(), time(nullptr));
     const int numBytes = config.ByteSize();
     vector<uint8_t> buffer(numBytes);
     config.SerializeToArray(&buffer[0], numBytes);
@@ -200,7 +198,7 @@
 StatsdConfig build_fake_config() {
     // HACK: Hard code a test metric for counting screen on events...
     StatsdConfig config;
-    config.set_name("CONFIG_12345");
+    config.set_id(12345);
 
     int WAKE_LOCK_TAG_ID = 1111;  // put a fake id here to make testing easier.
     int WAKE_LOCK_UID_KEY_ID = 1;
@@ -209,7 +207,7 @@
     int WAKE_LOCK_ACQUIRE_VALUE = 1;
     int WAKE_LOCK_RELEASE_VALUE = 0;
 
-    int APP_USAGE_ID = 12345;
+    int APP_USAGE_TAG_ID = 12345;
     int APP_USAGE_UID_KEY_ID = 1;
     int APP_USAGE_STATE_KEY = 2;
     int APP_USAGE_FOREGROUND = 1;
@@ -232,14 +230,14 @@
 
     // Count Screen ON events.
     CountMetric* metric = config.add_count_metric();
-    metric->set_name("METRIC_1");
-    metric->set_what("SCREEN_TURNED_ON");
-    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    metric->set_id(1);  // METRIC_1
+    metric->set_what(102);  //  "SCREEN_TURNED_ON"
+    metric->set_bucket(ONE_MINUTE);
 
     // Anomaly threshold for screen-on count.
     // TODO(b/70627390): Uncomment once the bug is fixed.
     /*Alert* alert = config.add_alert();
-    alert->set_name("ALERT_1");
+    alert->set_id("ALERT_1");
     alert->set_metric_name("METRIC_1");
     alert->set_number_of_buckets(6);
     alert->set_trigger_if_sum_gt(10);
@@ -248,25 +246,26 @@
     details->add_section(12);
     details->add_section(13);*/
 
-    AllowedLogSource* logSource = config.mutable_log_source();
-    logSource->add_uid(1000);
-    logSource->add_uid(0);
-    logSource->add_package("com.android.statsd.dogfood");
-    logSource->add_package("com.android.bluetooth");
+    config.add_allowed_log_source("AID_ROOT");
+    config.add_allowed_log_source("AID_SYSTEM");
+    config.add_allowed_log_source("AID_BLUETOOTH");
+    config.add_allowed_log_source("com.android.statsd.dogfood");
+    config.add_allowed_log_source("com.android.systemui");
 
     // Count process state changes, slice by uid.
     metric = config.add_count_metric();
-    metric->set_name("METRIC_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);
+    metric->set_id(2);  // "METRIC_2"
+    metric->set_what(104);
+    metric->set_bucket(ONE_MINUTE);
+    FieldMatcher* dimensions = metric->mutable_dimensions();
+    dimensions->set_field(UID_PROCESS_STATE_TAG_ID);
+    dimensions->add_child()->set_field(UID_PROCESS_STATE_UID_KEY);
 
     // Anomaly threshold for background count.
     // TODO(b/70627390): Uncomment once the bug is fixed.
     /*
     alert = config.add_alert();
-    alert->set_name("ALERT_2");
+    alert->set_id("ALERT_2");
     alert->set_metric_name("METRIC_2");
     alert->set_number_of_buckets(4);
     alert->set_trigger_if_sum_gt(30);
@@ -277,79 +276,95 @@
 
     // Count process state changes, slice by uid, while SCREEN_IS_OFF
     metric = config.add_count_metric();
-    metric->set_name("METRIC_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");
+    metric->set_id(3);
+    metric->set_what(104);
+    metric->set_bucket(ONE_MINUTE);
+
+    dimensions = metric->mutable_dimensions();
+    dimensions->set_field(UID_PROCESS_STATE_TAG_ID);
+    dimensions->add_child()->set_field(UID_PROCESS_STATE_UID_KEY);
+    metric->set_condition(202);
 
     // Count wake lock, slice by uid, while SCREEN_IS_ON and app in background
     metric = config.add_count_metric();
-    metric->set_name("METRIC_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");
+    metric->set_id(4);
+    metric->set_what(107);
+    metric->set_bucket(ONE_MINUTE);
+    dimensions = metric->mutable_dimensions();
+    dimensions->set_field(WAKE_LOCK_TAG_ID);
+    dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+
+
+    metric->set_condition(204);
     MetricConditionLink* link = metric->add_links();
-    link->set_condition("APP_IS_BACKGROUND");
-    link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID);
-    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+    link->set_condition(203);
+    link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID);
+    link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+    link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID);
+    link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID);
 
     // Duration of an app holding any wl, while screen on and app in background, slice by uid
     DurationMetric* durationMetric = config.add_duration_metric();
-    durationMetric->set_name("METRIC_5");
-    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->set_id(5);
+    durationMetric->set_bucket(ONE_MINUTE);
     durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
-    keyMatcher = durationMetric->add_dimension();
-    keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
-    durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
-    durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    dimensions = durationMetric->mutable_dimensions();
+    dimensions->set_field(WAKE_LOCK_TAG_ID);
+    dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+    durationMetric->set_what(205);
+    durationMetric->set_condition(204);
     link = durationMetric->add_links();
-    link->set_condition("APP_IS_BACKGROUND");
-    link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID);
-    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+    link->set_condition(203);
+    link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID);
+    link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+    link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID);
+    link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID);
 
     // max Duration of an app holding any wl, while screen on and app in background, slice by uid
     durationMetric = config.add_duration_metric();
-    durationMetric->set_name("METRIC_6");
-    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->set_id(6);
+    durationMetric->set_bucket(ONE_MINUTE);
     durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
-    keyMatcher = durationMetric->add_dimension();
-    keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
-    durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
-    durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    dimensions = durationMetric->mutable_dimensions();
+    dimensions->set_field(WAKE_LOCK_TAG_ID);
+    dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+    durationMetric->set_what(205);
+    durationMetric->set_condition(204);
     link = durationMetric->add_links();
-    link->set_condition("APP_IS_BACKGROUND");
-    link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID);
-    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+    link->set_condition(203);
+    link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID);
+    link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+    link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID);
+    link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID);
 
     // Duration of an app holding any wl, while screen on and app in background
     durationMetric = config.add_duration_metric();
-    durationMetric->set_name("METRIC_7");
-    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->set_id(7);
+    durationMetric->set_bucket(ONE_MINUTE);
     durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
-    durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
-    durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    durationMetric->set_what(205);
+    durationMetric->set_condition(204);
     link = durationMetric->add_links();
-    link->set_condition("APP_IS_BACKGROUND");
-    link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID);
-    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+    link->set_condition(203);
+    link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID);
+    link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+    link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID);
+    link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID);
+
 
     // Duration of screen on time.
     durationMetric = config.add_duration_metric();
-    durationMetric->set_name("METRIC_8");
-    durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L);
+    durationMetric->set_id(8);
+    durationMetric->set_bucket(ONE_MINUTE);
     durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
-    durationMetric->set_what("SCREEN_IS_ON");
+    durationMetric->set_what(201);
 
     // Anomaly threshold for background count.
     // TODO(b/70627390): Uncomment once the bug is fixed.
     /*
     alert = config.add_alert();
-    alert->set_name("ALERT_8");
-    alert->set_metric_name("METRIC_8");
+    alert->set_id(308);
+    alert->set_metric_id(8);
     alert->set_number_of_buckets(4);
     alert->set_trigger_if_sum_gt(2000000000); // 2 seconds
     alert->set_refractory_period_secs(120);
@@ -358,142 +373,148 @@
 
     // Value metric to count KERNEL_WAKELOCK when screen turned on
     ValueMetric* valueMetric = config.add_value_metric();
-    valueMetric->set_name("METRIC_6");
-    valueMetric->set_what("KERNEL_WAKELOCK");
-    valueMetric->set_value_field(KERNEL_WAKELOCK_COUNT_KEY);
-    valueMetric->set_condition("SCREEN_IS_ON");
-    keyMatcher = valueMetric->add_dimension();
-    keyMatcher->set_key(KERNEL_WAKELOCK_NAME_KEY);
+    valueMetric->set_id(11);
+    valueMetric->set_what(109);
+    valueMetric->mutable_value_field()->set_field(KERNEL_WAKELOCK_TAG_ID);
+    valueMetric->mutable_value_field()->add_child()->set_field(KERNEL_WAKELOCK_COUNT_KEY);
+    valueMetric->set_condition(201);
+    dimensions = valueMetric->mutable_dimensions();
+    dimensions->set_field(KERNEL_WAKELOCK_TAG_ID);
+    dimensions->add_child()->set_field(KERNEL_WAKELOCK_NAME_KEY);
     // This is for testing easier. We should never set bucket size this small.
-    valueMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
+    durationMetric->set_bucket(ONE_MINUTE);
 
     // Add an EventMetric to log process state change events.
     EventMetric* eventMetric = config.add_event_metric();
-    eventMetric->set_name("METRIC_9");
-    eventMetric->set_what("SCREEN_TURNED_ON");
+    eventMetric->set_id(9);
+    eventMetric->set_what(102); // "SCREEN_TURNED_ON"
 
     // Add an GaugeMetric.
     GaugeMetric* gaugeMetric = config.add_gauge_metric();
-    gaugeMetric->set_name("METRIC_10");
-    gaugeMetric->set_what("DEVICE_TEMPERATURE");
-    gaugeMetric->mutable_gauge_fields()->add_field_num(DEVICE_TEMPERATURE_KEY);
-    gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
+    gaugeMetric->set_id(10);
+    gaugeMetric->set_what(101);
+    auto gaugeFieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields();
+    gaugeFieldMatcher->set_field(DEVICE_TEMPERATURE_TAG_ID);
+    gaugeFieldMatcher->add_child()->set_field(DEVICE_TEMPERATURE_KEY);
+    durationMetric->set_bucket(ONE_MINUTE);
 
-    // Event matchers............
+    // Event matchers.
     AtomMatcher* temperatureAtomMatcher = config.add_atom_matcher();
-    temperatureAtomMatcher->set_name("DEVICE_TEMPERATURE");
-    temperatureAtomMatcher->mutable_simple_atom_matcher()->set_tag(
+    temperatureAtomMatcher->set_id(101);  // "DEVICE_TEMPERATURE"
+    temperatureAtomMatcher->mutable_simple_atom_matcher()->set_atom_id(
         DEVICE_TEMPERATURE_TAG_ID);
 
     AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_TURNED_ON");
+    eventMatcher->set_id(102);  // "SCREEN_TURNED_ON"
     SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(SCREEN_EVENT_TAG_ID);
-    KeyValueMatcher* keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
-    keyValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE);
+    simpleAtomMatcher->set_atom_id(SCREEN_EVENT_TAG_ID);
+    FieldValueMatcher* fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(SCREEN_EVENT_STATE_KEY);
+    fieldValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_TURNED_OFF");
+    eventMatcher->set_id(103);  // "SCREEN_TURNED_OFF"
     simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(SCREEN_EVENT_TAG_ID);
-    keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
-    keyValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE);
+    simpleAtomMatcher->set_atom_id(SCREEN_EVENT_TAG_ID);
+    fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(SCREEN_EVENT_STATE_KEY);
+    fieldValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("PROCESS_STATE_CHANGE");
+    eventMatcher->set_id(104);  // "PROCESS_STATE_CHANGE"
     simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(UID_PROCESS_STATE_TAG_ID);
+    simpleAtomMatcher->set_atom_id(UID_PROCESS_STATE_TAG_ID);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("APP_GOES_BACKGROUND");
+    eventMatcher->set_id(105);  // "APP_GOES_BACKGROUND"
     simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(APP_USAGE_ID);
-    keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
-    keyValueMatcher->set_eq_int(APP_USAGE_BACKGROUND);
+    simpleAtomMatcher->set_atom_id(APP_USAGE_TAG_ID);
+    fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(APP_USAGE_STATE_KEY);
+    fieldValueMatcher->set_eq_int(APP_USAGE_BACKGROUND);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("APP_GOES_FOREGROUND");
+    eventMatcher->set_id(106);  // "APP_GOES_FOREGROUND"
     simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(APP_USAGE_ID);
-    keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
-    keyValueMatcher->set_eq_int(APP_USAGE_FOREGROUND);
+    simpleAtomMatcher->set_atom_id(APP_USAGE_TAG_ID);
+    fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(APP_USAGE_STATE_KEY);
+    fieldValueMatcher->set_eq_int(APP_USAGE_FOREGROUND);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("APP_GET_WL");
+    eventMatcher->set_id(107);  // "APP_GET_WL"
     simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(WAKE_LOCK_TAG_ID);
-    keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
-    keyValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE);
+    simpleAtomMatcher->set_atom_id(WAKE_LOCK_TAG_ID);
+    fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(WAKE_LOCK_STATE_KEY);
+    fieldValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("APP_RELEASE_WL");
+    eventMatcher->set_id(108);  //"APP_RELEASE_WL"
     simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(WAKE_LOCK_TAG_ID);
-    keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
-    keyValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE);
+    simpleAtomMatcher->set_atom_id(WAKE_LOCK_TAG_ID);
+    fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(WAKE_LOCK_STATE_KEY);
+    fieldValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE);
 
     // pulled events
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("KERNEL_WAKELOCK");
+    eventMatcher->set_id(109);  // "KERNEL_WAKELOCK"
     simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(KERNEL_WAKELOCK_TAG_ID);
+    simpleAtomMatcher->set_atom_id(KERNEL_WAKELOCK_TAG_ID);
 
     // Predicates.............
     Predicate* predicate = config.add_predicate();
-    predicate->set_name("SCREEN_IS_ON");
+    predicate->set_id(201);  // "SCREEN_IS_ON"
     SimplePredicate* simplePredicate = predicate->mutable_simple_predicate();
-    simplePredicate->set_start("SCREEN_TURNED_ON");
-    simplePredicate->set_stop("SCREEN_TURNED_OFF");
+    simplePredicate->set_start(102);  // "SCREEN_TURNED_ON"
+    simplePredicate->set_stop(103);
     simplePredicate->set_count_nesting(false);
 
     predicate = config.add_predicate();
-    predicate->set_name("SCREEN_IS_OFF");
+    predicate->set_id(202);  // "SCREEN_IS_OFF"
     simplePredicate = predicate->mutable_simple_predicate();
-    simplePredicate->set_start("SCREEN_TURNED_OFF");
-    simplePredicate->set_stop("SCREEN_TURNED_ON");
+    simplePredicate->set_start(103);
+    simplePredicate->set_stop(102);  // "SCREEN_TURNED_ON"
     simplePredicate->set_count_nesting(false);
 
     predicate = config.add_predicate();
-    predicate->set_name("APP_IS_BACKGROUND");
+    predicate->set_id(203);  // "APP_IS_BACKGROUND"
     simplePredicate = predicate->mutable_simple_predicate();
-    simplePredicate->set_start("APP_GOES_BACKGROUND");
-    simplePredicate->set_stop("APP_GOES_FOREGROUND");
-    KeyMatcher* predicate_dimension1 = simplePredicate->add_dimension();
-    predicate_dimension1->set_key(APP_USAGE_UID_KEY_ID);
+    simplePredicate->set_start(105);
+    simplePredicate->set_stop(106);
+    FieldMatcher* predicate_dimension1 = simplePredicate->mutable_dimensions();
+    predicate_dimension1->set_field(APP_USAGE_TAG_ID);
+    predicate_dimension1->add_child()->set_field(APP_USAGE_UID_KEY_ID);
     simplePredicate->set_count_nesting(false);
 
     predicate = config.add_predicate();
-    predicate->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    predicate->set_id(204);  // "APP_IS_BACKGROUND_AND_SCREEN_ON"
     Predicate_Combination* combination_predicate = predicate->mutable_combination();
     combination_predicate->set_operation(LogicalOperation::AND);
-    combination_predicate->add_predicate("APP_IS_BACKGROUND");
-    combination_predicate->add_predicate("SCREEN_IS_ON");
+    combination_predicate->add_predicate(203);
+    combination_predicate->add_predicate(201);
 
     predicate = config.add_predicate();
-    predicate->set_name("WL_HELD_PER_APP_PER_NAME");
+    predicate->set_id(205);  // "WL_HELD_PER_APP_PER_NAME"
     simplePredicate = predicate->mutable_simple_predicate();
-    simplePredicate->set_start("APP_GET_WL");
-    simplePredicate->set_stop("APP_RELEASE_WL");
-    KeyMatcher* predicate_dimension = simplePredicate->add_dimension();
-    predicate_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
-    predicate_dimension = simplePredicate->add_dimension();
-    predicate_dimension->set_key(WAKE_LOCK_NAME_KEY);
+    simplePredicate->set_start(107);
+    simplePredicate->set_stop(108);
+    FieldMatcher* predicate_dimension = simplePredicate->mutable_dimensions();
+    predicate_dimension1->set_field(WAKE_LOCK_TAG_ID);
+    predicate_dimension->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+    predicate_dimension->add_child()->set_field(WAKE_LOCK_NAME_KEY);
     simplePredicate->set_count_nesting(true);
 
     predicate = config.add_predicate();
-    predicate->set_name("WL_HELD_PER_APP");
+    predicate->set_id(206);  // "WL_HELD_PER_APP"
     simplePredicate = predicate->mutable_simple_predicate();
-    simplePredicate->set_start("APP_GET_WL");
-    simplePredicate->set_stop("APP_RELEASE_WL");
+    simplePredicate->set_start(107);
+    simplePredicate->set_stop(108);
     simplePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
-    predicate_dimension = simplePredicate->add_dimension();
-    predicate_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
+    predicate_dimension = simplePredicate->mutable_dimensions();
+    predicate_dimension->set_field(WAKE_LOCK_TAG_ID);
+    predicate_dimension->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
     simplePredicate->set_count_nesting(true);
 
     return config;
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index ea42a35..ad666bc 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -48,6 +48,11 @@
      */
     void Startup();
 
+    /*
+     * Dummy initializer for tests.
+     */
+    void StartupForTest();
+
     /**
      * Someone else wants to know about the configs.
      */
diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp
new file mode 100644
index 0000000..45b3586
--- /dev/null
+++ b/cmds/statsd/src/dimension.cpp
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/statsd_config.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_internal.pb.h"
+#include "dimension.h"
+#include "field_util.h"
+
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const DimensionsValue* getSingleLeafValue(const DimensionsValue* value) {
+    if (value->value_case() == DimensionsValue::ValueCase::kValueTuple) {
+        return getSingleLeafValue(&value->value_tuple().dimensions_value(0));
+    } else {
+        return value;
+    }
+}
+
+DimensionsValue getSingleLeafValue(const DimensionsValue& value) {
+    const DimensionsValue* leafValue = getSingleLeafValue(&value);
+    return *leafValue;
+}
+
+void appendLeafNodeToParent(const Field& field,
+                            const DimensionsValue& value,
+                            DimensionsValue* parentValue) {
+    if (field.child_size() <= 0) {
+        *parentValue = value;
+        parentValue->set_field(field.field());
+        return;
+    }
+    parentValue->set_field(field.field());
+    int idx = -1;
+    for (int i = 0; i < parentValue->mutable_value_tuple()->dimensions_value_size(); ++i) {
+        if (parentValue->mutable_value_tuple()->dimensions_value(i).field() ==
+                field.child(0).field()) {
+            idx = i;
+        }
+    }
+    if (idx < 0) {
+        parentValue->mutable_value_tuple()->add_dimensions_value();
+        idx = parentValue->mutable_value_tuple()->dimensions_value_size() - 1;
+    }
+    appendLeafNodeToParent(
+        field.child(0), value,
+        parentValue->mutable_value_tuple()->mutable_dimensions_value(idx));
+}
+
+void addNodeToRootDimensionsValues(const Field& field,
+                                   const DimensionsValue& node,
+                                   std::vector<DimensionsValue>* rootValues) {
+    if (rootValues == nullptr) {
+        return;
+    }
+    if (rootValues->empty()) {
+        DimensionsValue rootValue;
+        appendLeafNodeToParent(field, node, &rootValue);
+        rootValues->push_back(rootValue);
+    } else {
+        for (size_t i = 0; i < rootValues->size(); ++i) {
+            appendLeafNodeToParent(field, node, &rootValues->at(i));
+        }
+    }
+}
+
+namespace {
+
+void findDimensionsValues(
+       const FieldValueMap& fieldValueMap,
+       const FieldMatcher& matcher,
+       const Field& field,
+       std::vector<DimensionsValue>* rootDimensionsValues);
+
+void findNonRepeatedDimensionsValues(
+       const FieldValueMap& fieldValueMap,
+       const FieldMatcher& matcher,
+       const Field& field,
+       std::vector<DimensionsValue>* rootValues) {
+    if (matcher.child_size() > 0) {
+        for (const auto& childMatcher : matcher.child()) {
+          Field childField = field;
+          appendLeaf(&childField, childMatcher.field());
+          findDimensionsValues(fieldValueMap, childMatcher, childField, rootValues);
+        }
+    } else {
+        auto ret = fieldValueMap.equal_range(field);
+        int found = 0;
+        for (auto it = ret.first; it != ret.second; ++it) {
+            found++;
+        }
+        // Not found.
+        if (found <= 0) {
+            return;
+        }
+        if (found > 1) {
+            ALOGE("Found multiple values for optional field.");
+            return;
+        }
+        addNodeToRootDimensionsValues(field, ret.first->second, rootValues);
+    }
+}
+
+void findRepeatedDimensionsValues(const FieldValueMap& fieldValueMap,
+                                  const FieldMatcher& matcher,
+                                  const Field& field,
+                                  std::vector<DimensionsValue>* rootValues) {
+    if (matcher.position() == Position::FIRST) {
+        Field first_field = field;
+        setPositionForLeaf(&first_field, 0);
+        findNonRepeatedDimensionsValues(fieldValueMap, matcher, first_field, rootValues);
+    } else {
+        auto itLower = fieldValueMap.lower_bound(field);
+        if (itLower == fieldValueMap.end()) {
+            return;
+        }
+        Field next_field = field;
+        getNextField(&next_field);
+        auto itUpper = fieldValueMap.lower_bound(next_field);
+
+        switch (matcher.position()) {
+             case Position::LAST:
+                 {
+                     itUpper--;
+                     if (itUpper != fieldValueMap.end()) {
+                         Field last_field = field;
+                         int last_index = getPositionByReferenceField(field, itUpper->first);
+                         if (last_index < 0) {
+                            return;
+                         }
+                         setPositionForLeaf(&last_field, last_index);
+                         findNonRepeatedDimensionsValues(
+                            fieldValueMap, matcher, last_field, rootValues);
+                     }
+                 }
+                 break;
+             case Position::ANY:
+                 {
+                    std::set<int> indexes;
+                    for (auto it = itLower; it != itUpper; ++it) {
+                        int index = getPositionByReferenceField(field, it->first);
+                        if (index >= 0) {
+                            indexes.insert(index);
+                        }
+                    }
+                    if (!indexes.empty()) {
+                        Field any_field = field;
+                        std::vector<DimensionsValue> allValues;
+                        for (const int index : indexes) {
+                             setPositionForLeaf(&any_field, index);
+                             std::vector<DimensionsValue> newValues = *rootValues;
+                             findNonRepeatedDimensionsValues(
+                                fieldValueMap, matcher, any_field, &newValues);
+                             allValues.insert(allValues.end(), newValues.begin(), newValues.end());
+                        }
+                        rootValues->clear();
+                        rootValues->insert(rootValues->end(), allValues.begin(), allValues.end());
+                    }
+                 }
+                 break;
+             default:
+                break;
+         }
+    }
+}
+
+void findDimensionsValues(
+       const FieldValueMap& fieldValueMap,
+       const FieldMatcher& matcher,
+       const Field& field,
+       std::vector<DimensionsValue>* rootDimensionsValues) {
+    if (!matcher.has_position()) {
+        findNonRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues);
+    } else {
+        findRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues);
+    }
+}
+
+} // namespace
+
+void findDimensionsValues(
+       const FieldValueMap& fieldValueMap,
+       const FieldMatcher& matcher,
+       std::vector<DimensionsValue>* rootDimensionsValues) {
+    findDimensionsValues(fieldValueMap, matcher,
+                    buildSimpleAtomField(matcher.field()), rootDimensionsValues);
+}
+
+FieldMatcher buildSimpleAtomFieldMatcher(const int tagId) {
+    FieldMatcher matcher;
+    matcher.set_field(tagId);
+    return matcher;
+}
+
+FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum) {
+    FieldMatcher matcher;
+    matcher.set_field(tagId);
+    matcher.add_child()->set_field(atomFieldNum);
+    return matcher;
+}
+
+constexpr int ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO = 1;
+constexpr int UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 1;
+constexpr int TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 2;
+
+void buildAttributionUidFieldMatcher(const int tagId, const Position position,
+                                     FieldMatcher *matcher) {
+    matcher->set_field(tagId);
+    matcher->add_child()->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO);
+    FieldMatcher* child = matcher->mutable_child(0)->add_child();
+    child->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO);
+}
+
+void buildAttributionTagFieldMatcher(const int tagId, const Position position,
+                                     FieldMatcher *matcher) {
+    matcher->set_field(tagId);
+    FieldMatcher* child = matcher->add_child();
+    child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO);
+    child->set_position(position);
+    child = child->add_child();
+    child->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO);
+}
+
+void buildAttributionFieldMatcher(const int tagId, const Position position,
+                                  FieldMatcher *matcher) {
+    matcher->set_field(tagId);
+    FieldMatcher* child = matcher->add_child();
+    child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO);
+    child->set_position(position);
+    child = child->add_child();
+    child->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO);
+    child->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO);
+}
+
+void DimensionsValueToString(const DimensionsValue& value, std::string *flattened) {
+    *flattened += std::to_string(value.field());
+    *flattened += ":";
+    switch (value.value_case()) {
+        case DimensionsValue::ValueCase::kValueStr:
+            *flattened += value.value_str();
+            break;
+        case DimensionsValue::ValueCase::kValueInt:
+            *flattened += std::to_string(value.value_int());
+            break;
+        case DimensionsValue::ValueCase::kValueLong:
+            *flattened += std::to_string(value.value_long());
+            break;
+        case DimensionsValue::ValueCase::kValueBool:
+            *flattened += std::to_string(value.value_bool());
+            break;
+        case DimensionsValue::ValueCase::kValueFloat:
+            *flattened += std::to_string(value.value_float());
+            break;
+        case DimensionsValue::ValueCase::kValueTuple:
+            {
+                *flattened += "{";
+                for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) {
+                    DimensionsValueToString(value.value_tuple().dimensions_value(i), flattened);
+                    *flattened += "|";
+                }
+                *flattened += "}";
+            }
+            break;
+        case DimensionsValue::ValueCase::VALUE_NOT_SET:
+            break;
+    }
+}
+
+void getDimensionsValueLeafNodes(
+    const DimensionsValue& value, std::vector<DimensionsValue> *leafNodes) {
+    switch (value.value_case()) {
+        case DimensionsValue::ValueCase::kValueStr:
+        case DimensionsValue::ValueCase::kValueInt:
+        case DimensionsValue::ValueCase::kValueLong:
+        case DimensionsValue::ValueCase::kValueBool:
+        case DimensionsValue::ValueCase::kValueFloat:
+            leafNodes->push_back(value);
+            break;
+        case DimensionsValue::ValueCase::kValueTuple:
+            for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) {
+                getDimensionsValueLeafNodes(value.value_tuple().dimensions_value(i), leafNodes);
+            }
+            break;
+        case DimensionsValue::ValueCase::VALUE_NOT_SET:
+            break;
+        default:
+            break;
+    }
+}
+
+std::string DimensionsValueToString(const DimensionsValue& value) {
+    std::string flatten;
+    DimensionsValueToString(value, &flatten);
+    return flatten;
+}
+
+bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub) {
+    if (dimension.field() != sub.field()) {
+        return false;
+    }
+    if (dimension.value_case() != sub.value_case()) {
+        return false;
+    }
+    switch (dimension.value_case()) {
+        case DimensionsValue::ValueCase::kValueStr:
+            return dimension.value_str() == sub.value_str();
+        case DimensionsValue::ValueCase::kValueInt:
+            return dimension.value_int() == sub.value_int();
+        case DimensionsValue::ValueCase::kValueLong:
+            return dimension.value_long() == sub.value_long();
+        case DimensionsValue::ValueCase::kValueBool:
+            return dimension.value_bool() == sub.value_bool();
+        case DimensionsValue::ValueCase::kValueFloat:
+            return dimension.value_float() == sub.value_float();
+        case DimensionsValue::ValueCase::kValueTuple: {
+            if (dimension.value_tuple().dimensions_value_size() < sub.value_tuple().dimensions_value_size()) {
+                return false;
+            }
+            bool allSub = true;
+            for (int i = 0; i < sub.value_tuple().dimensions_value_size(); ++i) {
+                bool isSub = false;
+                for (int j = 0; !isSub && j < dimension.value_tuple().dimensions_value_size(); ++j) {
+                    isSub |= IsSubDimension(dimension.value_tuple().dimensions_value(j),
+                                            sub.value_tuple().dimensions_value(i));
+                }
+                allSub &= isSub;
+            }
+            return allSub;
+        }
+        break;
+        case DimensionsValue::ValueCase::VALUE_NOT_SET:
+            return false;
+        default:
+            return false;
+    }
+}
+
+long getLongFromDimenValue(const DimensionsValue& dimensionValue) {
+    switch (dimensionValue.value_case()) {
+        case DimensionsValue::ValueCase::kValueInt:
+            return dimensionValue.value_int();
+        case DimensionsValue::ValueCase::kValueLong:
+            return dimensionValue.value_long();
+        case DimensionsValue::ValueCase::kValueBool:
+            return dimensionValue.value_bool() ? 1 : 0;
+        case DimensionsValue::ValueCase::kValueFloat:
+            return (int64_t)dimensionValue.value_float();
+        case DimensionsValue::ValueCase::kValueTuple:
+        case DimensionsValue::ValueCase::kValueStr:
+        case DimensionsValue::ValueCase::VALUE_NOT_SET:
+            return 0;
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h
new file mode 100644
index 0000000..5bb64a9
--- /dev/null
+++ b/cmds/statsd/src/dimension.h
@@ -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.
+ */
+
+#pragma once
+
+#include <log/logprint.h>
+#include <set>
+#include <vector>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "field_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+
+// Returns the leaf node from the DimensionsValue proto. It assume that the input has only one
+// leaf node at most.
+const DimensionsValue* getSingleLeafValue(const DimensionsValue* value);
+DimensionsValue getSingleLeafValue(const DimensionsValue& value);
+
+// Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto
+// represents a tree. When the input proto has repeated fields and the input "dimensions" wants
+// "ANY" locations, it will return multiple trees.
+void findDimensionsValues(
+       const FieldValueMap& fieldValueMap,
+       const FieldMatcher& matcher,
+       std::vector<DimensionsValue>* rootDimensionsValues);
+
+// Utils to build FieldMatcher proto for simple one-depth atoms.
+FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum);
+FieldMatcher buildSimpleAtomFieldMatcher(const int tagId);
+
+// Utils to build FieldMatcher proto for attribution nodes.
+FieldMatcher buildAttributionUidFieldMatcher(const int tagId, const Position position);
+FieldMatcher buildAttributionTagFieldMatcher(const int tagId, const Position position);
+FieldMatcher buildAttributionFieldMatcher(const int tagId, const Position position);
+
+// Utils to print pretty string for DimensionsValue proto.
+std::string DimensionsValueToString(const DimensionsValue& value);
+void DimensionsValueToString(const DimensionsValue& value, std::string *flattened);
+
+bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub);
+
+// Helper function to get long value from the DimensionsValue proto.
+long getLongFromDimenValue(const DimensionsValue& dimensionValue);
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
index 9738760..a61afb4 100644
--- a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
+++ b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
@@ -20,6 +20,9 @@
 #include <fstream>
 #include "external/CpuTimePerUidFreqPuller.h"
 
+#include "../guardrail/StatsdStats.h"
+#include "CpuTimePerUidFreqPuller.h"
+#include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
 #include "statslog.h"
 
@@ -45,43 +48,47 @@
  * This provides the times a UID's processes spent executing at each different cpu frequency.
  * The file contains a monotonically increasing count of time for a single boot.
  */
-bool CpuTimePerUidFreqPuller::Pull(const int tagId, vector<shared_ptr<LogEvent>>* data) {
-  data->clear();
+CpuTimePerUidFreqPuller::CpuTimePerUidFreqPuller()
+    : StatsPuller(android::util::CPU_TIME_PER_UID_FREQ) {
+}
 
-  ifstream fin;
-  fin.open(sProcFile);
-  if (!fin.good()) {
-    VLOG("Failed to read pseudo file %s", sProcFile.c_str());
-    return false;
-  }
+bool CpuTimePerUidFreqPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
+    data->clear();
 
-  uint64_t timestamp = time(nullptr) * NS_PER_SEC;
-  char buf[kLineBufferSize];
-  // first line prints the format and frequencies
-  fin.getline(buf, kLineBufferSize);
-  char * pch;
-  while(!fin.eof()){
+    ifstream fin;
+    fin.open(sProcFile);
+    if (!fin.good()) {
+        VLOG("Failed to read pseudo file %s", sProcFile.c_str());
+        return false;
+    }
+
+    uint64_t timestamp = time(nullptr) * NS_PER_SEC;
+    char buf[kLineBufferSize];
+    // first line prints the format and frequencies
     fin.getline(buf, kLineBufferSize);
-    pch = strtok (buf, " :");
-    if (pch == NULL) break;
-    uint64_t uid = std::stoull(pch);
-    pch = strtok(NULL, " ");
-    uint64_t timeMs;
-    int idx = 0;
-    do {
-      timeMs = std::stoull(pch);
-      auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_FREQ, timestamp);
-      ptr->write(uid);
-      ptr->write(idx);
-      ptr->write(timeMs);
-      ptr->init();
-      data->push_back(ptr);
-      VLOG("uid %lld, freq idx %d, sys time %lld", (long long)uid, idx, (long long)timeMs);
-      idx ++;
-      pch = strtok(NULL, " ");
-    } while (pch != NULL);
-  }
-  return true;
+    char* pch;
+    while (!fin.eof()) {
+        fin.getline(buf, kLineBufferSize);
+        pch = strtok(buf, " :");
+        if (pch == NULL) break;
+        uint64_t uid = std::stoull(pch);
+        pch = strtok(NULL, " ");
+        uint64_t timeMs;
+        int idx = 0;
+        do {
+            timeMs = std::stoull(pch);
+            auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_FREQ, timestamp);
+            ptr->write(uid);
+            ptr->write(idx);
+            ptr->write(timeMs);
+            ptr->init();
+            data->push_back(ptr);
+            VLOG("uid %lld, freq idx %d, sys time %lld", (long long)uid, idx, (long long)timeMs);
+            idx++;
+            pch = strtok(NULL, " ");
+        } while (pch != NULL);
+    }
+    return true;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.h b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.h
index 839e5aa..6f6c669 100644
--- a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.h
+++ b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.h
@@ -33,7 +33,8 @@
  */
 class CpuTimePerUidFreqPuller : public StatsPuller {
  public:
-  bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data) override;
+     CpuTimePerUidFreqPuller();
+     bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
index f69b9b5..e7ea4b9 100644
--- a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
+++ b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
@@ -20,6 +20,8 @@
 #include <fstream>
 #include "external/CpuTimePerUidPuller.h"
 
+#include "CpuTimePerUidPuller.h"
+#include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
 #include "statslog.h"
 
@@ -42,38 +44,42 @@
  * This provides the time a UID's processes spent executing in user-space and kernel-space.
  * The file contains a monotonically increasing count of time for a single boot.
  */
-bool CpuTimePerUidPuller::Pull(const int tagId, vector<shared_ptr<LogEvent>>* data) {
-  data->clear();
+CpuTimePerUidPuller::CpuTimePerUidPuller() : StatsPuller(android::util::CPU_TIME_PER_UID) {
+}
 
-  ifstream fin;
-  fin.open(sProcFile);
-  if (!fin.good()) {
-    VLOG("Failed to read pseudo file %s", sProcFile.c_str());
-    return false;
-  }
+bool CpuTimePerUidPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
+    data->clear();
 
-  uint64_t timestamp = time(nullptr) * NS_PER_SEC;
-  char buf[kLineBufferSize];
-  char * pch;
-  while(!fin.eof()){
-    fin.getline(buf, kLineBufferSize);
-    pch = strtok(buf, " :");
-    if (pch == NULL) break;
-    uint64_t uid = std::stoull(pch);
-    pch = strtok(buf, " ");
-    uint64_t userTimeMs = std::stoull(pch);
-    pch = strtok(buf, " ");
-    uint64_t sysTimeMs = std::stoull(pch);
+    ifstream fin;
+    fin.open(sProcFile);
+    if (!fin.good()) {
+        VLOG("Failed to read pseudo file %s", sProcFile.c_str());
+        return false;
+    }
 
-    auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID, timestamp);
-    ptr->write(uid);
-    ptr->write(userTimeMs);
-    ptr->write(sysTimeMs);
-    ptr->init();
-    data->push_back(ptr);
-    VLOG("uid %lld, user time %lld, sys time %lld", (long long)uid, (long long)userTimeMs, (long long)sysTimeMs);
-  }
-  return true;
+    uint64_t timestamp = time(nullptr) * NS_PER_SEC;
+    char buf[kLineBufferSize];
+    char* pch;
+    while (!fin.eof()) {
+        fin.getline(buf, kLineBufferSize);
+        pch = strtok(buf, " :");
+        if (pch == NULL) break;
+        uint64_t uid = std::stoull(pch);
+        pch = strtok(buf, " ");
+        uint64_t userTimeMs = std::stoull(pch);
+        pch = strtok(buf, " ");
+        uint64_t sysTimeMs = std::stoull(pch);
+
+        auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID, timestamp);
+        ptr->write(uid);
+        ptr->write(userTimeMs);
+        ptr->write(sysTimeMs);
+        ptr->init();
+        data->push_back(ptr);
+        VLOG("uid %lld, user time %lld, sys time %lld", (long long)uid, (long long)userTimeMs,
+             (long long)sysTimeMs);
+    }
+    return true;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/CpuTimePerUidPuller.h b/cmds/statsd/src/external/CpuTimePerUidPuller.h
index 9bb8946..d0d39d0 100644
--- a/cmds/statsd/src/external/CpuTimePerUidPuller.h
+++ b/cmds/statsd/src/external/CpuTimePerUidPuller.h
@@ -33,7 +33,8 @@
  */
 class CpuTimePerUidPuller : public StatsPuller {
  public:
-  bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data) override;
+     CpuTimePerUidPuller();
+     bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
index cb9f1cc..2e29fb0 100644
--- a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
+++ b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
@@ -33,6 +33,7 @@
 #include "external/ResourcePowerManagerPuller.h"
 #include "external/StatsPuller.h"
 
+#include "ResourcePowerManagerPuller.h"
 #include "logd/LogEvent.h"
 #include "statslog.h"
 
@@ -72,7 +73,10 @@
     return gPowerHalV1_0 != nullptr;
 }
 
-bool ResourcePowerManagerPuller::Pull(const int tagId, vector<shared_ptr<LogEvent>>* data) {
+ResourcePowerManagerPuller::ResourcePowerManagerPuller(int tagId) : StatsPuller(tagId) {
+}
+
+bool ResourcePowerManagerPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
     std::lock_guard<std::mutex> lock(gPowerHalMutex);
 
     if (!getPowerHal()) {
@@ -83,79 +87,87 @@
     uint64_t timestamp = time(nullptr) * NS_PER_SEC;
 
     data->clear();
-    Return<void> ret = gPowerHalV1_0->getPlatformLowPowerStats(
-            [&data, timestamp](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
 
-                if (status != Status::SUCCESS) return;
-
-                for (size_t i = 0; i < states.size(); i++) {
-                    const PowerStatePlatformSleepState& state = states[i];
-
-                    auto statePtr = make_shared<LogEvent>(
-                            android::util::PLATFORM_SLEEP_STATE, timestamp);
-                    statePtr->write(state.name);
-                    statePtr->write(state.residencyInMsecSinceBoot);
-                    statePtr->write(state.totalTransitions);
-                    statePtr->write(state.supportedOnlyInSuspend);
-                    statePtr->init();
-                    data->push_back(statePtr);
-                    VLOG("powerstate: %s, %lld, %lld, %d", state.name.c_str(),
-                         (long long)state.residencyInMsecSinceBoot,
-                         (long long)state.totalTransitions, state.supportedOnlyInSuspend ? 1 : 0);
-                    for (auto voter : state.voters) {
-                        auto voterPtr =
-                                make_shared<LogEvent>(android::util::SLEEP_STATE_VOTER, timestamp);
-                        voterPtr->write(state.name);
-                        voterPtr->write(voter.name);
-                        voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot);
-                        voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot);
-                        voterPtr->init();
-                        data->push_back(voterPtr);
-                        VLOG("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(),
-                             voter.name.c_str(), (long long)voter.totalTimeInMsecVotedForSinceBoot,
-                             (long long)voter.totalNumberOfTimesVotedSinceBoot);
-                    }
-                }
-            });
-    if (!ret.isOk()) {
-        ALOGE("getLowPowerStats() failed: power HAL service not available");
-        gPowerHalV1_0 = nullptr;
-        return false;
-    }
-
-    // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1
-    sp<android::hardware::power::V1_1::IPower> gPowerHal_1_1 =
-            android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
-    if (gPowerHal_1_1 != nullptr) {
-        ret = gPowerHal_1_1->getSubsystemLowPowerStats(
-                [&data, timestamp](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
-
+    Return<void> ret;
+    if (mTagId == android::util::PLATFORM_SLEEP_STATE ||
+        mTagId == android::util::SLEEP_STATE_VOTER) {
+        ret = gPowerHalV1_0->getPlatformLowPowerStats(
+                [&data, timestamp](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
                     if (status != Status::SUCCESS) return;
 
-                    if (subsystems.size() > 0) {
-                        for (size_t i = 0; i < subsystems.size(); i++) {
-                            const PowerStateSubsystem& subsystem = subsystems[i];
-                            for (size_t j = 0; j < subsystem.states.size(); j++) {
-                                const PowerStateSubsystemSleepState& state = subsystem.states[j];
-                                auto subsystemStatePtr = make_shared<LogEvent>(
-                                        android::util::SUBSYSTEM_SLEEP_STATE, timestamp);
-                                subsystemStatePtr->write(subsystem.name);
-                                subsystemStatePtr->write(state.name);
-                                subsystemStatePtr->write(state.residencyInMsecSinceBoot);
-                                subsystemStatePtr->write(state.totalTransitions);
-                                subsystemStatePtr->write(state.lastEntryTimestampMs);
-                                subsystemStatePtr->write(state.supportedOnlyInSuspend);
-                                subsystemStatePtr->init();
-                                data->push_back(subsystemStatePtr);
-                                VLOG("subsystemstate: %s, %s, %lld, %lld, %lld",
-                                     subsystem.name.c_str(), state.name.c_str(),
-                                     (long long)state.residencyInMsecSinceBoot,
-                                     (long long)state.totalTransitions,
-                                     (long long)state.lastEntryTimestampMs);
-                            }
+                    for (size_t i = 0; i < states.size(); i++) {
+                        const PowerStatePlatformSleepState& state = states[i];
+
+                        auto statePtr = make_shared<LogEvent>(android::util::PLATFORM_SLEEP_STATE,
+                                                              timestamp);
+                        statePtr->write(state.name);
+                        statePtr->write(state.residencyInMsecSinceBoot);
+                        statePtr->write(state.totalTransitions);
+                        statePtr->write(state.supportedOnlyInSuspend);
+                        statePtr->init();
+                        data->push_back(statePtr);
+                        VLOG("powerstate: %s, %lld, %lld, %d", state.name.c_str(),
+                             (long long)state.residencyInMsecSinceBoot,
+                             (long long)state.totalTransitions,
+                             state.supportedOnlyInSuspend ? 1 : 0);
+                        for (auto voter : state.voters) {
+                            auto voterPtr = make_shared<LogEvent>(android::util::SLEEP_STATE_VOTER,
+                                                                  timestamp);
+                            voterPtr->write(state.name);
+                            voterPtr->write(voter.name);
+                            voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot);
+                            voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot);
+                            voterPtr->init();
+                            data->push_back(voterPtr);
+                            VLOG("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(),
+                                 voter.name.c_str(),
+                                 (long long)voter.totalTimeInMsecVotedForSinceBoot,
+                                 (long long)voter.totalNumberOfTimesVotedSinceBoot);
                         }
                     }
                 });
+        if (!ret.isOk()) {
+            ALOGE("getLowPowerStats() failed: power HAL service not available");
+            gPowerHalV1_0 = nullptr;
+            return false;
+        }
+    }
+
+    if (mTagId == android::util::SUBSYSTEM_SLEEP_STATE) {
+        // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1
+        sp<android::hardware::power::V1_1::IPower> gPowerHal_1_1 =
+                android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
+        if (gPowerHal_1_1 != nullptr) {
+            ret = gPowerHal_1_1->getSubsystemLowPowerStats(
+                    [&data, timestamp](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
+                        if (status != Status::SUCCESS) return;
+
+                        if (subsystems.size() > 0) {
+                            for (size_t i = 0; i < subsystems.size(); i++) {
+                                const PowerStateSubsystem& subsystem = subsystems[i];
+                                for (size_t j = 0; j < subsystem.states.size(); j++) {
+                                    const PowerStateSubsystemSleepState& state =
+                                            subsystem.states[j];
+                                    auto subsystemStatePtr = make_shared<LogEvent>(
+                                            android::util::SUBSYSTEM_SLEEP_STATE, timestamp);
+                                    subsystemStatePtr->write(subsystem.name);
+                                    subsystemStatePtr->write(state.name);
+                                    subsystemStatePtr->write(state.residencyInMsecSinceBoot);
+                                    subsystemStatePtr->write(state.totalTransitions);
+                                    subsystemStatePtr->write(state.lastEntryTimestampMs);
+                                    subsystemStatePtr->write(state.supportedOnlyInSuspend);
+                                    subsystemStatePtr->init();
+                                    data->push_back(subsystemStatePtr);
+                                    VLOG("subsystemstate: %s, %s, %lld, %lld, %lld",
+                                         subsystem.name.c_str(), state.name.c_str(),
+                                         (long long)state.residencyInMsecSinceBoot,
+                                         (long long)state.totalTransitions,
+                                         (long long)state.lastEntryTimestampMs);
+                                }
+                            }
+                        }
+                    });
+        }
     }
     return true;
 }
diff --git a/cmds/statsd/src/external/ResourcePowerManagerPuller.h b/cmds/statsd/src/external/ResourcePowerManagerPuller.h
index c396c12..3399408 100644
--- a/cmds/statsd/src/external/ResourcePowerManagerPuller.h
+++ b/cmds/statsd/src/external/ResourcePowerManagerPuller.h
@@ -28,7 +28,8 @@
  */
 class ResourcePowerManagerPuller : public StatsPuller {
 public:
-    bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data) override;
+    ResourcePowerManagerPuller(int tagId);
+    bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp b/cmds/statsd/src/external/StatsCompanionServicePuller.cpp
index ffe1be9..b955f1c 100644
--- a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp
+++ b/cmds/statsd/src/external/StatsCompanionServicePuller.cpp
@@ -22,6 +22,7 @@
 #include <private/android_filesystem_config.h>
 #include "StatsCompanionServicePuller.h"
 #include "StatsService.h"
+#include "guardrail/StatsdStats.h"
 
 using namespace android;
 using namespace android::base;
@@ -39,13 +40,16 @@
 
 // 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.
-bool StatsCompanionServicePuller::Pull(const int tagId, vector<shared_ptr<LogEvent> >* data) {
+StatsCompanionServicePuller::StatsCompanionServicePuller(int tagId) : StatsPuller(tagId) {
+}
+
+bool StatsCompanionServicePuller::PullInternal(vector<shared_ptr<LogEvent> >* data) {
     sp<IStatsCompanionService> statsCompanion = StatsService::getStatsCompanionService();
     vector<StatsLogEventWrapper> returned_value;
     if (statsCompanion != NULL) {
-        Status status = statsCompanion->pullData(tagId, &returned_value);
+        Status status = statsCompanion->pullData(mTagId, &returned_value);
         if (!status.isOk()) {
-            ALOGW("error pulling for %d", tagId);
+            ALOGW("error pulling for %d", mTagId);
             return false;
         }
         data->clear();
@@ -60,7 +64,7 @@
             std::copy(it.bytes.begin(), it.bytes.end(), tmp.buf + kLogMsgHeaderSize);
             data->push_back(make_shared<LogEvent>(tmp));
         }
-        ALOGD("StatsCompanionServicePuller::pull succeeded for %d", tagId);
+        ALOGD("StatsCompanionServicePuller::pull succeeded for %d", mTagId);
         return true;
     } else {
         ALOGW("statsCompanion not found!");
diff --git a/cmds/statsd/src/external/StatsCompanionServicePuller.h b/cmds/statsd/src/external/StatsCompanionServicePuller.h
index 3ff2274..4c91f31 100644
--- a/cmds/statsd/src/external/StatsCompanionServicePuller.h
+++ b/cmds/statsd/src/external/StatsCompanionServicePuller.h
@@ -25,7 +25,8 @@
 
 class StatsCompanionServicePuller : public StatsPuller {
 public:
-    bool Pull(const int tagId, vector<std::shared_ptr<LogEvent> >* data) override;
+    StatsCompanionServicePuller(int tagId);
+    bool PullInternal(vector<std::shared_ptr<LogEvent> >* data) override;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp
new file mode 100644
index 0000000..cadc535
--- /dev/null
+++ b/cmds/statsd/src/external/StatsPuller.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.
+ */
+
+#define DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "StatsPuller.h"
+#include "guardrail/StatsdStats.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::lock_guard;
+
+// ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
+StatsPuller::StatsPuller(const int tagId)
+    : mTagId(tagId) {
+    if (StatsdStats::kPullerCooldownMap.find(tagId) == StatsdStats::kPullerCooldownMap.end()) {
+        mCoolDownSec = StatsdStats::kDefaultPullerCooldown;
+    } else {
+        mCoolDownSec = StatsdStats::kPullerCooldownMap[tagId];
+    }
+    VLOG("Puller for tag %d created. Cooldown set to %ld", mTagId, mCoolDownSec);
+}
+
+bool StatsPuller::Pull(std::vector<std::shared_ptr<LogEvent>>* data) {
+    lock_guard<std::mutex> lock(mLock);
+    StatsdStats::getInstance().notePull(mTagId);
+    long curTime = time(nullptr);
+    if (curTime - mLastPullTimeSec < mCoolDownSec) {
+        (*data) = mCachedData;
+        StatsdStats::getInstance().notePullFromCache(mTagId);
+        return true;
+    }
+    if (mMinPullIntervalSec > curTime - mLastPullTimeSec) {
+        mMinPullIntervalSec = curTime - mLastPullTimeSec;
+        StatsdStats::getInstance().updateMinPullIntervalSec(mTagId, mMinPullIntervalSec);
+    }
+    mCachedData.clear();
+    mLastPullTimeSec = curTime;
+    bool ret = PullInternal(&mCachedData);
+    if (ret) {
+        (*data) = mCachedData;
+    }
+    return ret;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index 940ad9c..47cc9f0 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -18,11 +18,13 @@
 
 #include <android/os/StatsLogEventWrapper.h>
 #include <utils/String16.h>
+#include <mutex>
 #include <vector>
+
 #include "logd/LogEvent.h"
+#include "guardrail/StatsdStats.h"
 
 using android::os::StatsLogEventWrapper;
-using std::vector;
 
 namespace android {
 namespace os {
@@ -30,9 +32,33 @@
 
 class StatsPuller {
 public:
-    virtual ~StatsPuller(){};
+    StatsPuller(const int tagId);
 
-    virtual bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data) = 0;
+    virtual ~StatsPuller() {}
+
+    bool Pull(std::vector<std::shared_ptr<LogEvent>>* data);
+
+protected:
+    // The atom tag id this puller pulls
+    const int mTagId;
+
+private:
+    mutable std::mutex mLock;
+    // Minimum time before this puller does actual pull again.
+    // If a pull request comes before cooldown, a cached version from purevious pull
+    // will be returned.
+    // The actual value should be determined by individual pullers.
+    long mCoolDownSec;
+    // For puller stats
+    long mMinPullIntervalSec = LONG_MAX;
+
+    virtual bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) = 0;
+
+    // Cache of data from last pull. If next request comes before cool down finishes,
+    // cached data will be returned.
+    std::vector<std::shared_ptr<LogEvent>> mCachedData;
+
+    long mLastPullTimeSec;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
index d707f85..58c7b12 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
@@ -45,29 +45,29 @@
 
 StatsPullerManagerImpl::StatsPullerManagerImpl()
     : mCurrentPullingInterval(LONG_MAX) {
-    shared_ptr<StatsPuller> statsCompanionServicePuller = make_shared<StatsCompanionServicePuller>();
-    shared_ptr<StatsPuller> resourcePowerManagerPuller = make_shared<ResourcePowerManagerPuller>();
-    shared_ptr<StatsPuller> cpuTimePerUidPuller = make_shared<CpuTimePerUidPuller>();
-    shared_ptr<StatsPuller> cpuTimePerUidFreqPuller = make_shared<CpuTimePerUidFreqPuller>();
-
     mPullers.insert({android::util::KERNEL_WAKELOCK,
-                     statsCompanionServicePuller});
+                     make_shared<StatsCompanionServicePuller>(android::util::KERNEL_WAKELOCK)});
     mPullers.insert({android::util::WIFI_BYTES_TRANSFER,
-                     statsCompanionServicePuller});
-    mPullers.insert({android::util::MOBILE_BYTES_TRANSFER,
-                     statsCompanionServicePuller});
+                     make_shared<StatsCompanionServicePuller>(android::util::WIFI_BYTES_TRANSFER)});
+    mPullers.insert(
+            {android::util::MOBILE_BYTES_TRANSFER,
+             make_shared<StatsCompanionServicePuller>(android::util::MOBILE_BYTES_TRANSFER)});
     mPullers.insert({android::util::WIFI_BYTES_TRANSFER_BY_FG_BG,
-                     statsCompanionServicePuller});
+                     make_shared<StatsCompanionServicePuller>(
+                             android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)});
     mPullers.insert({android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG,
-                     statsCompanionServicePuller});
+                     make_shared<StatsCompanionServicePuller>(
+                             android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)});
     mPullers.insert({android::util::PLATFORM_SLEEP_STATE,
-                     resourcePowerManagerPuller});
+                     make_shared<ResourcePowerManagerPuller>(android::util::PLATFORM_SLEEP_STATE)});
     mPullers.insert({android::util::SLEEP_STATE_VOTER,
-                     resourcePowerManagerPuller});
-    mPullers.insert({android::util::SUBSYSTEM_SLEEP_STATE,
-                     resourcePowerManagerPuller});
-    mPullers.insert({android::util::CPU_TIME_PER_UID, cpuTimePerUidPuller});
-    mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ, cpuTimePerUidFreqPuller});
+                     make_shared<ResourcePowerManagerPuller>(android::util::SLEEP_STATE_VOTER)});
+    mPullers.insert(
+            {android::util::SUBSYSTEM_SLEEP_STATE,
+             make_shared<ResourcePowerManagerPuller>(android::util::SUBSYSTEM_SLEEP_STATE)});
+    mPullers.insert({android::util::CPU_TIME_PER_FREQ, make_shared<StatsCompanionServicePuller>(android::util::CPU_TIME_PER_FREQ)});
+    mPullers.insert({android::util::CPU_TIME_PER_UID, make_shared<CpuTimePerUidPuller>()});
+    mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ, make_shared<CpuTimePerUidFreqPuller>()});
 
     mStatsCompanionService = StatsService::getStatsCompanionService();
 }
@@ -76,7 +76,7 @@
     if (DEBUG) ALOGD("Initiating pulling %d", tagId);
 
     if (mPullers.find(tagId) != mPullers.end()) {
-        bool ret = mPullers.find(tagId)->second->Pull(tagId, data);
+        bool ret = mPullers.find(tagId)->second->Pull(data);
         ALOGD("pulled %d items", (int)data->size());
         return ret;
     } else {
diff --git a/cmds/statsd/src/field_util.cpp b/cmds/statsd/src/field_util.cpp
new file mode 100644
index 0000000..d10e167
--- /dev/null
+++ b/cmds/statsd/src/field_util.cpp
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "field_util.h"
+
+#include <set>
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// This function is to compare two Field trees where each node has at most one child.
+bool CompareField(const Field& a, const Field& b) {
+    if (a.field() < b.field()) {
+        return true;
+    }
+    if (a.field() > b.field()) {
+        return false;
+    }
+    if (a.position_index() < b.position_index()) {
+        return true;
+    }
+    if (a.position_index() > b.position_index()) {
+        return false;
+    }
+    if (a.child_size() < b.child_size()) {
+        return true;
+    }
+    if (a.child_size() > b.child_size()) {
+        return false;
+    }
+    if (a.child_size() == 0 && b.child_size() == 0) {
+       return false;
+    }
+    return CompareField(a.child(0), b.child(0));
+}
+
+const Field* getSingleLeaf(const Field* field) {
+    if (field->child_size() <= 0) {
+        return field;
+    } else {
+        return getSingleLeaf(&field->child(0));
+    }
+}
+
+Field* getSingleLeaf(Field* field) {
+    if (field->child_size() <= 0) {
+        return field;
+    } else {
+        return getSingleLeaf(field->mutable_child(0));
+    }
+}
+
+void FieldToString(const Field& field, std::string *flattened) {
+    *flattened += std::to_string(field.field());
+    if (field.has_position_index()) {
+        *flattened += "[";
+        *flattened += std::to_string(field.position_index());
+        *flattened += "]";
+    }
+    if (field.child_size() <= 0) {
+        return;
+    }
+    *flattened += ".";
+    *flattened += "{";
+    for (int i = 0 ; i < field.child_size(); ++i) {
+        *flattened += FieldToString(field.child(i));
+    }
+    *flattened += "},";
+}
+
+std::string FieldToString(const Field& field) {
+    std::string flatten;
+    FieldToString(field, &flatten);
+    return flatten;
+}
+
+bool setFieldInLeafValueProto(const Field &field, DimensionsValue* leafValue) {
+    if (field.child_size() <= 0) {
+        leafValue->set_field(field.field());
+        return true;
+    } else if (field.child_size() == 1)  {
+        return setFieldInLeafValueProto(field.child(0), leafValue);
+    } else {
+        ALOGE("Not able to set the 'field' in leaf value for multiple children.");
+        return false;
+    }
+}
+
+Field buildAtomField(const int tagId, const Field &atomField) {
+    Field field;
+    *field.add_child() = atomField;
+    field.set_field(tagId);
+    return field;
+}
+
+Field buildSimpleAtomField(const int tagId, const int atomFieldNum) {
+    Field field;
+    field.set_field(tagId);
+    field.add_child()->set_field(atomFieldNum);
+    return field;
+}
+
+Field buildSimpleAtomField(const int tagId) {
+    Field field;
+    field.set_field(tagId);
+    return field;
+}
+
+void appendLeaf(Field *parent, int node_field_num) {
+    if (!parent->has_field()) {
+        parent->set_field(node_field_num);
+    } else if (parent->child_size() <= 0) {
+        parent->add_child()->set_field(node_field_num);
+    } else {
+        appendLeaf(parent->mutable_child(0), node_field_num);
+    }
+}
+
+void appendLeaf(Field *parent, int node_field_num, int position) {
+    if (!parent->has_field()) {
+        parent->set_field(node_field_num);
+        parent->set_position_index(position);
+    } else if (parent->child_size() <= 0) {
+        auto child = parent->add_child();
+        child->set_field(node_field_num);
+        child->set_position_index(position);
+    } else {
+        appendLeaf(parent->mutable_child(0), node_field_num, position);
+    }
+}
+
+
+void getNextField(Field* field) {
+    if (field->child_size() <= 0) {
+        field->set_field(field->field() + 1);
+        return;
+    }
+    if (field->child_size() != 1) {
+        return;
+    }
+    getNextField(field->mutable_child(0));
+}
+
+void increasePosition(Field *field) {
+    if (!field->has_position_index()) {
+        field->set_position_index(0);
+    } else {
+        field->set_position_index(field->position_index() + 1);
+    }
+}
+
+int getPositionByReferenceField(const Field& ref, const Field& field_with_index) {
+    if (ref.child_size() <= 0) {
+        return field_with_index.position_index();
+    }
+    if (ref.child_size() != 1 ||
+        field_with_index.child_size() != 1) {
+        return -1;
+    }
+    return getPositionByReferenceField(ref.child(0), field_with_index.child(0));
+}
+
+void setPositionForLeaf(Field *field, int index) {
+    if (field->child_size() <= 0) {
+        field->set_position_index(index);
+    } else {
+        setPositionForLeaf(field->mutable_child(0), index);
+    }
+}
+
+void findFields(
+       const FieldValueMap& fieldValueMap,
+       const FieldMatcher& matcher,
+       const Field& field,
+       std::vector<Field>* rootFields);
+
+void findNonRepeatedFields(
+       const FieldValueMap& fieldValueMap,
+       const FieldMatcher& matcher,
+       const Field& field,
+       std::vector<Field>* rootFields) {
+    if (matcher.child_size() > 0) {
+        for (const auto& childMatcher : matcher.child()) {
+          Field childField = field;
+          appendLeaf(&childField, childMatcher.field());
+          findFields(fieldValueMap, childMatcher, childField, rootFields);
+        }
+    } else {
+        auto ret = fieldValueMap.equal_range(field);
+        int found = 0;
+        for (auto it = ret.first; it != ret.second; ++it) {
+            found++;
+        }
+        // Not found.
+        if (found <= 0) {
+            return;
+        }
+        if (found > 1) {
+            ALOGE("Found multiple values for optional field.");
+            return;
+        }
+        rootFields->push_back(ret.first->first);
+    }
+}
+
+void findRepeatedFields(const FieldValueMap& fieldValueMap, const FieldMatcher& matcher,
+                        const Field& field, std::vector<Field>* rootFields) {
+    if (matcher.position() == Position::FIRST) {
+        Field first_field = field;
+        setPositionForLeaf(&first_field, 0);
+        findNonRepeatedFields(fieldValueMap, matcher, first_field, rootFields);
+    } else {
+        auto itLower = fieldValueMap.lower_bound(field);
+        if (itLower == fieldValueMap.end()) {
+            return;
+        }
+        Field next_field = field;
+        getNextField(&next_field);
+        auto itUpper = fieldValueMap.lower_bound(next_field);
+
+        switch (matcher.position()) {
+             case Position::LAST:
+                 {
+                     itUpper--;
+                     if (itUpper != fieldValueMap.end()) {
+                         Field last_field = field;
+                         int last_index = getPositionByReferenceField(field, itUpper->first);
+                         if (last_index < 0) {
+                            return;
+                         }
+                         setPositionForLeaf(&last_field, last_index);
+                         findNonRepeatedFields(
+                            fieldValueMap, matcher, last_field, rootFields);
+                     }
+                 }
+                 break;
+             case Position::ANY:
+                 {
+                    std::set<int> indexes;
+                    for (auto it = itLower; it != itUpper; ++it) {
+                        int index = getPositionByReferenceField(field, it->first);
+                        if (index >= 0) {
+                            indexes.insert(index);
+                        }
+                    }
+                    if (!indexes.empty()) {
+                        Field any_field = field;
+                        for (const int index : indexes) {
+                             setPositionForLeaf(&any_field, index);
+                             findNonRepeatedFields(
+                                fieldValueMap, matcher, any_field, rootFields);
+                        }
+                    }
+                 }
+                 break;
+             default:
+                break;
+         }
+    }
+}
+
+void findFields(
+       const FieldValueMap& fieldValueMap,
+       const FieldMatcher& matcher,
+       const Field& field,
+       std::vector<Field>* rootFields) {
+    if (!matcher.has_position()) {
+        findNonRepeatedFields(fieldValueMap, matcher, field, rootFields);
+    } else {
+        findRepeatedFields(fieldValueMap, matcher, field, rootFields);
+    }
+}
+
+void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap) {
+    std::vector<Field> rootFields;
+    findFields(*fieldValueMap, matcher, buildSimpleAtomField(matcher.field()), &rootFields);
+    std::set<Field, FieldCmp> rootFieldSet(rootFields.begin(), rootFields.end());
+    auto it = fieldValueMap->begin();
+    while (it != fieldValueMap->end()) {
+        if (rootFieldSet.find(it->first) == rootFieldSet.end()) {
+            it = fieldValueMap->erase(it);
+        } else {
+            it++;
+        }
+    }
+}
+
+bool hasLeafNode(const FieldMatcher& matcher) {
+    if (!matcher.has_field()) {
+        return false;
+    }
+    for (int i = 0; i < matcher.child_size(); ++i) {
+        if (hasLeafNode(matcher.child(i))) {
+            return true;
+        }
+    }
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/field_util.h b/cmds/statsd/src/field_util.h
new file mode 100644
index 0000000..3e7d54c
--- /dev/null
+++ b/cmds/statsd/src/field_util.h
@@ -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.
+ */
+
+#pragma once
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_internal.pb.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Function to sort the Field protos.
+bool CompareField(const Field& a, const Field& b);
+struct FieldCmp {
+    bool operator()(const Field& a, const Field& b) const {
+        return CompareField(a, b);
+    }
+};
+
+// Flattened dimensions value map. To save space, usually the key contains the tree structure info
+// and value field is only leaf node.
+typedef std::map<Field, DimensionsValue, FieldCmp> FieldValueMap;
+
+// Util function to print the Field proto.
+std::string FieldToString(const Field& field);
+
+// Util function to find the leaf node from the input Field proto and set it in the corresponding
+// value proto.
+bool setFieldInLeafValueProto(const Field &field, DimensionsValue* leafValue);
+
+// Returns the leaf node from the Field proto. It assume that the input has only one
+// leaf node at most.
+const Field* getSingleLeaf(const Field* field);
+Field* getSingleLeaf(Field* field);
+
+// Append a node to the current leaf. It assumes that the input "parent" has one leaf node at most.
+void appendLeaf(Field *parent, int node_field_num);
+void appendLeaf(Field *parent, int node_field_num, int position);
+
+// Given the field sorting logic, this function is to increase the "field" at the leaf node.
+void getNextField(Field* field);
+
+// Increase the position index for the node. If the "position_index" is not set, set it as 0.
+void increasePosition(Field *field);
+
+// Finds the leaf node and set the index there.
+void setPositionForLeaf(Field *field, int index);
+
+// Returns true if the matcher has specified at least one leaf node.
+bool hasLeafNode(const FieldMatcher& matcher);
+
+// The two input Field proto are describing the same tree structure. Both contain one leaf node at
+// most. This is find the position index info for the leaf node at "reference" stored in the
+// "field_with_index" tree.
+int getPositionByReferenceField(const Field& reference, const Field& field_with_index);
+
+// Utils to build the Field proto for simple atom fields.
+Field buildAtomField(const int tagId, const Field &atomField);
+Field buildSimpleAtomField(const int tagId, const int atomFieldNum);
+Field buildSimpleAtomField(const int tagId);
+
+// Find out all the fields specified by the matcher.
+void findFields(
+       const FieldValueMap& fieldValueMap,
+       const FieldMatcher& matcher,
+       std::vector<Field>* rootFields);
+
+// Filter out the fields not in the field matcher.
+void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index bf277f0..36dd616 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -19,6 +19,7 @@
 #include "StatsdStats.h"
 
 #include <android/util/ProtoOutputStream.h>
+#include "../stats_log_util.h"
 #include "statslog.h"
 
 namespace android {
@@ -59,6 +60,20 @@
 
 const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1;
 
+std::map<int, long> StatsdStats::kPullerCooldownMap = {
+        {android::util::KERNEL_WAKELOCK, 1},
+        {android::util::WIFI_BYTES_TRANSFER, 1},
+        {android::util::MOBILE_BYTES_TRANSFER, 1},
+        {android::util::WIFI_BYTES_TRANSFER_BY_FG_BG, 1},
+        {android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG, 1},
+        {android::util::PLATFORM_SLEEP_STATE, 1},
+        {android::util::SLEEP_STATE_VOTER, 1},
+        {android::util::SUBSYSTEM_SLEEP_STATE, 1},
+        {android::util::CPU_TIME_PER_FREQ, 1},
+        {android::util::CPU_TIME_PER_UID, 1},
+        {android::util::CPU_TIME_PER_UID_FREQ, 1},
+};
+
 // TODO: add stats for pulled atoms.
 StatsdStats::StatsdStats() {
     mPushedAtomStats.resize(android::util::kMaxPushedAtomId + 1);
@@ -70,6 +85,14 @@
     return statsInstance;
 }
 
+void StatsdStats::addToIceBoxLocked(const StatsdStatsReport_ConfigStats& stats) {
+    // The size of mIceBox grows strictly by one at a time. It won't be > kMaxIceBoxSize.
+    if (mIceBox.size() == kMaxIceBoxSize) {
+        mIceBox.pop_front();
+    }
+    mIceBox.push_back(stats);
+}
+
 void StatsdStats::noteConfigReceived(const ConfigKey& key, int metricsCount, int conditionsCount,
                                      int matchersCount, int alertsCount, bool isValid) {
     lock_guard<std::mutex> lock(mLock);
@@ -80,7 +103,7 @@
 
     StatsdStatsReport_ConfigStats configStats;
     configStats.set_uid(key.GetUid());
-    configStats.set_name(key.GetName());
+    configStats.set_id(key.GetId());
     configStats.set_creation_time_sec(nowTimeSec);
     configStats.set_metric_count(metricsCount);
     configStats.set_condition_count(conditionsCount);
@@ -92,7 +115,7 @@
         mConfigStats[key] = configStats;
     } else {
         configStats.set_deletion_time_sec(nowTimeSec);
-        mIceBox.push_back(configStats);
+        addToIceBoxLocked(configStats);
     }
 }
 
@@ -108,7 +131,7 @@
         mMetricsStats.erase(key);
         mAlertStats.erase(key);
         mConditionStats.erase(key);
-        mIceBox.push_back(it->second);
+        addToIceBoxLocked(it->second);
         mConfigStats.erase(it);
     }
 }
@@ -196,34 +219,34 @@
     mUidMapStats.set_bytes_used(bytes);
 }
 
-void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const string& name, int size) {
+void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size) {
     lock_guard<std::mutex> lock(mLock);
     // if name doesn't exist before, it will create the key with count 0.
     auto& conditionSizeMap = mConditionStats[key];
-    if (size > conditionSizeMap[name]) {
-        conditionSizeMap[name] = size;
+    if (size > conditionSizeMap[id]) {
+        conditionSizeMap[id] = size;
     }
 }
 
-void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const string& name, int size) {
+void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size) {
     lock_guard<std::mutex> lock(mLock);
     // if name doesn't exist before, it will create the key with count 0.
     auto& metricsDimensionMap = mMetricsStats[key];
-    if (size > metricsDimensionMap[name]) {
-        metricsDimensionMap[name] = size;
+    if (size > metricsDimensionMap[id]) {
+        metricsDimensionMap[id] = size;
     }
 }
 
-void StatsdStats::noteMatcherMatched(const ConfigKey& key, const string& name) {
+void StatsdStats::noteMatcherMatched(const ConfigKey& key, const int64_t& id) {
     lock_guard<std::mutex> lock(mLock);
     auto& matcherStats = mMatcherStats[key];
-    matcherStats[name]++;
+    matcherStats[id]++;
 }
 
-void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const string& name) {
+void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const int64_t& id) {
     lock_guard<std::mutex> lock(mLock);
     auto& alertStats = mAlertStats[key];
-    alertStats[name]++;
+    alertStats[id]++;
 }
 
 void StatsdStats::noteRegisteredAnomalyAlarmChanged() {
@@ -231,6 +254,21 @@
     mAnomalyAlarmRegisteredStats++;
 }
 
+void StatsdStats::updateMinPullIntervalSec(int pullAtomId, long intervalSec) {
+    lock_guard<std::mutex> lock(mLock);
+    mPulledAtomStats[pullAtomId].minPullIntervalSec = intervalSec;
+}
+
+void StatsdStats::notePull(int pullAtomId) {
+    lock_guard<std::mutex> lock(mLock);
+    mPulledAtomStats[pullAtomId].totalPull++;
+}
+
+void StatsdStats::notePullFromCache(int pullAtomId) {
+    lock_guard<std::mutex> lock(mLock);
+    mPulledAtomStats[pullAtomId].totalPullFromCache++;
+}
+
 void StatsdStats::noteAtomLogged(int atomId, int32_t timeSec) {
     lock_guard<std::mutex> lock(mLock);
 
@@ -279,9 +317,10 @@
         const auto& matcherStats = mMatcherStats[key];
         for (const auto& stats : matcherStats) {
             auto output = configStats.add_matcher_stats();
-            output->set_name(stats.first);
+            output->set_id(stats.first);
             output->set_matched_times(stats.second);
-            VLOG("matcher %s matched %d times", stats.first.c_str(), stats.second);
+            VLOG("matcher %lld matched %d times",
+                (long long)stats.first, stats.second);
         }
     }
     // Add condition stats
@@ -289,9 +328,10 @@
         const auto& conditionStats = mConditionStats[key];
         for (const auto& stats : conditionStats) {
             auto output = configStats.add_condition_stats();
-            output->set_name(stats.first);
+            output->set_id(stats.first);
             output->set_max_tuple_counts(stats.second);
-            VLOG("condition %s max output tuple size %d", stats.first.c_str(), stats.second);
+            VLOG("condition %lld max output tuple size %d",
+                (long long)stats.first, stats.second);
         }
     }
     // Add metrics stats
@@ -299,9 +339,10 @@
         const auto& conditionStats = mMetricsStats[key];
         for (const auto& stats : conditionStats) {
             auto output = configStats.add_metric_stats();
-            output->set_name(stats.first);
+            output->set_id(stats.first);
             output->set_max_tuple_counts(stats.second);
-            VLOG("metrics %s max output tuple size %d", stats.first.c_str(), stats.second);
+            VLOG("metrics %lld max output tuple size %d",
+                (long long)stats.first, stats.second);
         }
     }
     // Add anomaly detection alert stats
@@ -309,9 +350,9 @@
         const auto& alertStats = mAlertStats[key];
         for (const auto& stats : alertStats) {
             auto output = configStats.add_alert_stats();
-            output->set_name(stats.first);
+            output->set_id(stats.first);
             output->set_alerted_times(stats.second);
-            VLOG("alert %s declared %d times", stats.first.c_str(), stats.second);
+            VLOG("alert %lld declared %d times", (long long)stats.first, stats.second);
         }
     }
 }
@@ -343,9 +384,9 @@
         // in production.
         if (DEBUG) {
             VLOG("*****ICEBOX*****");
-            VLOG("Config {%d-%s}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
+            VLOG("Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
                  "#matcher=%d, #alert=%d,  #valid=%d",
-                 configStats.uid(), configStats.name().c_str(), configStats.creation_time_sec(),
+                 configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(),
                  configStats.deletion_time_sec(), configStats.metric_count(),
                  configStats.condition_count(), configStats.matcher_count(),
                  configStats.alert_count(), configStats.is_valid());
@@ -364,9 +405,9 @@
         auto& configStats = pair.second;
         if (DEBUG) {
             VLOG("********Active Configs***********");
-            VLOG("Config {%d-%s}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
+            VLOG("Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
                  "#matcher=%d, #alert=%d,  #valid=%d",
-                 configStats.uid(), configStats.name().c_str(), configStats.creation_time_sec(),
+                 configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(),
                  configStats.deletion_time_sec(), configStats.metric_count(),
                  configStats.condition_count(), configStats.matcher_count(),
                  configStats.alert_count(), configStats.is_valid());
@@ -398,7 +439,7 @@
         configStats.clear_alert_stats();
     }
 
-    VLOG("********Atom stats***********");
+    VLOG("********Pushed Atom stats***********");
     const size_t atomCounts = mPushedAtomStats.size();
     for (size_t i = 2; i < atomCounts; i++) {
         if (mPushedAtomStats[i] > 0) {
@@ -412,6 +453,12 @@
         }
     }
 
+    VLOG("********Pulled Atom stats***********");
+    for (const auto& pair : mPulledAtomStats) {
+        android::os::statsd::writePullerStatsToStream(pair, &proto);
+        VLOG("Atom %d->%ld, %ld, %ld\n", (int) pair.first, (long) pair.second.totalPull, (long) pair.second.totalPullFromCache, (long) pair.second.minPullIntervalSec);
+    }
+
     if (mAnomalyAlarmRegisteredStats > 0) {
         VLOG("********AnomalyAlarmStats stats***********");
         long long token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ANOMALY_ALARM_STATS);
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index cb868e1f..52ab253 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -17,9 +17,11 @@
 
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "statslog.h"
 
 #include <gtest/gtest_prod.h>
 #include <log/log_time.h>
+#include <list>
 #include <mutex>
 #include <string>
 #include <vector>
@@ -44,6 +46,9 @@
     const static int kMaxMetricCountPerConfig = 300;
     const static int kMaxMatcherCountPerConfig = 500;
 
+    // The max number of old config stats we keep.
+    const static int kMaxIceBoxSize = 20;
+
     const static int kMaxTimestampCount = 20;
 
     const static int kMaxLogSourceCount = 50;
@@ -62,6 +67,13 @@
     /* Min period between two checks of byte size per config key in nanoseconds. */
     static const unsigned long long kMinByteSizeCheckPeriodNs = 10 * NS_PER_SEC;
 
+    // Default minimum interval between pulls for an atom. Pullers can return cached values if
+    // another pull request happens within this interval.
+    static std::map<int, long> kPullerCooldownMap;
+
+    // Default cooldown time for a puller
+    static const long kDefaultPullerCooldown = 1;
+
     /**
      * Report a new config has been received and report the static stats about the config.
      *
@@ -99,10 +111,10 @@
      * count > kDimensionKeySizeSoftLimit.
      *
      * [key]: The config key that this condition belongs to.
-     * [name]: The name of the condition.
+     * [id]: The id of the condition.
      * [size]: The output tuple size.
      */
-    void noteConditionDimensionSize(const ConfigKey& key, const std::string& name, int size);
+    void noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size);
 
     /**
      * Report the size of output tuple of a metric.
@@ -111,26 +123,26 @@
      * count > kDimensionKeySizeSoftLimit.
      *
      * [key]: The config key that this metric belongs to.
-     * [name]: The name of the metric.
+     * [id]: The id of the metric.
      * [size]: The output tuple size.
      */
-    void noteMetricDimensionSize(const ConfigKey& key, const std::string& name, int size);
+    void noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size);
 
     /**
      * Report a matcher has been matched.
      *
      * [key]: The config key that this matcher belongs to.
-     * [name]: The name of the matcher.
+     * [id]: The id of the matcher.
      */
-    void noteMatcherMatched(const ConfigKey& key, const std::string& name);
+    void noteMatcherMatched(const ConfigKey& key, const int64_t& id);
 
     /**
      * Report that an anomaly detection alert has been declared.
      *
      * [key]: The config key that this alert belongs to.
-     * [name]: The name of the alert.
+     * [id]: The id of the alert.
      */
-    void noteAnomalyDeclared(const ConfigKey& key, const std::string& name);
+    void noteAnomalyDeclared(const ConfigKey& key, const int64_t& id);
 
     /**
      * Report an atom event has been logged.
@@ -154,6 +166,15 @@
     void setUidMapChanges(int changes);
     void setCurrentUidMapMemory(int bytes);
 
+    // Update minimum interval between pulls for an pulled atom
+    void updateMinPullIntervalSec(int pullAtomId, long intervalSec);
+
+    // Notify pull request for an atom
+    void notePull(int pullAtomId);
+
+    // Notify pull request for an atom served from cached data
+    void notePullFromCache(int pullAtomId);
+
     /**
      * Reset the historical stats. Including all stats in icebox, and the tracked stats about
      * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue
@@ -168,6 +189,12 @@
      */
     void dumpStats(std::vector<uint8_t>* buffer, bool reset);
 
+    typedef struct {
+        long totalPull;
+        long totalPullFromCache;
+        long minPullIntervalSec;
+    } PulledAtomStats;
+
 private:
     StatsdStats();
 
@@ -179,20 +206,22 @@
     StatsdStatsReport_UidMapStats mUidMapStats;
 
     // The stats about the configs that are still in use.
+    // The map size is capped by kMaxConfigCount.
     std::map<const ConfigKey, StatsdStatsReport_ConfigStats> mConfigStats;
 
     // Stores the stats for the configs that are no longer in use.
-    std::vector<const StatsdStatsReport_ConfigStats> mIceBox;
+    // The size of the vector is capped by kMaxIceBoxSize.
+    std::list<const StatsdStatsReport_ConfigStats> mIceBox;
 
     // Stores the number of output tuple of condition trackers when it's bigger than
     // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1,
-    // it means some data has been dropped.
-    std::map<const ConfigKey, std::map<const std::string, int>> mConditionStats;
+    // it means some data has been dropped. The map size is capped by kMaxConfigCount.
+    std::map<const ConfigKey, std::map<const int64_t, int>> mConditionStats;
 
     // Stores the number of output tuple of metric producers when it's bigger than
     // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1,
-    // it means some data has been dropped.
-    std::map<const ConfigKey, std::map<const std::string, int>> mMetricsStats;
+    // it means some data has been dropped. The map size is capped by kMaxConfigCount.
+    std::map<const ConfigKey, std::map<const int64_t, int>> mMetricsStats;
 
     // Stores the number of times a pushed atom is logged.
     // The size of the vector is the largest pushed atom id in atoms.proto + 1. Atoms
@@ -200,16 +229,19 @@
     // This is a vector, not a map because it will be accessed A LOT -- for each stats log.
     std::vector<int> mPushedAtomStats;
 
+    // Maps PullAtomId to its stats. The size is capped by the puller atom counts.
+    std::map<int, PulledAtomStats> mPulledAtomStats;
+
     // Stores the number of times statsd modified the anomaly alarm registered with
     // StatsCompanionService.
     int mAnomalyAlarmRegisteredStats = 0;
 
     // Stores the number of times an anomaly detection alert has been declared
-    // (per config, per alert name).
-    std::map<const ConfigKey, std::map<const std::string, int>> mAlertStats;
+    // (per config, per alert name). The map size is capped by kMaxConfigCount.
+    std::map<const ConfigKey, std::map<const int64_t, int>> mAlertStats;
 
-    // Stores how many times a matcher have been matched.
-    std::map<const ConfigKey, std::map<const std::string, int>> mMatcherStats;
+    // Stores how many times a matcher have been matched. The map size is capped by kMaxConfigCount.
+    std::map<const ConfigKey, std::map<const int64_t, int>> mMatcherStats;
 
     void noteConfigRemovedInternalLocked(const ConfigKey& key);
 
@@ -224,6 +256,8 @@
 
     void noteBroadcastSent(const ConfigKey& key, int32_t timeSec);
 
+    void addToIceBoxLocked(const StatsdStatsReport_ConfigStats& stats);
+
     FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd);
     FRIEND_TEST(StatsdStatsTest, TestInvalidConfigAdd);
     FRIEND_TEST(StatsdStatsTest, TestConfigRemove);
@@ -235,4 +269,4 @@
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index d660b5f..49a6e33 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -17,8 +17,14 @@
 #define DEBUG true  // STOPSHIP if true
 #include "logd/LogEvent.h"
 
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+#include <set>
 #include <sstream>
-#include "stats_util.h"
+
+#include "field_util.h"
+#include "dimension.h"
+#include "stats_log_util.h"
 
 namespace android {
 namespace os {
@@ -30,16 +36,20 @@
 using android::util::ProtoOutputStream;
 
 LogEvent::LogEvent(log_msg& msg) {
-    mContext =
+    android_log_context context =
             create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t));
     mTimestampNs = msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec;
     mLogUid = msg.entry_v4.uid;
-    init(mContext);
+    init(context);
+    if (context) {
+        android_log_destroy(&context);
+    }
 }
 
 LogEvent::LogEvent(int32_t tagId, uint64_t timestampNs) {
     mTimestampNs = timestampNs;
     mTagId = tagId;
+    mLogUid = 0;
     mContext = create_android_logger(1937006964); // the event tag shared by all stats logs
     if (mContext) {
         android_log_write_int32(mContext, tagId);
@@ -53,6 +63,14 @@
         // turns to reader mode
         mContext = create_android_log_parser(buffer, len);
         init(mContext);
+        // destroy the context to save memory.
+        android_log_destroy(&mContext);
+    }
+}
+
+LogEvent::~LogEvent() {
+    if (mContext) {
+        android_log_destroy(&mContext);
     }
 }
 
@@ -98,19 +116,72 @@
     return false;
 }
 
-LogEvent::~LogEvent() {
+bool LogEvent::write(const std::vector<AttributionNode>& nodes) {
     if (mContext) {
-        android_log_destroy(&mContext);
+         if (android_log_write_list_begin(mContext) < 0) {
+            return false;
+         }
+         for (size_t i = 0; i < nodes.size(); ++i) {
+             if (!write(nodes[i])) {
+                return false;
+             }
+         }
+         if (android_log_write_list_end(mContext) < 0) {
+            return false;
+         }
+         return true;
+    }
+    return false;
+}
+
+bool LogEvent::write(const AttributionNode& node) {
+    if (mContext) {
+         if (android_log_write_list_begin(mContext) < 0) {
+            return false;
+         }
+         if (android_log_write_int32(mContext, node.uid()) < 0) {
+            return false;
+         }
+         if (android_log_write_string8(mContext, node.tag().c_str()) < 0) {
+            return false;
+         }
+         if (android_log_write_int32(mContext, node.uid()) < 0) {
+            return false;
+         }
+         if (android_log_write_list_end(mContext) < 0) {
+            return false;
+         }
+         return true;
+    }
+    return false;
+}
+
+namespace {
+
+void increaseField(Field *field, bool is_child) {
+    if (is_child) {
+        if (field->child_size() <= 0) {
+            field->add_child();
+        }
+    } else {
+        field->clear_child();
+    }
+    Field* curr = is_child ? field->mutable_child(0) : field;
+    if (!curr->has_field()) {
+        curr->set_field(1);
+    } else {
+        curr->set_field(curr->field() + 1);
     }
 }
 
+}  // namespace
+
 /**
  * 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(android_log_context context) {
-    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
@@ -118,24 +189,79 @@
     // list-related log elements and the order we get there is our index-keyed data
     // structure.
     int i = 0;
+
+    int seenListStart = 0;
+
+    Field field;
     do {
         elem = android_log_read_next(context);
         switch ((int)elem.type) {
             case EVENT_TYPE_INT:
-                // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id. If we add WorkSource, it would
-                // be the list starting at [2].
+                // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id.
                 if (i == 1) {
                     mTagId = elem.data.int32;
-                    break;
+                } else {
+                    increaseField(&field, seenListStart > 0/* is_child */);
+                    DimensionsValue dimensionsValue;
+                    dimensionsValue.set_value_int(elem.data.int32);
+                    setFieldInLeafValueProto(field, &dimensionsValue);
+                    mFieldValueMap.insert(
+                        std::make_pair(buildAtomField(mTagId, field), dimensionsValue));
                 }
+                break;
             case EVENT_TYPE_FLOAT:
+                {
+                    increaseField(&field, seenListStart > 0/* is_child */);
+                    DimensionsValue dimensionsValue;
+                    dimensionsValue.set_value_float(elem.data.float32);
+                    setFieldInLeafValueProto(field, &dimensionsValue);
+                    mFieldValueMap.insert(
+                        std::make_pair(buildAtomField(mTagId, field), dimensionsValue));
+                }
+                break;
             case EVENT_TYPE_STRING:
+                {
+                    increaseField(&field, seenListStart > 0/* is_child */);
+                    DimensionsValue dimensionsValue;
+                    dimensionsValue.set_value_str(string(elem.data.string, elem.len).c_str());
+                    setFieldInLeafValueProto(field, &dimensionsValue);
+                    mFieldValueMap.insert(
+                        std::make_pair(buildAtomField(mTagId, field), dimensionsValue));
+                }
+                break;
             case EVENT_TYPE_LONG:
-                mElements.push_back(elem);
+                {
+                    increaseField(&field, seenListStart > 0 /* is_child */);
+                    DimensionsValue dimensionsValue;
+                    dimensionsValue.set_value_long(elem.data.int64);
+                    setFieldInLeafValueProto(field, &dimensionsValue);
+                    mFieldValueMap.insert(
+                        std::make_pair(buildAtomField(mTagId, field), dimensionsValue));
+                }
                 break;
             case EVENT_TYPE_LIST:
+                if (i >= 1) {
+                    if (seenListStart > 0) {
+                       increasePosition(&field);
+                    } else {
+                        increaseField(&field, false /* is_child */);
+                    }
+                    seenListStart++;
+                    if (seenListStart >= 3) {
+                        ALOGE("Depth > 2. Not supported!");
+                        return;
+                    }
+                }
                 break;
             case EVENT_TYPE_LIST_STOP:
+                seenListStart--;
+                if (seenListStart == 0) {
+                    field.clear_position_index();
+                } else {
+                    if (field.child_size() > 0) {
+                       field.mutable_child(0)->clear_field();
+                    }
+                }
                 break;
             case EVENT_TYPE_UNKNOWN:
                 break;
@@ -147,142 +273,145 @@
 }
 
 int64_t LogEvent::GetLong(size_t key, status_t* err) const {
-    if (key < 1 || (key - 1)  >= mElements.size()) {
+    DimensionsValue value;
+    if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
         *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 DimensionsValue* leafValue = getSingleLeafValue(&value);
+    switch (leafValue->value_case()) {
+        case DimensionsValue::ValueCase::kValueInt:
+            return (int64_t)leafValue->value_int();
+        case DimensionsValue::ValueCase::kValueLong:
+            return leafValue->value_long();
+        case DimensionsValue::ValueCase::kValueBool:
+            return leafValue->value_bool() ? 1 : 0;
+        case DimensionsValue::ValueCase::kValueFloat:
+            return (int64_t)leafValue->value_float();
+        case DimensionsValue::ValueCase::kValueTuple:
+        case DimensionsValue::ValueCase::kValueStr:
+        case DimensionsValue::ValueCase::VALUE_NOT_SET: {
+            *err = BAD_TYPE;
+            return 0;
+        }
     }
 }
 
 const char* LogEvent::GetString(size_t key, status_t* err) const {
-    if (key < 1 || (key - 1)  >= mElements.size()) {
+    DimensionsValue value;
+    if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
         *err = BAD_INDEX;
-        return NULL;
+        return 0;
     }
-    key--;
-    const android_log_list_element& elem = mElements[key];
-    if (elem.type != EVENT_TYPE_STRING) {
-        *err = BAD_TYPE;
-        return NULL;
+    const DimensionsValue* leafValue = getSingleLeafValue(&value);
+    switch (leafValue->value_case()) {
+        case DimensionsValue::ValueCase::kValueStr:
+            return leafValue->value_str().c_str();
+        case DimensionsValue::ValueCase::kValueInt:
+        case DimensionsValue::ValueCase::kValueLong:
+        case DimensionsValue::ValueCase::kValueBool:
+        case DimensionsValue::ValueCase::kValueFloat:
+        case DimensionsValue::ValueCase::kValueTuple:
+        case DimensionsValue::ValueCase::VALUE_NOT_SET: {
+            *err = BAD_TYPE;
+            return 0;
+        }
     }
-    // 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()) {
+    DimensionsValue value;
+    if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
         *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;
+    const DimensionsValue* leafValue = getSingleLeafValue(&value);
+    switch (leafValue->value_case()) {
+        case DimensionsValue::ValueCase::kValueInt:
+            return leafValue->value_int() != 0;
+        case DimensionsValue::ValueCase::kValueLong:
+            return leafValue->value_long() != 0;
+        case DimensionsValue::ValueCase::kValueBool:
+            return leafValue->value_bool();
+        case DimensionsValue::ValueCase::kValueFloat:
+            return leafValue->value_float() != 0;
+        case DimensionsValue::ValueCase::kValueTuple:
+        case DimensionsValue::ValueCase::kValueStr:
+        case DimensionsValue::ValueCase::VALUE_NOT_SET: {
+            *err = BAD_TYPE;
+            return 0;
+        }
     }
 }
 
 float LogEvent::GetFloat(size_t key, status_t* err) const {
-    if (key < 1 || (key - 1)  >= mElements.size()) {
+    DimensionsValue value;
+    if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
         *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;
+    const DimensionsValue* leafValue = getSingleLeafValue(&value);
+    switch (leafValue->value_case()) {
+        case DimensionsValue::ValueCase::kValueInt:
+            return (float)leafValue->value_int();
+        case DimensionsValue::ValueCase::kValueLong:
+            return (float)leafValue->value_long();
+        case DimensionsValue::ValueCase::kValueBool:
+            return leafValue->value_bool() ? 1.0f : 0.0f;
+        case DimensionsValue::ValueCase::kValueFloat:
+            return leafValue->value_float();
+        case DimensionsValue::ValueCase::kValueTuple:
+        case DimensionsValue::ValueCase::kValueStr:
+        case DimensionsValue::ValueCase::VALUE_NOT_SET: {
+            *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--;
+void LogEvent::GetAtomDimensionsValueProtos(const FieldMatcher& matcher,
+                                            std::vector<DimensionsValue> *dimensionsValues) const {
+    findDimensionsValues(mFieldValueMap, matcher, dimensionsValues);
+}
 
-    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_long(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);
+bool LogEvent::GetAtomDimensionsValueProto(const FieldMatcher& matcher,
+                                           DimensionsValue* dimensionsValue) const {
+    std::vector<DimensionsValue> rootDimensionsValues;
+    findDimensionsValues(mFieldValueMap, matcher, &rootDimensionsValues);
+    if (rootDimensionsValues.size() != 1) {
+        return false;
     }
-    return pair;
+    *dimensionsValue = rootDimensionsValues.front();
+    return true;
+}
+
+bool LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField,
+                                                 DimensionsValue* dimensionsValue) const {
+    return GetAtomDimensionsValueProto(
+        buildSimpleAtomFieldMatcher(mTagId, atomField), dimensionsValue);
+}
+
+DimensionsValue LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField)  const {
+    DimensionsValue dimensionsValue;
+    GetSimpleAtomDimensionsValueProto(atomField, &dimensionsValue);
+    return dimensionsValue;
 }
 
 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);
+    for (const auto& itr : mFieldValueMap) {
+        result << FieldToString(itr.first);
         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 << DimensionsValueToString(itr.second);
+        result << " ";
     }
     result << " }";
     return result.str();
 }
 
-void LogEvent::ToProto(ProtoOutputStream& proto) const {
-    long long atomToken = proto.start(FIELD_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(FIELD_TYPE_INT32 | key, elem.data.int32);
-        } else if (elem.type == EVENT_TYPE_LONG) {
-            proto.write(FIELD_TYPE_INT64 | key, (long long)elem.data.int64);
-        } else if (elem.type == EVENT_TYPE_FLOAT) {
-            proto.write(FIELD_TYPE_FLOAT | key, elem.data.float32);
-        } else if (elem.type == EVENT_TYPE_STRING) {
-            proto.write(FIELD_TYPE_STRING | key, elem.data.string);
-        }
-    }
-    proto.end(atomToken);
+void LogEvent::ToProto(ProtoOutputStream& protoOutput) const {
+    writeFieldValueTreeToStream(getFieldValueMap(), &protoOutput);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index d3f38de..8f3dedf 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "field_util.h"
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 
 #include <android/util/ProtoOutputStream.h>
@@ -23,9 +24,11 @@
 #include <log/log_read.h>
 #include <private/android_logger.h>
 #include <utils/Errors.h>
+#include <utils/JenkinsHash.h>
 
 #include <memory>
 #include <string>
+#include <map>
 #include <vector>
 
 namespace android {
@@ -77,6 +80,20 @@
     bool GetBool(size_t key, status_t* err) const;
     float GetFloat(size_t key, status_t* err) const;
 
+    /*
+     * Get DimensionsValue proto objects from FieldMatcher.
+     */
+    void GetAtomDimensionsValueProtos(
+        const FieldMatcher& matcher, std::vector<DimensionsValue> *dimensionsValues) const;
+    bool GetAtomDimensionsValueProto(
+        const FieldMatcher& matcher, DimensionsValue* dimensionsValue) const;
+
+    /*
+     * Get a DimensionsValue proto objects from Field.
+     */
+    bool GetSimpleAtomDimensionsValueProto(size_t field, DimensionsValue* dimensionsValue) const;
+    DimensionsValue  GetSimpleAtomDimensionsValueProto(size_t atomField)  const;
+
     /**
      * Write test data to the LogEvent. This can only be used when the LogEvent is constructed
      * using LogEvent(tagId, timestampNs). You need to call init() before you can read from it.
@@ -87,6 +104,8 @@
     bool write(int64_t value);
     bool write(const string& value);
     bool write(float value);
+    bool write(const std::vector<AttributionNode>& nodes);
+    bool write(const AttributionNode& node);
 
     /**
      * Return a string representation of this event.
@@ -98,11 +117,6 @@
      */
     void ToProto(android::util::ProtoOutputStream& out) const;
 
-    /*
-     * Get a KeyValuePair proto object.
-     */
-    KeyValuePair GetKeyValueProto(size_t key) const;
-
     /**
      * Used with the constructor where tag is passed in. Converts the log_event_list to read mode
      * and prepares the list for reading.
@@ -114,10 +128,12 @@
      */
     void setTimestampNs(uint64_t timestampNs) {mTimestampNs = timestampNs;}
 
-    int size() const {
-        return mElements.size();
+    inline int size() const {
+        return mFieldValueMap.size();
     }
 
+    inline const FieldValueMap& getFieldValueMap() const { return mFieldValueMap; }
+
 private:
     /**
      * Don't copy, it's slower. If we really need this we can add it but let's try to
@@ -130,8 +146,11 @@
      */
     void init(android_log_context context);
 
-    vector<android_log_list_element> mElements;
+    FieldValueMap mFieldValueMap;
 
+    // This field is used when statsD wants to create log event object and write fields to it. After
+    // calling init() function, this object would be destroyed to save memory usage.
+    // When the log event is created from log msg, this field is never initiated.
     android_log_context mContext;
 
     uint64_t mTimestampNs;
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
index 51a38b6..15c067e 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
@@ -29,8 +29,8 @@
 using std::unordered_map;
 using std::vector;
 
-CombinationLogMatchingTracker::CombinationLogMatchingTracker(const string& name, const int index)
-    : LogMatchingTracker(name, index) {
+CombinationLogMatchingTracker::CombinationLogMatchingTracker(const int64_t& id, const int index)
+    : LogMatchingTracker(id, index) {
 }
 
 CombinationLogMatchingTracker::~CombinationLogMatchingTracker() {
@@ -38,7 +38,7 @@
 
 bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
                                          const vector<sp<LogMatchingTracker>>& allTrackers,
-                                         const unordered_map<string, int>& matcherMap,
+                                         const unordered_map<int64_t, int>& matcherMap,
                                          vector<bool>& stack) {
     if (mInitialized) {
         return true;
@@ -60,10 +60,10 @@
         return false;
     }
 
-    for (const string& child : matcher.matcher()) {
+    for (const auto& child : matcher.matcher()) {
         auto pair = matcherMap.find(child);
         if (pair == matcherMap.end()) {
-            ALOGW("Matcher %s not found in the config", child.c_str());
+            ALOGW("Matcher %lld not found in the config", (long long)child);
             return false;
         }
 
@@ -76,14 +76,14 @@
         }
 
         if (!allTrackers[childIndex]->init(allLogMatchers, allTrackers, matcherMap, stack)) {
-            ALOGW("child matcher init failed %s", child.c_str());
+            ALOGW("child matcher init failed %lld", (long long)child);
             return false;
         }
 
         mChildren.push_back(childIndex);
 
-        const set<int>& childTagIds = allTrackers[childIndex]->getTagIds();
-        mTagIds.insert(childTagIds.begin(), childTagIds.end());
+        const set<int>& childTagIds = allTrackers[childIndex]->getAtomIds();
+        mAtomIds.insert(childTagIds.begin(), childTagIds.end());
     }
 
     mInitialized = true;
@@ -100,7 +100,7 @@
         return;
     }
 
-    if (mTagIds.find(event.GetTagId()) == mTagIds.end()) {
+    if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
         matcherResults[mIndex] = MatchingState::kNotMatched;
         return;
     }
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
index 81f6e80..2a3f08d 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
@@ -31,11 +31,11 @@
 // Represents a AtomMatcher_Combination in the StatsdConfig.
 class CombinationLogMatchingTracker : public virtual LogMatchingTracker {
 public:
-    CombinationLogMatchingTracker(const std::string& name, const int index);
+    CombinationLogMatchingTracker(const int64_t& id, const int index);
 
     bool init(const std::vector<AtomMatcher>& allLogMatchers,
               const std::vector<sp<LogMatchingTracker>>& allTrackers,
-              const std::unordered_map<std::string, int>& matcherMap,
+              const std::unordered_map<int64_t, int>& matcherMap,
               std::vector<bool>& stack);
 
     ~CombinationLogMatchingTracker();
diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h
index 8162c44..4f30a04 100644
--- a/cmds/statsd/src/matchers/LogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/LogMatchingTracker.h
@@ -33,8 +33,8 @@
 
 class LogMatchingTracker : public virtual RefBase {
 public:
-    LogMatchingTracker(const std::string& name, const int index)
-        : mName(name), mIndex(index), mInitialized(false){};
+    LogMatchingTracker(const int64_t& id, const int index)
+        : mId(id), mIndex(index), mInitialized(false){};
 
     virtual ~LogMatchingTracker(){};
 
@@ -48,7 +48,7 @@
     //        circle dependency.
     virtual bool init(const std::vector<AtomMatcher>& allLogMatchers,
                       const std::vector<sp<LogMatchingTracker>>& allTrackers,
-                      const std::unordered_map<std::string, int>& matcherMap,
+                      const std::unordered_map<int64_t, int>& matcherMap,
                       std::vector<bool>& stack) = 0;
 
     // Called when a log event comes.
@@ -65,17 +65,17 @@
     // 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;
+    virtual const std::set<int>& getAtomIds() const {
+        return mAtomIds;
     }
 
-    const std::string& getName() const {
-        return mName;
+    const int64_t& getId() const {
+        return mId;
     }
 
 protected:
     // Name of this matching. We don't really need the name, but it makes log message easy to debug.
-    const std::string mName;
+    const int64_t mId;
 
     // Index of this LogMatchingTracker in MetricsManager's container.
     const int mIndex;
@@ -88,7 +88,7 @@
     // 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;
+    std::set<int> mAtomIds;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
index ac217ab..31b3db5 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
@@ -29,13 +29,14 @@
 using std::vector;
 
 
-SimpleLogMatchingTracker::SimpleLogMatchingTracker(const string& name, const int index,
-                                                   const SimpleAtomMatcher& matcher)
-    : LogMatchingTracker(name, index), mMatcher(matcher) {
-    if (!matcher.has_tag()) {
+SimpleLogMatchingTracker::SimpleLogMatchingTracker(const int64_t& id, const int index,
+                                                   const SimpleAtomMatcher& matcher,
+                                                   const UidMap& uidMap)
+    : LogMatchingTracker(id, index), mMatcher(matcher), mUidMap(uidMap) {
+    if (!matcher.has_atom_id()) {
         mInitialized = false;
     } else {
-        mTagIds.insert(matcher.tag());
+        mAtomIds.insert(matcher.atom_id());
         mInitialized = true;
     }
 }
@@ -45,7 +46,7 @@
 
 bool SimpleLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
                                     const vector<sp<LogMatchingTracker>>& allTrackers,
-                                    const unordered_map<string, int>& matcherMap,
+                                    const unordered_map<int64_t, int>& matcherMap,
                                     vector<bool>& stack) {
     // no need to do anything.
     return mInitialized;
@@ -55,18 +56,18 @@
                                           const vector<sp<LogMatchingTracker>>& allTrackers,
                                           vector<MatchingState>& matcherResults) {
     if (matcherResults[mIndex] != MatchingState::kNotComputed) {
-        VLOG("Matcher %s already evaluated ", mName.c_str());
+        VLOG("Matcher %lld already evaluated ", (long long)mId);
         return;
     }
 
-    if (mTagIds.find(event.GetTagId()) == mTagIds.end()) {
+    if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
         matcherResults[mIndex] = MatchingState::kNotMatched;
         return;
     }
 
-    bool matched = matchesSimple(mMatcher, event);
+    bool matched = matchesSimple(mUidMap, mMatcher, event);
     matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
-    VLOG("Stats SimpleLogMatcher %s matched? %d", mName.c_str(), matched);
+    VLOG("Stats SimpleLogMatcher %lld matched? %d", (long long)mId, matched);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
index 2c188c1..28b339c 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
@@ -24,6 +24,7 @@
 #include <vector>
 #include "LogMatchingTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "packages/UidMap.h"
 
 namespace android {
 namespace os {
@@ -31,14 +32,15 @@
 
 class SimpleLogMatchingTracker : public virtual LogMatchingTracker {
 public:
-    SimpleLogMatchingTracker(const std::string& name, const int index,
-                             const SimpleAtomMatcher& matcher);
+    SimpleLogMatchingTracker(const int64_t& id, const int index,
+                             const SimpleAtomMatcher& matcher,
+                             const UidMap& uidMap);
 
     ~SimpleLogMatchingTracker();
 
     bool init(const std::vector<AtomMatcher>& allLogMatchers,
               const std::vector<sp<LogMatchingTracker>>& allTrackers,
-              const std::unordered_map<std::string, int>& matcherMap,
+              const std::unordered_map<int64_t, int>& matcherMap,
               std::vector<bool>& stack) override;
 
     void onLogEvent(const LogEvent& event,
@@ -47,6 +49,7 @@
 
 private:
     const SimpleAtomMatcher mMatcher;
+    const UidMap& mUidMap;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 9e88e5d0..46d9b92 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -19,7 +19,9 @@
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "matchers/LogMatchingTracker.h"
 #include "matchers/matcher_util.h"
+#include "dimension.h"
 #include "stats_util.h"
+#include "field_util.h"
 
 #include <log/event_tag_map.h>
 #include <log/log_event_list.h>
@@ -91,127 +93,173 @@
     return matched;
 }
 
-bool matchesSimple(const SimpleAtomMatcher& simpleMatcher, const LogEvent& event) {
-    const int tagId = event.GetTagId();
+bool IsAttributionUidField(const Field& field) {
+    return field.child_size() == 1 && field.child(0).field() == 1
+        && field.child(0).child_size() == 1 && field.child(0).child(0).field() == 1;
+}
 
-    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;
-                    break;
-                }
-            } else {
-                allMatched = false;
-                break;
-            }
-        } 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;
-                        break;
-                    }
-                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) {
-                    if (!(val < cur.lt_int())) {
-                        allMatched = false;
-                        break;
-                    }
-                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) {
-                    if (!(val > cur.gt_int())) {
-                        allMatched = false;
-                        break;
-                    }
-                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) {
-                    if (!(val <= cur.lte_int())) {
-                        allMatched = false;
-                        break;
-                    }
-                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
-                    if (!(val >= cur.gte_int())) {
-                        allMatched = false;
-                        break;
-                    }
-                }
-            } else {
-                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;
-                    break;
-                }
-            } else {
-                allMatched = false;
-                break;
-            }
-        } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat ||
-                   matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
-            // Float fields
-            status_t err = NO_ERROR;
-            float val = event.GetFloat(key, &err);
-            if (err == NO_ERROR) {
-                if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) {
-                    if (!(val < cur.lt_float())) {
-                        allMatched = false;
-                        break;
-                    }
-                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
-                    if (!(val > cur.gt_float())) {
-                        allMatched = false;
-                        break;
-                    }
-                }
-            } else {
-                allMatched = false;
-                break;
-            }
-        } else {
-            // If value matcher is not present, assume that we match.
+bool matchesNonRepeatedField(
+       const UidMap& uidMap,
+       const FieldValueMap& fieldMap,
+       const FieldValueMatcher&matcher,
+       const Field& field) {
+    if (matcher.value_matcher_case() ==
+            FieldValueMatcher::ValueMatcherCase::VALUE_MATCHER_NOT_SET) {
+        return !fieldMap.empty() && fieldMap.begin()->first.field() == matcher.field();
+    } else if (matcher.value_matcher_case() == FieldValueMatcher::ValueMatcherCase::kMatchesTuple) {
+        bool allMatched = true;
+        for (int i = 0; allMatched && i <  matcher.matches_tuple().field_value_matcher_size(); ++i) {
+            const auto& childMatcher = matcher.matches_tuple().field_value_matcher(i);
+            Field childField = field;
+            appendLeaf(&childField, childMatcher.field());
+            allMatched &= matchFieldSimple(uidMap, fieldMap, childMatcher, childField);
         }
+        return allMatched;
+    } else {
+        auto ret = fieldMap.equal_range(field);
+        int found = 0;
+        for (auto it = ret.first; it != ret.second; ++it) {
+            found++;
+        }
+        // Not found.
+        if (found <= 0) {
+            return false;
+        }
+        if (found > 1) {
+            ALOGE("Found multiple values for optional field.");
+            return false;
+        }
+        bool matched = false;
+        switch (matcher.value_matcher_case()) {
+            case FieldValueMatcher::ValueMatcherCase::kEqBool:
+                 // Logd does not support bool, it is int instead.
+                 matched = ((ret.first->second.value_int() > 0) == matcher.eq_bool());
+                 break;
+            case FieldValueMatcher::ValueMatcherCase::kEqString:
+                 {
+                    if (IsAttributionUidField(field)) {
+                        const int uid = ret.first->second.value_int();
+                        std::set<string> packageNames =
+                            uidMap.getAppNamesFromUid(uid, true /* normalize*/);
+                        matched = packageNames.find(matcher.eq_string()) != packageNames.end();
+                    } else {
+                        matched = (ret.first->second.value_str() == matcher.eq_string());
+                    }
+                 }
+                 break;
+            case FieldValueMatcher::ValueMatcherCase::kEqInt:
+                 matched = (ret.first->second.value_int() == matcher.eq_int());
+                 break;
+            case FieldValueMatcher::ValueMatcherCase::kLtInt:
+                 matched = (ret.first->second.value_int() < matcher.lt_int());
+                 break;
+            case FieldValueMatcher::ValueMatcherCase::kGtInt:
+                 matched = (ret.first->second.value_int() > matcher.gt_int());
+                 break;
+            case FieldValueMatcher::ValueMatcherCase::kLtFloat:
+                 matched = (ret.first->second.value_float() < matcher.lt_float());
+                 break;
+            case FieldValueMatcher::ValueMatcherCase::kGtFloat:
+                 matched = (ret.first->second.value_float() > matcher.gt_float());
+                 break;
+            case FieldValueMatcher::ValueMatcherCase::kLteInt:
+                 matched = (ret.first->second.value_int() <= matcher.lte_int());
+                 break;
+            case FieldValueMatcher::ValueMatcherCase::kGteInt:
+                 matched = (ret.first->second.value_int() >= matcher.gte_int());
+                 break;
+            default:
+                break;
+        }
+        return matched;
     }
-    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);
+bool matchesRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap,
+                          const FieldValueMatcher&matcher, const Field& field) {
+    if (matcher.position() == Position::FIRST) {
+        Field first_field = field;
+        setPositionForLeaf(&first_field, 0);
+        return matchesNonRepeatedField(uidMap, fieldMap, matcher, first_field);
+    } else {
+        auto itLower = fieldMap.lower_bound(field);
+        if (itLower == fieldMap.end()) {
+            return false;
+        }
+        Field next_field = field;
+        getNextField(&next_field);
+        auto itUpper = fieldMap.lower_bound(next_field);
+        switch (matcher.position()) {
+             case Position::LAST:
+                 {
+                     itUpper--;
+                     if (itUpper == fieldMap.end()) {
+                        return false;
+                     } else {
+                         Field last_field = field;
+                         int last_index = getPositionByReferenceField(field, itUpper->first);
+                         if (last_index < 0) {
+                            return false;
+                         }
+                         setPositionForLeaf(&last_field, last_index);
+                         return matchesNonRepeatedField(uidMap, fieldMap, matcher, last_field);
+                     }
+                 }
+                 break;
+             case Position::ANY:
+                 {
+                    std::set<int> indexes;
+                    for (auto it = itLower; it != itUpper; ++it) {
+                        int index = getPositionByReferenceField(field, it->first);
+                        if (index >= 0) {
+                            indexes.insert(index);
+                        }
+                    }
+                    bool matched = false;
+                    for (const int index : indexes) {
+                         Field any_field = field;
+                         setPositionForLeaf(&any_field, index);
+                         matched |= matchesNonRepeatedField(uidMap, fieldMap, matcher, any_field);
+                    }
+                    return matched;
+                 }
+             default:
+                return false;
+         }
     }
-    return key;
+
 }
 
+bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& fieldMap,
+                      const FieldValueMatcher&matcher, const Field& field) {
+    if (!matcher.has_position()) {
+        return matchesNonRepeatedField(uidMap, fieldMap, matcher, field);
+    } else {
+        return matchesRepeatedField(uidMap, fieldMap, matcher, field);
+    }
+}
+
+bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher,
+                   const LogEvent& event) {
+    if (simpleMatcher.field_value_matcher_size() <= 0) {
+        return event.GetTagId() == simpleMatcher.atom_id();
+    }
+    Field root_field;
+    root_field.set_field(simpleMatcher.atom_id());
+    FieldValueMatcher root_field_matcher;
+    root_field_matcher.set_field(simpleMatcher.atom_id());
+    for (int i = 0; i < simpleMatcher.field_value_matcher_size(); i++) {
+        *root_field_matcher.mutable_matches_tuple()->add_field_value_matcher() =
+            simpleMatcher.field_value_matcher(i);
+    }
+    return matchFieldSimple(uidMap, event.getFieldValueMap(), root_field_matcher, root_field);
+}
+
+vector<DimensionsValue> getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher) {
+    vector<DimensionsValue> values;
+    findDimensionsValues(event.getFieldValueMap(), matcher, &values);
+    return values;
+}
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h
index f54ab36..704cb4c 100644
--- a/cmds/statsd/src/matchers/matcher_util.h
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef MATCHER_UTIL_H
-#define MATCHER_UTIL_H
+#pragma once
 
 #include "logd/LogEvent.h"
 
@@ -28,6 +27,7 @@
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
+#include "packages/UidMap.h"
 
 namespace android {
 namespace os {
@@ -42,12 +42,14 @@
 bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation,
                       const std::vector<MatchingState>& matcherResults);
 
-bool matchesSimple(const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper);
+bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& dimensionsMap,
+                      const FieldValueMatcher& matcher, const Field& field);
 
-std::vector<KeyValuePair> getDimensionKey(const LogEvent& event,
-                                          const std::vector<KeyMatcher>& dimensions);
+bool matchesSimple(const UidMap& uidMap,
+    const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper);
+
+std::vector<DimensionsValue> getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher);
 
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-#endif  // MATCHER_UTIL_H
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 9031ed0..3e98098 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -20,6 +20,7 @@
 #include "CountMetricProducer.h"
 #include "guardrail/StatsdStats.h"
 #include "stats_util.h"
+#include "stats_log_util.h"
 
 #include <limits.h>
 #include <stdlib.h>
@@ -42,7 +43,7 @@
 namespace statsd {
 
 // for StatsLogReport
-const int FIELD_ID_NAME = 1;
+const int FIELD_ID_ID = 1;
 const int FIELD_ID_START_REPORT_NANOS = 2;
 const int FIELD_ID_END_REPORT_NANOS = 3;
 const int FIELD_ID_COUNT_METRICS = 5;
@@ -51,12 +52,6 @@
 // for CountMetricData
 const int FIELD_ID_DIMENSION = 1;
 const int FIELD_ID_BUCKET_INFO = 2;
-// for KeyValuePair
-const int FIELD_ID_KEY = 1;
-const int FIELD_ID_VALUE_STR = 2;
-const int FIELD_ID_VALUE_INT = 3;
-const int FIELD_ID_VALUE_BOOL = 4;
-const int FIELD_ID_VALUE_FLOAT = 5;
 // for CountBucketInfo
 const int FIELD_ID_START_BUCKET_NANOS = 1;
 const int FIELD_ID_END_BUCKET_NANOS = 2;
@@ -66,16 +61,16 @@
                                          const int conditionIndex,
                                          const sp<ConditionWizard>& wizard,
                                          const uint64_t startTimeNs)
-    : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard) {
+    : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) {
     // 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;
+    if (metric.has_bucket()) {
+        mBucketSizeNs = TimeUnitToBucketSizeInMillis(metric.bucket()) * 1000000;
     } else {
         mBucketSizeNs = LLONG_MAX;
     }
 
     // TODO: use UidMap if uid->pkg_name is required
-    mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+    mDimensions = metric.dimensions();
 
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
@@ -83,7 +78,7 @@
         mConditionSliced = true;
     }
 
-    VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(),
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
 
@@ -92,43 +87,49 @@
 }
 
 void CountMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) {
-    VLOG("Metric %s onSlicedConditionMayChange", mName.c_str());
+    VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
+}
+
+void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
+    flushIfNeededLocked(dumpTimeNs);
+    report->set_metric_id(mMetricId);
+    report->set_start_report_nanos(mStartTimeNs);
+
+    auto count_metrics = report->mutable_count_metrics();
+    for (const auto& counter : mPastBuckets) {
+        CountMetricData* metricData = count_metrics->add_data();
+        *metricData->mutable_dimension() = counter.first.getDimensionsValue();
+        for (const auto& bucket : counter.second) {
+            CountBucketInfo* bucketInfo = metricData->add_bucket_info();
+            bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
+            bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs);
+            bucketInfo->set_count(bucket.mCount);
+        }
+    }
 }
 
 void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
                                              ProtoOutputStream* protoOutput) {
     flushIfNeededLocked(dumpTimeNs);
 
-    protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
     long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS);
 
-    VLOG("metric %s dump report now...", mName.c_str());
+    VLOG("metric %lld dump report now...",(long long)mMetricId);
 
     for (const auto& counter : mPastBuckets) {
         const HashableDimensionKey& hashableKey = counter.first;
-        const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
         VLOG("  dimension key %s", hashableKey.c_str());
 
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
-        // First fill dimension (KeyValuePairs).
-        for (const auto& kv : kvs) {
-            long long dimensionToken = protoOutput->start(
-                    FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
-            protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
-            if (kv.has_value_str()) {
-                protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
-            } else if (kv.has_value_int()) {
-                protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
-            } else if (kv.has_value_bool()) {
-                protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
-            } else if (kv.has_value_float()) {
-                protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
-            }
-            protoOutput->end(dimensionToken);
-        }
+        // First fill dimension.
+        long long dimensionToken = protoOutput->start(
+                FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
+        writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+        protoOutput->end(dimensionToken);
 
         // Then fill bucket_info (CountBucketInfo).
         for (const auto& bucket : counter.second) {
@@ -157,7 +158,7 @@
 
 void CountMetricProducer::onConditionChangedLocked(const bool conditionMet,
                                                    const uint64_t eventTime) {
-    VLOG("Metric %s onConditionChanged", mName.c_str());
+    VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
     mCondition = conditionMet;
 }
 
@@ -169,11 +170,11 @@
     // 1. Report the tuple count if the tuple count > soft limit
     if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mCurrentSlicedCounter->size() + 1;
-        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount);
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-            ALOGE("CountMetric %s dropping data for dimension key %s", mName.c_str(),
-                  newKey.c_str());
+            ALOGE("CountMetric %lld dropping data for dimension key %s",
+                (long long)mMetricId, newKey.c_str());
             return true;
         }
     }
@@ -183,7 +184,7 @@
 
 void CountMetricProducer::onMatchedLogEventInternalLocked(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
-        const map<string, HashableDimensionKey>& conditionKey, bool condition,
+        const ConditionKey& conditionKey, bool condition,
         const LogEvent& event) {
     uint64_t eventTimeNs = event.GetTimestampNs();
 
@@ -214,7 +215,7 @@
                                          mCurrentSlicedCounter->find(eventKey)->second);
     }
 
-    VLOG("metric %s %s->%lld", mName.c_str(), eventKey.c_str(),
+    VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.c_str(),
          (long long)(*mCurrentSlicedCounter)[eventKey]);
 }
 
@@ -233,8 +234,8 @@
         info.mCount = counter.second;
         auto& bucketList = mPastBuckets[counter.first];
         bucketList.push_back(info);
-        VLOG("metric %s, dump key value: %s -> %lld", mName.c_str(), counter.first.c_str(),
-             (long long)counter.second);
+        VLOG("metric %lld, dump key value: %s -> %lld",
+            (long long)mMetricId, counter.first.c_str(), (long long)counter.second);
     }
 
     for (auto& tracker : mAnomalyTrackers) {
@@ -246,7 +247,7 @@
     uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
     mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
     mCurrentBucketNum += numBucketsForward;
-    VLOG("metric %s: new bucket start time: %lld", mName.c_str(),
+    VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
          (long long)mCurrentBucketStartTimeNs);
 }
 
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index e32fc06..16fc7ee 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -51,12 +51,13 @@
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
-            const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+            const ConditionKey& conditionKey, bool condition,
             const LogEvent& event) override;
 
 private:
     void onDumpReportLocked(const uint64_t dumpTimeNs,
                             android::util::ProtoOutputStream* protoOutput) override;
+    void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override;
 
     // Internal interface to handle condition change.
     void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override;
@@ -83,7 +84,7 @@
     FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
-    FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
+    FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 1c8f422..b546297 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -20,6 +20,7 @@
 #include "DurationMetricProducer.h"
 #include "guardrail/StatsdStats.h"
 #include "stats_util.h"
+#include "stats_log_util.h"
 
 #include <limits.h>
 #include <stdlib.h>
@@ -41,7 +42,7 @@
 namespace statsd {
 
 // for StatsLogReport
-const int FIELD_ID_NAME = 1;
+const int FIELD_ID_ID = 1;
 const int FIELD_ID_START_REPORT_NANOS = 2;
 const int FIELD_ID_END_REPORT_NANOS = 3;
 const int FIELD_ID_DURATION_METRICS = 6;
@@ -50,12 +51,6 @@
 // for DurationMetricData
 const int FIELD_ID_DIMENSION = 1;
 const int FIELD_ID_BUCKET_INFO = 2;
-// for KeyValuePair
-const int FIELD_ID_KEY = 1;
-const int FIELD_ID_VALUE_STR = 2;
-const int FIELD_ID_VALUE_INT = 3;
-const int FIELD_ID_VALUE_BOOL = 4;
-const int FIELD_ID_VALUE_FLOAT = 5;
 // for DurationBucketInfo
 const int FIELD_ID_START_BUCKET_NANOS = 1;
 const int FIELD_ID_END_BUCKET_NANOS = 2;
@@ -66,26 +61,26 @@
                                                const size_t stopIndex, const size_t stopAllIndex,
                                                const bool nesting,
                                                const sp<ConditionWizard>& wizard,
-                                               const vector<KeyMatcher>& internalDimension,
+                                               const FieldMatcher& internalDimensions,
                                                const uint64_t startTimeNs)
-    : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard),
+    : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard),
       mAggregationType(metric.aggregation_type()),
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
       mStopAllIndex(stopAllIndex),
       mNested(nesting),
-      mInternalDimension(internalDimension) {
+      mInternalDimensions(internalDimensions) {
     // 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;
+    if (metric.has_bucket()) {
+        mBucketSizeNs = TimeUnitToBucketSizeInMillis(metric.bucket()) * 1000000;
     } else {
         mBucketSizeNs = LLONG_MAX;
     }
 
     // TODO: use UidMap if uid->pkg_name is required
-    mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+    mDimensions = metric.dimensions();
 
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
@@ -93,7 +88,7 @@
         mConditionSliced = true;
     }
 
-    VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(),
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
 
@@ -101,15 +96,19 @@
     VLOG("~DurationMetric() called");
 }
 
-sp<AnomalyTracker> DurationMetricProducer::createAnomalyTracker(const Alert &alert) {
-    if (alert.trigger_if_sum_gt() > alert.number_of_buckets() * mBucketSizeNs) {
-        ALOGW("invalid alert: threshold (%lld) > possible recordable value (%d x %lld)",
-              alert.trigger_if_sum_gt(), alert.number_of_buckets(),
+sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(const Alert &alert) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (alert.trigger_if_sum_gt() > alert.num_buckets() * mBucketSizeNs) {
+        ALOGW("invalid alert: threshold (%f) > possible recordable value (%d x %lld)",
+              alert.trigger_if_sum_gt(), alert.num_buckets(),
               (long long)mBucketSizeNs);
         return nullptr;
     }
-    // TODO: return a DurationAnomalyTracker (which should sublclass AnomalyTracker)
-    return new AnomalyTracker(alert, mConfigKey);
+    sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, mConfigKey);
+    if (anomalyTracker != nullptr) {
+        mAnomalyTrackers.push_back(anomalyTracker);
+    }
+    return anomalyTracker;
 }
 
 unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
@@ -117,17 +116,17 @@
     switch (mAggregationType) {
         case DurationMetric_AggregationType_SUM:
             return make_unique<OringDurationTracker>(
-                    mConfigKey, mName, eventKey, mWizard, mConditionTrackerIndex, mNested,
-                    mCurrentBucketStartTimeNs, mBucketSizeNs, mAnomalyTrackers);
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
         case DurationMetric_AggregationType_MAX_SPARSE:
             return make_unique<MaxDurationTracker>(
-                    mConfigKey, mName, eventKey, mWizard, mConditionTrackerIndex, mNested,
-                    mCurrentBucketStartTimeNs, mBucketSizeNs, mAnomalyTrackers);
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
     }
 }
 
 void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) {
-    VLOG("Metric %s onSlicedConditionMayChange", mName.c_str());
+    VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
     flushIfNeededLocked(eventTime);
     // Now for each of the on-going event, check if the condition has changed for them.
     for (auto& pair : mCurrentSlicedDuration) {
@@ -137,7 +136,7 @@
 
 void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet,
                                                       const uint64_t eventTime) {
-    VLOG("Metric %s onConditionChanged", mName.c_str());
+    VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
     mCondition = conditionMet;
     flushIfNeededLocked(eventTime);
     // TODO: need to populate the condition change time from the event which triggers the condition
@@ -147,40 +146,46 @@
     }
 }
 
+void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
+    flushIfNeededLocked(dumpTimeNs);
+    report->set_metric_id(mMetricId);
+    report->set_start_report_nanos(mStartTimeNs);
+
+    auto duration_metrics = report->mutable_duration_metrics();
+    for (const auto& pair : mPastBuckets) {
+        DurationMetricData* metricData = duration_metrics->add_data();
+        *metricData->mutable_dimension() = pair.first.getDimensionsValue();
+        for (const auto& bucket : pair.second) {
+            auto bucketInfo = metricData->add_bucket_info();
+            bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
+            bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs);
+            bucketInfo->set_duration_nanos(bucket.mDuration);
+        }
+    }
+}
+
 void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
                                                 ProtoOutputStream* protoOutput) {
     flushIfNeededLocked(dumpTimeNs);
 
-    protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
     long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS);
 
-    VLOG("metric %s dump report now...", mName.c_str());
+    VLOG("metric %lld dump report now...", (long long)mMetricId);
 
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
-        const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
         VLOG("  dimension key %s", hashableKey.c_str());
 
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
-        // First fill dimension (KeyValuePairs).
-        for (const auto& kv : kvs) {
-            long long dimensionToken = protoOutput->start(
-                    FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
-            protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
-            if (kv.has_value_str()) {
-                protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
-            } else if (kv.has_value_int()) {
-                protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
-            } else if (kv.has_value_bool()) {
-                protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
-            } else if (kv.has_value_float()) {
-                protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
-            }
-            protoOutput->end(dimensionToken);
-        }
+        // First fill dimension.
+        long long dimensionToken = protoOutput->start(
+                FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
+        writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+        protoOutput->end(dimensionToken);
 
         // Then fill bucket_info (DurationBucketInfo).
         for (const auto& bucket : pair.second) {
@@ -232,11 +237,11 @@
     // 1. Report the tuple count if the tuple count > soft limit
     if (mCurrentSlicedDuration.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mCurrentSlicedDuration.size() + 1;
-        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount);
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-            ALOGE("DurationMetric %s dropping data for dimension key %s", mName.c_str(),
-                  newKey.c_str());
+            ALOGE("DurationMetric %lld dropping data for dimension key %s",
+                (long long)mMetricId, newKey.c_str());
             return true;
         }
     }
@@ -245,7 +250,7 @@
 
 void DurationMetricProducer::onMatchedLogEventInternalLocked(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
-        const map<string, HashableDimensionKey>& conditionKeys, bool condition,
+        const ConditionKey& conditionKeys, bool condition,
         const LogEvent& event) {
     flushIfNeededLocked(event.GetTimestampNs());
 
@@ -256,7 +261,6 @@
         return;
     }
 
-    HashableDimensionKey atomKey(getDimensionKey(event, mInternalDimension));
 
     if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
         if (hitGuardRailLocked(eventKey)) {
@@ -267,11 +271,25 @@
 
     auto it = mCurrentSlicedDuration.find(eventKey);
 
-    if (matcherIndex == mStartIndex) {
-        it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys);
-    } else if (matcherIndex == mStopIndex) {
-        it->second->noteStop(atomKey, event.GetTimestampNs(), false);
+    std::vector<DimensionsValue> values = getDimensionKeys(event, mInternalDimensions);
+    if (values.empty()) {
+        if (matcherIndex == mStartIndex) {
+            it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
+                                  event.GetTimestampNs(), conditionKeys);
+        } else if (matcherIndex == mStopIndex) {
+            it->second->noteStop(DEFAULT_DIMENSION_KEY, event.GetTimestampNs(), false);
+        }
+    } else {
+        for (const DimensionsValue& value : values) {
+            if (matcherIndex == mStartIndex) {
+                it->second->noteStart(HashableDimensionKey(value), condition,
+                                      event.GetTimestampNs(), conditionKeys);
+            } else if (matcherIndex == mStopIndex) {
+                it->second->noteStop(HashableDimensionKey(value), event.GetTimestampNs(), false);
+            }
+        }
     }
+
 }
 
 size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 7044b4b..e06b9a1 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -20,6 +20,7 @@
 #include <unordered_map>
 
 #include <android/util/ProtoOutputStream.h>
+#include "../anomaly/DurationAnomalyTracker.h"
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
 #include "MetricProducer.h"
@@ -41,21 +42,22 @@
                            const int conditionIndex, const size_t startIndex,
                            const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
                            const sp<ConditionWizard>& wizard,
-                           const vector<KeyMatcher>& internalDimension, const uint64_t startTimeNs);
+                           const FieldMatcher& internalDimensions, const uint64_t startTimeNs);
 
     virtual ~DurationMetricProducer();
 
-    virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) override;
+    sp<AnomalyTracker> addAnomalyTracker(const Alert &alert) override;
 
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
-            const std::map<std::string, HashableDimensionKey>& conditionKeys, bool condition,
+            const ConditionKey& conditionKeys, bool condition,
             const LogEvent& event) override;
 
 private:
     void onDumpReportLocked(const uint64_t dumpTimeNs,
                             android::util::ProtoOutputStream* protoOutput) override;
+    void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override;
 
     // Internal interface to handle condition change.
     void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override;
@@ -84,7 +86,7 @@
     const bool mNested;
 
     // The dimension from the atom predicate. e.g., uid, wakelock name.
-    const vector<KeyMatcher> mInternalDimension;
+    const FieldMatcher mInternalDimensions;
 
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
     // TODO: Add a lock to mPastBuckets.
@@ -98,6 +100,9 @@
     std::unique_ptr<DurationTracker> createDurationTracker(
             const HashableDimensionKey& eventKey) const;
 
+    // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
+    std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
+
     // Util function to check whether the specified dimension hits the guardrail.
     bool hitGuardRailLocked(const HashableDimensionKey& newKey);
 
@@ -105,6 +110,7 @@
 
     FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition);
     FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition);
+    FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 6a072b0..821d8ea 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -41,7 +41,7 @@
 namespace statsd {
 
 // for StatsLogReport
-const int FIELD_ID_NAME = 1;
+const int FIELD_ID_ID = 1;
 const int FIELD_ID_START_REPORT_NANOS = 2;
 const int FIELD_ID_END_REPORT_NANOS = 3;
 const int FIELD_ID_EVENT_METRICS = 4;
@@ -55,7 +55,7 @@
                                          const int conditionIndex,
                                          const sp<ConditionWizard>& wizard,
                                          const uint64_t startTimeNs)
-    : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard) {
+    : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) {
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
                                metric.links().end());
@@ -64,7 +64,7 @@
 
     startNewProtoOutputStreamLocked();
 
-    VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(),
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
 
@@ -96,14 +96,19 @@
     return buffer;
 }
 
+void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
+
+}
+
 void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
                                              ProtoOutputStream* protoOutput) {
-    protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
 
     size_t bufferSize = mProto->size();
-    VLOG("metric %s dump report now... proto size: %zu ", mName.c_str(), bufferSize);
+    VLOG("metric %lld dump report now... proto size: %zu ",
+        (long long)mMetricId, bufferSize);
     std::unique_ptr<std::vector<uint8_t>> buffer = serializeProtoLocked(*mProto);
 
     protoOutput->write(FIELD_TYPE_MESSAGE | FIELD_ID_EVENT_METRICS,
@@ -115,13 +120,13 @@
 
 void EventMetricProducer::onConditionChangedLocked(const bool conditionMet,
                                                    const uint64_t eventTime) {
-    VLOG("Metric %s onConditionChanged", mName.c_str());
+    VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
     mCondition = conditionMet;
 }
 
 void EventMetricProducer::onMatchedLogEventInternalLocked(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
-        const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+        const ConditionKey& conditionKey, bool condition,
         const LogEvent& event) {
     if (!condition) {
         return;
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 6120ad8..a57b07d 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -46,11 +46,12 @@
 private:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
-            const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+            const ConditionKey& conditionKey, bool condition,
             const LogEvent& event) override;
 
     void onDumpReportLocked(const uint64_t dumpTimeNs,
                             android::util::ProtoOutputStream* protoOutput) override;
+    void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override;
 
     // Internal interface to handle condition change.
     void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override;
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 47cca0e..1a4888c 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -19,6 +19,8 @@
 
 #include "GaugeMetricProducer.h"
 #include "guardrail/StatsdStats.h"
+#include "dimension.h"
+#include "stats_log_util.h"
 
 #include <cutils/log.h>
 
@@ -42,7 +44,7 @@
 namespace statsd {
 
 // for StatsLogReport
-const int FIELD_ID_NAME = 1;
+const int FIELD_ID_ID = 1;
 const int FIELD_ID_START_REPORT_NANOS = 2;
 const int FIELD_ID_END_REPORT_NANOS = 3;
 const int FIELD_ID_GAUGE_METRICS = 8;
@@ -51,12 +53,6 @@
 // for GaugeMetricData
 const int FIELD_ID_DIMENSION = 1;
 const int FIELD_ID_BUCKET_INFO = 2;
-// for KeyValuePair
-const int FIELD_ID_KEY = 1;
-const int FIELD_ID_VALUE_STR = 2;
-const int FIELD_ID_VALUE_INT = 3;
-const int FIELD_ID_VALUE_BOOL = 4;
-const int FIELD_ID_VALUE_FLOAT = 5;
 // for GaugeBucketInfo
 const int FIELD_ID_START_BUCKET_NANOS = 1;
 const int FIELD_ID_END_BUCKET_NANOS = 2;
@@ -64,25 +60,26 @@
 
 GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& metric,
                                          const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard, const int atomTagId,
+                                         const sp<ConditionWizard>& wizard,
                                          const int pullTagId, const uint64_t startTimeNs,
                                          shared_ptr<StatsPullerManager> statsPullerManager)
-    : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard),
+    : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard),
       mStatsPullerManager(statsPullerManager),
-      mPullTagId(pullTagId),
-      mAtomTagId(atomTagId) {
-    if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
-        mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
+      mPullTagId(pullTagId) {
+    mCurrentSlicedBucket = std::make_shared<DimToGaugeFieldsMap>();
+    mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
+    int64_t bucketSizeMills = 0;
+    if (metric.has_bucket()) {
+        bucketSizeMills = TimeUnitToBucketSizeInMillis(metric.bucket());
     } else {
-        mBucketSizeNs = kDefaultGaugemBucketSizeNs;
+        bucketSizeMills = TimeUnitToBucketSizeInMillis(ONE_HOUR);
     }
+    mBucketSizeNs = bucketSizeMills * 1000000;
 
-    for (int i = 0; i < metric.gauge_fields().field_num_size(); i++) {
-        mGaugeFields.push_back(metric.gauge_fields().field_num(i));
-    }
+    mFieldFilter = metric.gauge_fields_filter();
 
     // TODO: use UidMap if uid->pkg_name is required
-    mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+    mDimensions = metric.dimensions();
 
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
@@ -92,11 +89,10 @@
 
     // Kicks off the puller immediately.
     if (mPullTagId != -1) {
-        mStatsPullerManager->RegisterReceiver(mPullTagId, this,
-                                             metric.bucket().bucket_size_millis());
+        mStatsPullerManager->RegisterReceiver(mPullTagId, this, bucketSizeMills);
     }
 
-    VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(),
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
 
@@ -104,8 +100,8 @@
 GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& metric,
                                          const int conditionIndex,
                                          const sp<ConditionWizard>& wizard, const int pullTagId,
-                                         const int atomTagId, const int64_t startTimeNs)
-    : GaugeMetricProducer(key, metric, conditionIndex, wizard, pullTagId, atomTagId, startTimeNs,
+                                         const int64_t startTimeNs)
+    : GaugeMetricProducer(key, metric, conditionIndex, wizard, pullTagId, startTimeNs,
                           make_shared<StatsPullerManager>()) {
 }
 
@@ -116,40 +112,32 @@
     }
 }
 
+void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
+    flushIfNeededLocked(dumpTimeNs);
+}
+
 void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
                                              ProtoOutputStream* protoOutput) {
-    VLOG("gauge metric %s dump report now...", mName.c_str());
+    VLOG("gauge metric %lld report now...", (long long)mMetricId);
 
     flushIfNeededLocked(dumpTimeNs);
 
-    protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
     long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS);
 
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
-        const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
 
         VLOG("  dimension key %s", hashableKey.c_str());
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
-        // First fill dimension (KeyValuePairs).
-        for (const auto& kv : kvs) {
-            long long dimensionToken = protoOutput->start(
-                    FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
-            protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
-            if (kv.has_value_str()) {
-                protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
-            } else if (kv.has_value_int()) {
-                protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
-            } else if (kv.has_value_bool()) {
-                protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
-            } else if (kv.has_value_float()) {
-                protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
-            }
-            protoOutput->end(dimensionToken);
-        }
+        // First fill dimension.
+        long long dimensionToken = protoOutput->start(
+                FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
+        writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+        protoOutput->end(dimensionToken);
 
         // Then fill bucket_info (GaugeBucketInfo).
         for (const auto& bucket : pair.second) {
@@ -160,25 +148,11 @@
             protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
                                (long long)bucket.mBucketEndNs);
             long long atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM);
-            long long eventToken = protoOutput->start(FIELD_TYPE_MESSAGE | mAtomTagId);
-            for (const auto& pair : bucket.mEvent->kv) {
-                if (pair.has_value_int()) {
-                    protoOutput->write(FIELD_TYPE_INT32 | pair.key(), pair.value_int());
-                } else if (pair.has_value_long()) {
-                    protoOutput->write(FIELD_TYPE_INT64 | pair.key(), pair.value_long());
-                } else if (pair.has_value_str()) {
-                    protoOutput->write(FIELD_TYPE_STRING | pair.key(), pair.value_str());
-                } else if (pair.has_value_long()) {
-                    protoOutput->write(FIELD_TYPE_FLOAT | pair.key(), pair.value_float());
-                } else if (pair.has_value_bool()) {
-                    protoOutput->write(FIELD_TYPE_BOOL | pair.key(), pair.value_bool());
-                }
-            }
-            protoOutput->end(eventToken);
+            writeFieldValueTreeToStream(*bucket.mGaugeFields, protoOutput);
             protoOutput->end(atomToken);
             protoOutput->end(bucketInfoToken);
-            VLOG("\t bucket [%lld - %lld] content: %s", (long long)bucket.mBucketStartNs,
-                 (long long)bucket.mBucketEndNs, bucket.mEvent->ToString().c_str());
+            VLOG("\t bucket [%lld - %lld] includes %d gauge fields.", (long long)bucket.mBucketStartNs,
+                 (long long)bucket.mBucketEndNs, (int)bucket.mGaugeFields->size());
         }
         protoOutput->end(wrapperToken);
     }
@@ -192,7 +166,7 @@
 
 void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet,
                                                    const uint64_t eventTime) {
-    VLOG("Metric %s onConditionChanged", mName.c_str());
+    VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
     flushIfNeededLocked(eventTime);
     mCondition = conditionMet;
 
@@ -220,21 +194,16 @@
 }
 
 void GaugeMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) {
-    VLOG("Metric %s onSlicedConditionMayChange", mName.c_str());
+    VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
 }
 
-shared_ptr<EventKV> GaugeMetricProducer::getGauge(const LogEvent& event) {
-    shared_ptr<EventKV> ret = make_shared<EventKV>();
-    if (mGaugeFields.size() == 0) {
-        for (int i = 1; i <= event.size(); i++) {
-            ret->kv.push_back(event.GetKeyValueProto(i));
-        }
-    } else {
-        for (int i = 0; i < (int)mGaugeFields.size(); i++) {
-            ret->kv.push_back(event.GetKeyValueProto(mGaugeFields[i]));
-        }
+std::shared_ptr<FieldValueMap> GaugeMetricProducer::getGaugeFields(const LogEvent& event) {
+    std::shared_ptr<FieldValueMap> gaugeFields =
+        std::make_shared<FieldValueMap>(event.getFieldValueMap());
+    if (!mFieldFilter.include_all()) {
+        filterFields(mFieldFilter.fields(), gaugeFields.get());
     }
-    return ret;
+    return gaugeFields;
 }
 
 void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
@@ -254,11 +223,11 @@
     // 1. Report the tuple count if the tuple count > soft limit
     if (mCurrentSlicedBucket->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mCurrentSlicedBucket->size() + 1;
-        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount);
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-            ALOGE("GaugeMetric %s dropping data for dimension key %s", mName.c_str(),
-                  newKey.c_str());
+            ALOGE("GaugeMetric %lld dropping data for dimension key %s",
+                (long long)mMetricId, newKey.c_str());
             return true;
         }
     }
@@ -268,7 +237,7 @@
 
 void GaugeMetricProducer::onMatchedLogEventInternalLocked(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
-        const map<string, HashableDimensionKey>& conditionKey, bool condition,
+        const ConditionKey& conditionKey, bool condition,
         const LogEvent& event) {
     if (condition == false) {
         return;
@@ -285,21 +254,21 @@
     if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end()) {
         return;
     }
-    shared_ptr<EventKV> gauge = getGauge(event);
+    std::shared_ptr<FieldValueMap> gaugeFields = getGaugeFields(event);
     if (hitGuardRailLocked(eventKey)) {
         return;
     }
-    (*mCurrentSlicedBucket)[eventKey] = gauge;
+    (*mCurrentSlicedBucket)[eventKey] = gaugeFields;
     // Anomaly detection on gauge metric only works when there is one numeric
     // field specified.
     if (mAnomalyTrackers.size() > 0) {
-        if (gauge->kv.size() == 1) {
-            KeyValuePair pair = gauge->kv[0];
+        if (gaugeFields->size() == 1) {
+            const DimensionsValue& dimensionsValue = gaugeFields->begin()->second;
             long gaugeVal = 0;
-            if (pair.has_value_int()) {
-                gaugeVal = (long)pair.value_int();
-            } else if (pair.has_value_long()) {
-                gaugeVal = pair.value_long();
+            if (dimensionsValue.has_value_int()) {
+                gaugeVal = (long)dimensionsValue.value_int();
+            } else if (dimensionsValue.has_value_long()) {
+                gaugeVal = dimensionsValue.value_long();
             }
             for (auto& tracker : mAnomalyTrackers) {
                 tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey,
@@ -313,12 +282,12 @@
     mCurrentSlicedBucketForAnomaly->clear();
     status_t err = NO_ERROR;
     for (const auto& slice : *mCurrentSlicedBucket) {
-        KeyValuePair pair = slice.second->kv[0];
+        const DimensionsValue& dimensionsValue = slice.second->begin()->second;
         long gaugeVal = 0;
-        if (pair.has_value_int()) {
-            gaugeVal = (long)pair.value_int();
-        } else if (pair.has_value_long()) {
-            gaugeVal = pair.value_long();
+        if (dimensionsValue.has_value_int()) {
+            gaugeVal = (long)dimensionsValue.value_int();
+        } else if (dimensionsValue.has_value_long()) {
+            gaugeVal = dimensionsValue.value_long();
         }
         (*mCurrentSlicedBucketForAnomaly)[slice.first] = gaugeVal;
     }
@@ -342,11 +311,11 @@
     info.mBucketNum = mCurrentBucketNum;
 
     for (const auto& slice : *mCurrentSlicedBucket) {
-        info.mEvent = slice.second;
+        info.mGaugeFields = slice.second;
         auto& bucketList = mPastBuckets[slice.first];
         bucketList.push_back(info);
-        VLOG("gauge metric %s, dump key value: %s -> %s", mName.c_str(),
-             slice.first.c_str(), slice.second->ToString().c_str());
+        VLOG("gauge metric %lld, dump key value: %s",
+            (long long)mMetricId, slice.first.c_str());
     }
 
     // Reset counters
@@ -357,13 +326,14 @@
         }
     }
 
-    mCurrentSlicedBucket = std::make_shared<DimToEventKVMap>();
+    mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
+    mCurrentSlicedBucket = std::make_shared<DimToGaugeFieldsMap>();
 
     // Adjusts the bucket start time
     int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
     mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
     mCurrentBucketNum += numBucketsForward;
-    VLOG("metric %s: new bucket start time: %lld", mName.c_str(),
+    VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
          (long long)mCurrentBucketStartTimeNs);
 }
 
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 19d51e8..f267e98 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -35,10 +35,13 @@
 struct GaugeBucket {
     int64_t mBucketStartNs;
     int64_t mBucketEndNs;
-    std::shared_ptr<EventKV> mEvent;
+    std::shared_ptr<FieldValueMap> mGaugeFields;
     uint64_t mBucketNum;
 };
 
+typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<FieldValueMap>>
+    DimToGaugeFieldsMap;
+
 // This gauge metric producer first register the puller to automatically pull the gauge at the
 // beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise
 // proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric
@@ -47,7 +50,7 @@
 public:
     GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& countMetric,
                         const int conditionIndex, const sp<ConditionWizard>& wizard,
-                        const int pullTagId, const int atomTagId, const int64_t startTimeNs);
+                        const int pullTagId, const int64_t startTimeNs);
 
     virtual ~GaugeMetricProducer();
 
@@ -57,17 +60,18 @@
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
-            const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+            const ConditionKey& conditionKey, bool condition,
             const LogEvent& event) override;
 
 private:
     void onDumpReportLocked(const uint64_t dumpTimeNs,
                             android::util::ProtoOutputStream* protoOutput) override;
+    void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override;
 
     // for testing
     GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric,
                         const int conditionIndex, const sp<ConditionWizard>& wizard,
-                        const int pullTagId, const int atomTagId, const uint64_t startTimeNs,
+                        const int pullTagId, const uint64_t startTimeNs,
                         std::shared_ptr<StatsPullerManager> statsPullerManager);
 
     // Internal interface to handle condition change.
@@ -82,9 +86,6 @@
     // Util function to flush the old packet.
     void flushIfNeededLocked(const uint64_t& eventTime);
 
-    // The default bucket size for gauge metric is 1 hr.
-    static const uint64_t kDefaultGaugemBucketSizeNs = 60ULL * 60 * 1000 * 1000 * 1000;
-
     std::shared_ptr<StatsPullerManager> mStatsPullerManager;
     // tagId for pulled data. -1 if this is not pulled
     const int mPullTagId;
@@ -94,21 +95,19 @@
     std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
 
     // The current bucket.
-    std::shared_ptr<DimToEventKVMap> mCurrentSlicedBucket = std::make_shared<DimToEventKVMap>();
+    std::shared_ptr<DimToGaugeFieldsMap> mCurrentSlicedBucket;
 
     // The current bucket for anomaly detection.
-    std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
+    std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly;
 
     // Translate Atom based bucket to single numeric value bucket for anomaly
     void updateCurrentSlicedBucketForAnomaly();
 
-    int mAtomTagId;
-
     // Whitelist of fields to report. Empty means all are reported.
-    std::vector<int> mGaugeFields;
+    FieldFilter mFieldFilter;
 
     // apply a whitelist on the original input
-    std::shared_ptr<EventKV> getGauge(const LogEvent& event);
+    std::shared_ptr<FieldValueMap> getGaugeFields(const LogEvent& event);
 
     // Util function to check whether the specified dimension hits the guardrail.
     bool hitGuardRailLocked(const HashableDimensionKey& newKey);
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 5286908..d620a7e 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -28,24 +28,14 @@
         return;
     }
 
-    HashableDimensionKey eventKey;
-
-    if (mDimension.size() > 0) {
-        vector<KeyValuePair> key = getDimensionKey(event, mDimension);
-        eventKey = HashableDimensionKey(key);
-    } else {
-        eventKey = DEFAULT_DIMENSION_KEY;
-    }
-
     bool condition;
-
-    map<string, HashableDimensionKey> conditionKeys;
+    ConditionKey conditionKey;
     if (mConditionSliced) {
         for (const auto& link : mConditionLinks) {
-            HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
-            conditionKeys[link.condition()] = conditionKey;
+            conditionKey.insert(std::make_pair(link.condition(),
+                                               getDimensionKeysForCondition(event, link)));
         }
-        if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) {
+        if (mWizard->query(mConditionTrackerIndex, conditionKey) != ConditionState::kTrue) {
             condition = false;
         } else {
             condition = true;
@@ -53,7 +43,17 @@
     } else {
         condition = mCondition;
     }
-    onMatchedLogEventInternalLocked(matcherIndex, eventKey, conditionKeys, condition, event);
+
+    if (mDimensions.child_size() > 0) {
+        vector<DimensionsValue> dimensionValues = getDimensionKeys(event, mDimensions);
+        for (const DimensionsValue& dimensionValue : dimensionValues) {
+            onMatchedLogEventInternalLocked(
+                matcherIndex, HashableDimensionKey(dimensionValue), conditionKey, condition, event);
+        }
+    } else {
+        onMatchedLogEventInternalLocked(
+            matcherIndex, DEFAULT_DIMENSION_KEY, conditionKey, condition, event);
+    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 85ef4ad..3779c44 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -39,9 +39,9 @@
 // be a no-op.
 class MetricProducer : public virtual PackageInfoListener {
 public:
-    MetricProducer(const std::string& name, const ConfigKey& key, const int64_t startTimeNs,
+    MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t startTimeNs,
                    const int conditionIndex, const sp<ConditionWizard>& wizard)
-        : mName(name),
+        : mMetricId(metricId),
           mConfigKey(key),
           mStartTimeNs(startTimeNs),
           mCurrentBucketStartTimeNs(startTimeNs),
@@ -50,6 +50,7 @@
           mConditionSliced(false),
           mWizard(wizard),
           mConditionTrackerIndex(conditionIndex){};
+
     virtual ~MetricProducer(){};
 
     void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override{
@@ -90,6 +91,10 @@
         std::lock_guard<std::mutex> lock(mMutex);
         return onDumpReportLocked(dumpTimeNs, protoOutput);
     }
+    void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return onDumpReportLocked(dumpTimeNs, report);
+    }
 
     // Returns the memory in bytes currently used to store this metric's data. Does not change
     // state.
@@ -98,13 +103,13 @@
         return byteSizeLocked();
     }
 
-    virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) {
-        return new AnomalyTracker(alert, mConfigKey);
-    }
-
-    void addAnomalyTracker(sp<AnomalyTracker> tracker) {
+    virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert) {
         std::lock_guard<std::mutex> lock(mMutex);
-        mAnomalyTrackers.push_back(tracker);
+        sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
+        if (anomalyTracker != nullptr) {
+            mAnomalyTrackers.push_back(anomalyTracker);
+        }
+        return anomalyTracker;
     }
 
     int64_t getBuckeSizeInNs() const {
@@ -112,14 +117,19 @@
         return mBucketSizeNs;
     }
 
+    inline const int64_t& getMetricId() {
+        return mMetricId;
+    }
+
 protected:
     virtual void onConditionChangedLocked(const bool condition, const uint64_t eventTime) = 0;
     virtual void onSlicedConditionMayChangeLocked(const uint64_t eventTime) = 0;
     virtual void onDumpReportLocked(const uint64_t dumpTimeNs,
                                     android::util::ProtoOutputStream* protoOutput) = 0;
+    virtual void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) = 0;
     virtual size_t byteSizeLocked() const = 0;
 
-    const std::string mName;
+    const int64_t mMetricId;
 
     const ConfigKey mConfigKey;
 
@@ -140,7 +150,7 @@
 
     int mConditionTrackerIndex;
 
-    std::vector<KeyMatcher> mDimension;  // The dimension defined in statsd_config
+    FieldMatcher mDimensions;  // The dimension defined in statsd_config
 
     std::vector<MetricConditionLink> mConditionLinks;
 
@@ -163,7 +173,7 @@
      */
     virtual void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
-            const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+            const ConditionKey& conditionKey, bool condition,
             const LogEvent& event) = 0;
 
     // Consume the parsed stats log entry that already matched the "what" of the metric.
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 231bd8e..f929517 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -49,11 +49,11 @@
                                const long timeBaseSec, sp<UidMap> uidMap)
     : mConfigKey(key), mUidMap(uidMap) {
     mConfigValid =
-            initStatsdConfig(key, config, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
+            initStatsdConfig(key, config, *uidMap, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
                              mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
-                             mTrackerToMetricMap, mTrackerToConditionMap);
+                             mTrackerToMetricMap, mTrackerToConditionMap, mNoReportMetricIds);
 
-    if (!config.has_log_source()) {
+    if (config.allowed_log_source_size() == 0) {
         // TODO(b/70794411): uncomment the following line and remove the hard coded log source
         // after all configs have the log source added.
         // mConfigValid = false;
@@ -63,10 +63,14 @@
         mAllowedUid.push_back(0);
         mAllowedLogSources.insert(mAllowedUid.begin(), mAllowedUid.end());
     } else {
-        mAllowedUid.insert(mAllowedUid.begin(), config.log_source().uid().begin(),
-                           config.log_source().uid().end());
-        mAllowedPkg.insert(mAllowedPkg.begin(), config.log_source().package().begin(),
-                           config.log_source().package().end());
+        for (const auto& source : config.allowed_log_source()) {
+            auto it = UidMap::sAidToUidMapping.find(source);
+            if (it != UidMap::sAidToUidMapping.end()) {
+                mAllowedUid.push_back(it->second);
+            } else {
+                mAllowedPkg.push_back(source);
+            }
+        }
 
         if (mAllowedUid.size() + mAllowedPkg.size() > StatsdStats::kMaxLogSourceCount) {
             ALOGE("Too many log sources. This is likely to be an error in the config.");
@@ -142,15 +146,25 @@
     initLogSourceWhiteList();
 }
 
+void MetricsManager::onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report) {
+    for (const auto& producer : mAllMetricProducers) {
+        if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) {
+            producer->onDumpReport(dumpTimeStampNs, report->add_metrics());
+        }
+    }
+}
+
 void MetricsManager::onDumpReport(ProtoOutputStream* protoOutput) {
     VLOG("=========================Metric Reports Start==========================");
     uint64_t dumpTimeStampNs = time(nullptr) * NS_PER_SEC;
     // one StatsLogReport per MetricProduer
-    for (auto& metric : mAllMetricProducers) {
-        long long token =
-                protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS);
-        metric->onDumpReport(dumpTimeStampNs, protoOutput);
-        protoOutput->end(token);
+    for (const auto& producer : mAllMetricProducers) {
+        if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) {
+            long long token =
+                    protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS);
+            producer->onDumpReport(dumpTimeStampNs, protoOutput);
+            protoOutput->end(token);
+        }
     }
     VLOG("=========================Metric Reports End==========================");
 }
@@ -167,6 +181,22 @@
             VLOG("log source %d not on the whitelist", event.GetUid());
             return;
         }
+    } else { // Check that app hook fields are valid.
+        // TODO: Find a way to make these checks easier to maintain if the app hooks get changed.
+
+        // Label is 2nd from last field and must be from [0, 15].
+        status_t err = NO_ERROR;
+        long label = event.GetLong(event.size()-1, &err);
+        if (err != NO_ERROR || label < 0 || label > 15) {
+            VLOG("App hook does not have valid label %ld", label);
+            return;
+        }
+        // The state must be from 0,3. This part of code must be manually updated.
+        long apphookState = event.GetLong(event.size(), &err);
+        if (err != NO_ERROR || apphookState < 0 || apphookState > 3) {
+            VLOG("App hook does not have valid state %ld", apphookState);
+            return;
+        }
     }
 
     int tagId = event.GetTagId();
@@ -234,7 +264,7 @@
     for (size_t i = 0; i < mAllAtomMatchers.size(); i++) {
         if (matcherCache[i] == MatchingState::kMatched) {
             StatsdStats::getInstance().noteMatcherMatched(mConfigKey,
-                                                          mAllAtomMatchers[i]->getName());
+                                                          mAllAtomMatchers[i]->getId());
             auto pair = mTrackerToMetricMap.find(i);
             if (pair != mTrackerToMetricMap.end()) {
                 auto& metricList = pair->second;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 8faa75d..08b0981 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -45,8 +45,9 @@
 
     void onLogEvent(const LogEvent& event);
 
-    void onAnomalyAlarmFired(const uint64_t timestampNs,
-                         unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet);
+    void onAnomalyAlarmFired(
+        const uint64_t timestampNs,
+        unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet);
 
     void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
 
@@ -56,8 +57,13 @@
 
     void onUidMapReceived() override;
 
+    bool shouldAddUidMapListener() const {
+        return !mAllowedPkg.empty();
+    }
+
     // Config source owner can call onDumpReport() to get all the metrics collected.
     virtual void onDumpReport(android::util::ProtoOutputStream* protoOutput);
+    virtual void onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report);
 
     // Computes the total byte size of all metrics managed by a single config source.
     // Does not change the state.
@@ -128,6 +134,13 @@
     std::unordered_map<int, std::vector<int>> mConditionToMetricMap;
 
     void initLogSourceWhiteList();
+
+    // The metrics that don't need to be uploaded or even reported.
+    std::set<int64_t> mNoReportMetricIds;
+
+    FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
+    FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
+    FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 40aed7b..74bd6f9 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -17,8 +17,10 @@
 #define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
+#include "dimension.h"
 #include "ValueMetricProducer.h"
 #include "guardrail/StatsdStats.h"
+#include "stats_log_util.h"
 
 #include <cutils/log.h>
 #include <limits.h>
@@ -45,7 +47,7 @@
 namespace statsd {
 
 // for StatsLogReport
-const int FIELD_ID_NAME = 1;
+const int FIELD_ID_ID = 1;
 const int FIELD_ID_START_REPORT_NANOS = 2;
 const int FIELD_ID_END_REPORT_NANOS = 3;
 const int FIELD_ID_VALUE_METRICS = 7;
@@ -54,37 +56,31 @@
 // for ValueMetricData
 const int FIELD_ID_DIMENSION = 1;
 const int FIELD_ID_BUCKET_INFO = 2;
-// for KeyValuePair
-const int FIELD_ID_KEY = 1;
-const int FIELD_ID_VALUE_STR = 2;
-const int FIELD_ID_VALUE_INT = 3;
-const int FIELD_ID_VALUE_BOOL = 4;
-const int FIELD_ID_VALUE_FLOAT = 5;
 // for ValueBucketInfo
 const int FIELD_ID_START_BUCKET_NANOS = 1;
 const int FIELD_ID_END_BUCKET_NANOS = 2;
 const int FIELD_ID_VALUE = 3;
 
-static const uint64_t kDefaultBucketSizeMillis = 60 * 60 * 1000L;
-
 // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
 ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric& metric,
                                          const int conditionIndex,
                                          const sp<ConditionWizard>& wizard, const int pullTagId,
                                          const uint64_t startTimeNs,
                                          shared_ptr<StatsPullerManager> statsPullerManager)
-    : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard),
+    : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard),
       mValueField(metric.value_field()),
       mStatsPullerManager(statsPullerManager),
       mPullTagId(pullTagId) {
     // TODO: valuemetric for pushed events may need unlimited bucket length
-    if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
-        mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
+    int64_t bucketSizeMills = 0;
+    if (metric.has_bucket()) {
+        bucketSizeMills = TimeUnitToBucketSizeInMillis(metric.bucket());
     } else {
-        mBucketSizeNs = kDefaultBucketSizeMillis * 1000 * 1000;
+        bucketSizeMills = TimeUnitToBucketSizeInMillis(ONE_HOUR);
     }
 
-    mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+    mBucketSizeNs = bucketSizeMills * 1000000;
+    mDimensions = metric.dimensions();
 
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
@@ -94,11 +90,10 @@
 
     if (!metric.has_condition() && mPullTagId != -1) {
         VLOG("Setting up periodic pulling for %d", mPullTagId);
-        mStatsPullerManager->RegisterReceiver(mPullTagId, this,
-                                              metric.bucket().bucket_size_millis());
+        mStatsPullerManager->RegisterReceiver(mPullTagId, this, bucketSizeMills);
     }
-    VLOG("value metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(),
-         (long long)mBucketSizeNs, (long long)mStartTimeNs);
+    VLOG("value metric %lld created. bucket size %lld start_time: %lld",
+        (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
 
 // for testing
@@ -118,40 +113,45 @@
 }
 
 void ValueMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) {
-    VLOG("Metric %s onSlicedConditionMayChange", mName.c_str());
+    VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
+}
+
+void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
+    flushIfNeededLocked(dumpTimeNs);
+    report->set_metric_id(mMetricId);
+    report->set_start_report_nanos(mStartTimeNs);
+    auto value_metrics = report->mutable_value_metrics();
+    for (const auto& pair : mPastBuckets) {
+        ValueMetricData* metricData = value_metrics->add_data();
+        *metricData->mutable_dimension() = pair.first.getDimensionsValue();
+        for (const auto& bucket : pair.second) {
+            ValueBucketInfo* bucketInfo = metricData->add_bucket_info();
+            bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
+            bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs);
+            bucketInfo->set_value(bucket.mValue);
+        }
+    }
 }
 
 void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
                                              ProtoOutputStream* protoOutput) {
-    VLOG("metric %s dump report now...", mName.c_str());
+    VLOG("metric %lld dump report now...", (long long)mMetricId);
     flushIfNeededLocked(dumpTimeNs);
-    protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
     long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
 
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
         VLOG("  dimension key %s", hashableKey.c_str());
-        const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
-        // First fill dimension (KeyValuePairs).
-        for (const auto& kv : kvs) {
-            long long dimensionToken = protoOutput->start(
-                    FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
-            protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
-            if (kv.has_value_str()) {
-                protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
-            } else if (kv.has_value_int()) {
-                protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
-            } else if (kv.has_value_bool()) {
-                protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
-            } else if (kv.has_value_float()) {
-                protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
-            }
-            protoOutput->end(dimensionToken);
-        }
+        // First fill dimension.
+        long long dimensionToken = protoOutput->start(
+                FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
+        writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+        protoOutput->end(dimensionToken);
 
         // Then fill bucket_info (ValueBucketInfo).
         for (const auto& bucket : pair.second) {
@@ -171,7 +171,7 @@
     protoOutput->end(protoToken);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
 
-    VLOG("metric %s dump report now...", mName.c_str());
+    VLOG("metric %lld dump report now...", (long long)mMetricId);
     mPastBuckets.clear();
     mStartTimeNs = mCurrentBucketStartTimeNs;
     // TODO: Clear mDimensionKeyMap once the report is dumped.
@@ -218,7 +218,8 @@
         // For scheduled pulled data, the effective event time is snap to the nearest
         // bucket boundary to make bucket finalize.
         uint64_t realEventTime = allData.at(0)->GetTimestampNs();
-        uint64_t eventTime = mStartTimeNs + ((realEventTime - mStartTimeNs)/mBucketSizeNs) * mBucketSizeNs;
+        uint64_t eventTime = mStartTimeNs +
+            ((realEventTime - mStartTimeNs)/mBucketSizeNs) * mBucketSizeNs;
 
         mCondition = false;
         for (const auto& data : allData) {
@@ -242,11 +243,11 @@
     }
     if (mCurrentSlicedBucket.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mCurrentSlicedBucket.size() + 1;
-        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount);
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-            ALOGE("ValueMetric %s dropping data for dimension key %s", mName.c_str(),
-                  newKey.c_str());
+            ALOGE("ValueMetric %lld dropping data for dimension key %s",
+                (long long)mMetricId, newKey.c_str());
             return true;
         }
     }
@@ -256,7 +257,7 @@
 
 void ValueMetricProducer::onMatchedLogEventInternalLocked(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
-        const map<string, HashableDimensionKey>& conditionKey, bool condition,
+        const ConditionKey& conditionKey, bool condition,
         const LogEvent& event) {
     uint64_t eventTimeNs = event.GetTimestampNs();
     if (eventTimeNs < mCurrentBucketStartTimeNs) {
@@ -272,7 +273,11 @@
     }
     Interval& interval = mCurrentSlicedBucket[eventKey];
 
-    long value = get_value(event);
+    std::shared_ptr<FieldValueMap> valueFieldMap = getValueFields(event);
+    if (valueFieldMap->empty() || valueFieldMap->size() > 1) {
+        return;
+    }
+    const long value = getLongFromDimenValue(valueFieldMap->begin()->second);
 
     if (mPullTagId != -1) { // for pulled events
         if (mCondition == true) {
@@ -296,15 +301,11 @@
     }
 }
 
-long ValueMetricProducer::get_value(const LogEvent& event) {
-    status_t err = NO_ERROR;
-    long val = event.GetLong(mValueField, &err);
-    if (err == NO_ERROR) {
-        return val;
-    } else {
-        VLOG("Can't find value in message. %s", event.ToString().c_str());
-        return 0;
-    }
+std::shared_ptr<FieldValueMap> ValueMetricProducer::getValueFields(const LogEvent& event) {
+    std::shared_ptr<FieldValueMap> valueFields =
+        std::make_shared<FieldValueMap>(event.getFieldValueMap());
+    filterFields(mValueField, valueFields.get());
+    return valueFields;
 }
 
 void ValueMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
@@ -346,7 +347,7 @@
     if (numBucketsForward > 1) {
         VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
     }
-    VLOG("metric %s: new bucket start time: %lld", mName.c_str(),
+    VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
          (long long)mCurrentBucketStartTimeNs);
 }
 
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 2f27e4e..3e7032d 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -50,12 +50,13 @@
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
-            const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+            const ConditionKey& conditionKey, bool condition,
             const LogEvent& event) override;
 
 private:
     void onDumpReportLocked(const uint64_t dumpTimeNs,
                             android::util::ProtoOutputStream* protoOutput) override;
+    void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override;
 
     // Internal interface to handle condition change.
     void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override;
@@ -69,7 +70,7 @@
     // Util function to flush the old packet.
     void flushIfNeededLocked(const uint64_t& eventTime);
 
-    const int32_t mValueField;
+    const FieldMatcher mValueField;
 
     std::shared_ptr<StatsPullerManager> mStatsPullerManager;
 
@@ -102,7 +103,7 @@
     // TODO: Add a lock to mPastBuckets.
     std::unordered_map<HashableDimensionKey, std::vector<ValueBucket>> mPastBuckets;
 
-    long get_value(const LogEvent& event);
+    std::shared_ptr<FieldValueMap> getValueFields(const LogEvent& event);
 
     // Util function to check whether the specified dimension hits the guardrail.
     bool hitGuardRailLocked(const HashableDimensionKey& newKey);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 3c714b3..371460e 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -17,7 +17,7 @@
 #ifndef DURATION_TRACKER_H
 #define DURATION_TRACKER_H
 
-#include "anomaly/AnomalyTracker.h"
+#include "anomaly/DurationAnomalyTracker.h"
 #include "condition/ConditionWizard.h"
 #include "config/ConfigKey.h"
 #include "stats_util.h"
@@ -60,12 +60,12 @@
 
 class DurationTracker {
 public:
-    DurationTracker(const ConfigKey& key, const string& name, const HashableDimensionKey& eventKey,
+    DurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey,
                     sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
-                    uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
-                    const std::vector<sp<AnomalyTracker>>& anomalyTrackers)
+                    uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced,
+                    const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
         : mConfigKey(key),
-          mName(name),
+          mTrackerId(id),
           mEventKey(eventKey),
           mWizard(wizard),
           mConditionTrackerIndex(conditionIndex),
@@ -74,6 +74,7 @@
           mCurrentBucketStartTimeNs(currentBucketStartNs),
           mDuration(0),
           mCurrentBucketNum(0),
+          mConditionSliced(conditionSliced),
           mAnomalyTrackers(anomalyTrackers){};
 
     virtual ~DurationTracker(){};
@@ -94,7 +95,7 @@
             std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) = 0;
 
     // Predict the anomaly timestamp given the current status.
-    virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+    virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
                                               const uint64_t currentTimestamp) const = 0;
 
 protected:
@@ -145,7 +146,7 @@
     // A reference to the DurationMetricProducer's config key.
     const ConfigKey& mConfigKey;
 
-    const std::string mName;
+    const int64_t mTrackerId;
 
     HashableDimensionKey mEventKey;
 
@@ -163,10 +164,13 @@
 
     uint64_t mCurrentBucketNum;
 
-    std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
+    const bool mConditionSliced;
+
+    std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
 
     FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
-    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 6050f43..0c99391 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -24,13 +24,14 @@
 namespace os {
 namespace statsd {
 
-MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const string& name,
+MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
                                        const HashableDimensionKey& eventKey,
                                        sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                                        uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
-                                       const std::vector<sp<AnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
-                      bucketSizeNs, anomalyTrackers) {
+                                       bool conditionSliced,
+                                       const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      bucketSizeNs, conditionSliced, anomalyTrackers) {
 }
 
 bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -42,12 +43,13 @@
     // 1. Report the tuple count if the tuple count > soft limit
     if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mInfos.size() + 1;
-        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
-                                                           newTupleCount);
+        StatsdStats::getInstance().noteMetricDimensionSize(
+            mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()),
+            newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-            ALOGE("MaxDurTracker %s dropping data for dimension key %s", mName.c_str(),
-                  newKey.c_str());
+            ALOGE("MaxDurTracker %lld dropping data for dimension key %s",
+                (long long)mTrackerId, newKey.c_str());
             return true;
         }
     }
@@ -62,7 +64,9 @@
     }
 
     DurationInfo& duration = mInfos[key];
-    duration.conditionKeys = conditionKey;
+    if (mConditionSliced) {
+        duration.conditionKeys = conditionKey;
+    }
     VLOG("MaxDuration: key %s start condition %d", key.c_str(), condition);
 
     switch (duration.state) {
@@ -281,7 +285,7 @@
     }
 }
 
-int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
                                                       const uint64_t currentTimestamp) const {
     ALOGE("Max duration producer does not support anomaly timestamp prediction!!!");
     return currentTimestamp;
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 10eddb8..5d3c158 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -28,11 +28,11 @@
 // they stop or bucket expires.
 class MaxDurationTracker : public DurationTracker {
 public:
-    MaxDurationTracker(const ConfigKey& key, const string& name,
+    MaxDurationTracker(const ConfigKey& key, const int64_t& id,
                        const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
                        int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
-                       uint64_t bucketSizeNs,
-                       const std::vector<sp<AnomalyTracker>>& anomalyTrackers);
+                       uint64_t bucketSizeNs, bool conditionSliced,
+                       const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
     void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
@@ -46,7 +46,7 @@
     void onSlicedConditionMayChange(const uint64_t timestamp) override;
     void onConditionChanged(bool condition, const uint64_t timestamp) override;
 
-    int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+    int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
                                       const uint64_t currentTimestamp) const override;
 
 private:
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 5c43096..6bf4228 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -24,14 +24,13 @@
 
 using std::pair;
 
-OringDurationTracker::OringDurationTracker(const ConfigKey& key, const string& name,
-                                           const HashableDimensionKey& eventKey,
-                                           sp<ConditionWizard> wizard, int conditionIndex,
-                                           bool nesting, uint64_t currentBucketStartNs,
-                                           uint64_t bucketSizeNs,
-                                           const std::vector<sp<AnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
-                      bucketSizeNs, anomalyTrackers),
+OringDurationTracker::OringDurationTracker(
+        const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey,
+        sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+        uint64_t bucketSizeNs, bool conditionSliced,
+        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      bucketSizeNs, conditionSliced, anomalyTrackers),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
@@ -45,12 +44,13 @@
     }
     if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mConditionKeyMap.size() + 1;
-        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
-                                                           newTupleCount);
+        StatsdStats::getInstance().noteMetricDimensionSize(
+            mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()),
+            newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-            ALOGE("OringDurTracker %s dropping data for dimension key %s", mName.c_str(),
-                  newKey.c_str());
+            ALOGE("OringDurTracker %lld dropping data for dimension key %s",
+                (long long)mTrackerId, newKey.c_str());
             return true;
         }
     }
@@ -73,7 +73,7 @@
         mPaused[key]++;
     }
 
-    if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
+    if (mConditionSliced && mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
         mConditionKeyMap[key] = conditionKey;
     }
 
@@ -264,8 +264,8 @@
     }
 }
 
-int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
-                                                        const uint64_t eventTimestampNs) const {
+int64_t OringDurationTracker::predictAnomalyTimestampNs(
+        const DurationAnomalyTracker& anomalyTracker, const uint64_t eventTimestampNs) const {
     // TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32).
     // All variables below represent durations (not timestamps).
 
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index b7d3cba..638b7ad 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -27,11 +27,11 @@
 // Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
 class OringDurationTracker : public DurationTracker {
 public:
-    OringDurationTracker(const ConfigKey& key, const string& name,
+    OringDurationTracker(const ConfigKey& key, const int64_t& id,
                          const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
                          int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
-                         uint64_t bucketSizeNs,
-                         const std::vector<sp<AnomalyTracker>>& anomalyTrackers);
+                         uint64_t bucketSizeNs, bool conditionSliced,
+                         const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
 
     void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
                    const ConditionKey& conditionKey) override;
@@ -46,7 +46,7 @@
             uint64_t timestampNs,
             std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) override;
 
-    int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+    int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
                                       const uint64_t currentTimestamp) const override;
 
 private:
@@ -67,7 +67,8 @@
     FRIEND_TEST(OringDurationTrackerTest, TestCrossBucketBoundary);
     FRIEND_TEST(OringDurationTrackerTest, TestDurationConditionChange);
     FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
-    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 5d0e97e..bc887ac 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -38,21 +38,21 @@
 namespace os {
 namespace statsd {
 
-bool handleMetricWithLogTrackers(const string what, const int metricIndex,
+bool handleMetricWithLogTrackers(const int64_t what, const int metricIndex,
                                  const bool usedForDimension,
                                  const vector<sp<LogMatchingTracker>>& allAtomMatchers,
-                                 const unordered_map<string, int>& logTrackerMap,
+                                 const unordered_map<int64_t, int>& logTrackerMap,
                                  unordered_map<int, std::vector<int>>& trackerToMetricMap,
                                  int& logTrackerIndex) {
     auto logTrackerIt = logTrackerMap.find(what);
     if (logTrackerIt == logTrackerMap.end()) {
-        ALOGW("cannot find the AtomMatcher \"%s\" in config", what.c_str());
+        ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)what);
         return false;
     }
-    if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getTagIds().size() > 1) {
-        ALOGE("AtomMatcher \"%s\" has more than one tag ids. When a metric has dimension, "
+    if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getAtomIds().size() > 1) {
+        ALOGE("AtomMatcher \"%lld\" has more than one tag ids. When a metric has dimension, "
               "the \"what\" can only about one atom type.",
-              what.c_str());
+              (long long)what);
         return false;
     }
     logTrackerIndex = logTrackerIt->second;
@@ -62,22 +62,22 @@
 }
 
 bool handleMetricWithConditions(
-        const string condition, const int metricIndex,
-        const unordered_map<string, int>& conditionTrackerMap,
+        const int64_t condition, const int metricIndex,
+        const unordered_map<int64_t, int>& conditionTrackerMap,
         const ::google::protobuf::RepeatedPtrField<::android::os::statsd::MetricConditionLink>&
                 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 Predicate \"%s\" in the config", condition.c_str());
+        ALOGW("cannot find Predicate \"%lld\" in the config", (long long)condition);
         return false;
     }
 
     for (const auto& link : links) {
         auto it = conditionTrackerMap.find(link.condition());
         if (it == conditionTrackerMap.end()) {
-            ALOGW("cannot find Predicate \"%s\" in the config", link.condition().c_str());
+            ALOGW("cannot find Predicate \"%lld\" in the config", (long long)link.condition());
             return false;
         }
         allConditionTrackers[condition_it->second]->setSliced(true);
@@ -92,7 +92,8 @@
     return true;
 }
 
-bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap,
+bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap,
+                     unordered_map<int64_t, int>& logTrackerMap,
                      vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) {
     vector<AtomMatcher> matcherConfigs;
     const int atomMatcherCount = config.atom_matcher_size();
@@ -106,22 +107,22 @@
         switch (logMatcher.contents_case()) {
             case AtomMatcher::ContentsCase::kSimpleAtomMatcher:
                 allAtomMatchers.push_back(new SimpleLogMatchingTracker(
-                        logMatcher.name(), index, logMatcher.simple_atom_matcher()));
+                        logMatcher.id(), index, logMatcher.simple_atom_matcher(), uidMap));
                 break;
             case AtomMatcher::ContentsCase::kCombination:
                 allAtomMatchers.push_back(
-                        new CombinationLogMatchingTracker(logMatcher.name(), index));
+                        new CombinationLogMatchingTracker(logMatcher.id(), index));
                 break;
             default:
-                ALOGE("Matcher \"%s\" malformed", logMatcher.name().c_str());
+                ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id());
                 return false;
                 // continue;
         }
-        if (logTrackerMap.find(logMatcher.name()) != logTrackerMap.end()) {
+        if (logTrackerMap.find(logMatcher.id()) != logTrackerMap.end()) {
             ALOGE("Duplicate AtomMatcher found!");
             return false;
         }
-        logTrackerMap[logMatcher.name()] = index;
+        logTrackerMap[logMatcher.id()] = index;
         matcherConfigs.push_back(logMatcher);
     }
 
@@ -131,15 +132,15 @@
             return false;
         }
         // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only.
-        const set<int>& tagIds = matcher->getTagIds();
+        const set<int>& tagIds = matcher->getAtomIds();
         allTagIds.insert(tagIds.begin(), tagIds.end());
     }
     return true;
 }
 
 bool initConditions(const ConfigKey& key, const StatsdConfig& config,
-                    const unordered_map<string, int>& logTrackerMap,
-                    unordered_map<string, int>& conditionTrackerMap,
+                    const unordered_map<int64_t, int>& logTrackerMap,
+                    unordered_map<int64_t, int>& conditionTrackerMap,
                     vector<sp<ConditionTracker>>& allConditionTrackers,
                     unordered_map<int, std::vector<int>>& trackerToConditionMap) {
     vector<Predicate> conditionConfigs;
@@ -153,23 +154,23 @@
         switch (condition.contents_case()) {
             case Predicate::ContentsCase::kSimplePredicate: {
                 allConditionTrackers.push_back(new SimpleConditionTracker(
-                        key, condition.name(), index, condition.simple_predicate(), logTrackerMap));
+                        key, condition.id(), index, condition.simple_predicate(), logTrackerMap));
                 break;
             }
             case Predicate::ContentsCase::kCombination: {
                 allConditionTrackers.push_back(
-                        new CombinationConditionTracker(condition.name(), index));
+                        new CombinationConditionTracker(condition.id(), index));
                 break;
             }
             default:
-                ALOGE("Predicate \"%s\" malformed", condition.name().c_str());
+                ALOGE("Predicate \"%lld\" malformed", (long long)condition.id());
                 return false;
         }
-        if (conditionTrackerMap.find(condition.name()) != conditionTrackerMap.end()) {
+        if (conditionTrackerMap.find(condition.id()) != conditionTrackerMap.end()) {
             ALOGE("Duplicate Predicate found!");
             return false;
         }
-        conditionTrackerMap[condition.name()] = index;
+        conditionTrackerMap[condition.id()] = index;
         conditionConfigs.push_back(condition);
     }
 
@@ -189,14 +190,15 @@
 }
 
 bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec,
-                 const unordered_map<string, int>& logTrackerMap,
-                 const unordered_map<string, int>& conditionTrackerMap,
+                 const unordered_map<int64_t, int>& logTrackerMap,
+                 const unordered_map<int64_t, int>& conditionTrackerMap,
                  const vector<sp<LogMatchingTracker>>& allAtomMatchers,
                  vector<sp<ConditionTracker>>& allConditionTrackers,
                  vector<sp<MetricProducer>>& allMetricProducers,
                  unordered_map<int, std::vector<int>>& conditionToMetricMap,
                  unordered_map<int, std::vector<int>>& trackerToMetricMap,
-                 unordered_map<string, int>& metricMap) {
+                 unordered_map<int64_t, int>& metricMap,
+                 std::set<int64_t> &noReportMetricIds) {
     sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
     const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
                                 config.event_metric_size() + config.value_metric_size();
@@ -205,24 +207,27 @@
     // Align all buckets to same instant in MIN_BUCKET_SIZE_SEC, so that avoid alarm
     // clock will not grow very aggressive. New metrics will be delayed up to
     // MIN_BUCKET_SIZE_SEC before starting.
-    long currentTimeSec = time(nullptr);
-    uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec -
-                            (currentTimeSec - timeBaseSec) % kMinBucketSizeSec) *
-                           NS_PER_SEC;
+    // Why not use timeBaseSec directly?
+//    long currentTimeSec = time(nullptr);
+//    uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec -
+//                            (currentTimeSec - timeBaseSec) % kMinBucketSizeSec) *
+//                           NS_PER_SEC;
+
+    uint64_t startTimeNs = timeBaseSec * NS_PER_SEC;
 
     // Build MetricProducers for each metric defined in config.
     // 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 \"%s\"", metric.name().c_str());
+            ALOGW("cannot find \"what\" in CountMetric \"%lld\"", (long long)metric.id());
             return false;
         }
 
         int metricIndex = allMetricProducers.size();
-        metricMap.insert({metric.name(), metricIndex});
+        metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(),
                                          allAtomMatchers, logTrackerMap, trackerToMetricMap,
                                          trackerIndex)) {
             return false;
@@ -252,7 +257,7 @@
     for (int i = 0; i < config.duration_metric_size(); i++) {
         int metricIndex = allMetricProducers.size();
         const DurationMetric& metric = config.duration_metric(i);
-        metricMap.insert({metric.name(), metricIndex});
+        metricMap.insert({metric.id(), metricIndex});
 
         auto what_it = conditionTrackerMap.find(metric.what());
         if (what_it == conditionTrackerMap.end()) {
@@ -274,7 +279,7 @@
         int trackerIndices[3] = {-1, -1, -1};
         if (!simplePredicate.has_start() ||
             !handleMetricWithLogTrackers(simplePredicate.start(), metricIndex,
-                                         metric.dimension_size() > 0, allAtomMatchers,
+                                         metric.has_dimensions(), allAtomMatchers,
                                          logTrackerMap, trackerToMetricMap, trackerIndices[0])) {
             ALOGE("Duration metrics must specify a valid the start event matcher");
             return false;
@@ -282,21 +287,19 @@
 
         if (simplePredicate.has_stop() &&
             !handleMetricWithLogTrackers(simplePredicate.stop(), metricIndex,
-                                         metric.dimension_size() > 0, allAtomMatchers,
+                                         metric.has_dimensions(), allAtomMatchers,
                                          logTrackerMap, trackerToMetricMap, trackerIndices[1])) {
             return false;
         }
 
         if (simplePredicate.has_stop_all() &&
             !handleMetricWithLogTrackers(simplePredicate.stop_all(), metricIndex,
-                                         metric.dimension_size() > 0, allAtomMatchers,
+                                         metric.has_dimensions(), allAtomMatchers,
                                          logTrackerMap, trackerToMetricMap, trackerIndices[2])) {
             return false;
         }
 
-        vector<KeyMatcher> internalDimension;
-        internalDimension.insert(internalDimension.begin(), simplePredicate.dimension().begin(),
-                                 simplePredicate.dimension().end());
+        FieldMatcher internalDimensions = simplePredicate.dimensions();
 
         int conditionIndex = -1;
 
@@ -316,7 +319,7 @@
 
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
                 key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
-                trackerIndices[2], nesting, wizard, internalDimension, startTimeNs);
+                trackerIndices[2], nesting, wizard, internalDimensions, startTimeNs);
 
         allMetricProducers.push_back(durationMetric);
     }
@@ -325,8 +328,8 @@
     for (int i = 0; i < config.event_metric_size(); i++) {
         int metricIndex = allMetricProducers.size();
         const EventMetric& metric = config.event_metric(i);
-        metricMap.insert({metric.name(), metricIndex});
-        if (!metric.has_name() || !metric.has_what()) {
+        metricMap.insert({metric.id(), metricIndex});
+        if (!metric.has_id() || !metric.has_what()) {
             ALOGW("cannot find the metric name or what in config");
             return false;
         }
@@ -361,14 +364,14 @@
     for (int i = 0; i < config.value_metric_size(); i++) {
         const ValueMetric& metric = config.value_metric(i);
         if (!metric.has_what()) {
-            ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str());
+            ALOGW("cannot find \"what\" in ValueMetric \"%lld\"", (long long)metric.id());
             return false;
         }
 
         int metricIndex = allMetricProducers.size();
-        metricMap.insert({metric.name(), metricIndex});
+        metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(),
                                          allAtomMatchers, logTrackerMap, trackerToMetricMap,
                                          trackerIndex)) {
             return false;
@@ -376,10 +379,10 @@
 
         sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
         // If it is pulled atom, it should be simple matcher with one tagId.
-        if (atomMatcher->getTagIds().size() != 1) {
+        if (atomMatcher->getAtomIds().size() != 1) {
             return false;
         }
-        int atomTagId = *(atomMatcher->getTagIds().begin());
+        int atomTagId = *(atomMatcher->getAtomIds().begin());
         int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1;
 
         int conditionIndex = -1;
@@ -406,27 +409,27 @@
     for (int i = 0; i < config.gauge_metric_size(); i++) {
         const GaugeMetric& metric = config.gauge_metric(i);
         if (!metric.has_what()) {
-            ALOGW("cannot find \"what\" in GaugeMetric \"%s\"", metric.name().c_str());
+            ALOGW("cannot find \"what\" in GaugeMetric \"%lld\"", (long long)metric.id());
             return false;
         }
 
-        if ((!metric.gauge_fields().has_include_all() ||
-             (metric.gauge_fields().include_all() == false)) &&
-            metric.gauge_fields().field_num_size() == 0) {
-            ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str());
+        if ((!metric.gauge_fields_filter().has_include_all() ||
+             (metric.gauge_fields_filter().include_all() == false)) &&
+            !hasLeafNode(metric.gauge_fields_filter().fields())) {
+            ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id());
             return false;
         }
-        if ((metric.gauge_fields().has_include_all() &&
-             metric.gauge_fields().include_all() == true) &&
-            metric.gauge_fields().field_num_size() > 0) {
-            ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str());
+        if ((metric.gauge_fields_filter().has_include_all() &&
+             metric.gauge_fields_filter().include_all() == true) &&
+            hasLeafNode(metric.gauge_fields_filter().fields())) {
+            ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id());
             return false;
         }
 
         int metricIndex = allMetricProducers.size();
-        metricMap.insert({metric.name(), metricIndex});
+        metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(),
                                          allAtomMatchers, logTrackerMap, trackerToMetricMap,
                                          trackerIndex)) {
             return false;
@@ -434,10 +437,10 @@
 
         sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
         // If it is pulled atom, it should be simple matcher with one tagId.
-        if (atomMatcher->getTagIds().size() != 1) {
+        if (atomMatcher->getAtomIds().size() != 1) {
             return false;
         }
-        int atomTagId = *(atomMatcher->getTagIds().begin());
+        int atomTagId = *(atomMatcher->getAtomIds().begin());
         int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1;
 
         int conditionIndex = -1;
@@ -456,52 +459,82 @@
         }
 
         sp<MetricProducer> gaugeProducer = new GaugeMetricProducer(
-                key, metric, conditionIndex, wizard, pullTagId, atomTagId, startTimeNs);
+                key, metric, conditionIndex, wizard, pullTagId, startTimeNs);
         allMetricProducers.push_back(gaugeProducer);
     }
+    for (int i = 0; i < config.no_report_metric_size(); ++i) {
+        const auto no_report_metric = config.no_report_metric(i);
+        if (metricMap.find(no_report_metric) == metricMap.end()) {
+            ALOGW("no_report_metric %lld not exist", no_report_metric);
+            return false;
+        }
+        noReportMetricIds.insert(no_report_metric);
+    }
     return true;
 }
 
-bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& metricProducerMap,
+bool initAlerts(const StatsdConfig& config,
+                const unordered_map<int64_t, int>& metricProducerMap,
                 vector<sp<MetricProducer>>& allMetricProducers,
                 vector<sp<AnomalyTracker>>& allAnomalyTrackers) {
+    unordered_map<int64_t, int> anomalyTrackerMap;
     for (int i = 0; i < config.alert_size(); i++) {
         const Alert& alert = config.alert(i);
-        const auto& itr = metricProducerMap.find(alert.metric_name());
+        const auto& itr = metricProducerMap.find(alert.metric_id());
         if (itr == metricProducerMap.end()) {
-            ALOGW("alert \"%s\" has unknown metric name: \"%s\"", alert.name().c_str(),
-                  alert.metric_name().c_str());
+            ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(),
+                  (long long)alert.metric_id());
             return false;
         }
-        if (alert.trigger_if_sum_gt() < 0 || alert.number_of_buckets() <= 0) {
-            ALOGW("invalid alert: threshold=%lld num_buckets= %d",
-                  alert.trigger_if_sum_gt(), alert.number_of_buckets());
+        if (alert.trigger_if_sum_gt() < 0 || alert.num_buckets() <= 0) {
+            ALOGW("invalid alert: threshold=%f num_buckets= %d",
+                  alert.trigger_if_sum_gt(), alert.num_buckets());
             return false;
         }
         const int metricIndex = itr->second;
         sp<MetricProducer> metric = allMetricProducers[metricIndex];
-        sp<AnomalyTracker> anomalyTracker = metric->createAnomalyTracker(alert);
+        sp<AnomalyTracker> anomalyTracker = metric->addAnomalyTracker(alert);
         if (anomalyTracker != nullptr) {
-            metric->addAnomalyTracker(anomalyTracker);
+            anomalyTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size()));
             allAnomalyTrackers.push_back(anomalyTracker);
         }
     }
+    for (int i = 0; i < config.subscription_size(); ++i) {
+        const Subscription& subscription = config.subscription(i);
+        if (subscription.subscriber_information_case() ==
+            Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) {
+            ALOGW("subscription \"%lld\" has no subscriber info.\"",
+                (long long)subscription.id());
+            return false;
+        }
+        const auto& itr = anomalyTrackerMap.find(subscription.rule_id());
+        if (itr == anomalyTrackerMap.end()) {
+            ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"",
+                (long long)subscription.id(), (long long)subscription.rule_id());
+            return false;
+        }
+        const int anomalyTrackerIndex = itr->second;
+        allAnomalyTrackers[anomalyTrackerIndex]->addSubscription(subscription);
+    }
     return true;
 }
 
-bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, set<int>& allTagIds,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config,
+                      const UidMap& uidMap,
+                      const long timeBaseSec, set<int>& allTagIds,
                       vector<sp<LogMatchingTracker>>& allAtomMatchers,
                       vector<sp<ConditionTracker>>& allConditionTrackers,
                       vector<sp<MetricProducer>>& allMetricProducers,
                       vector<sp<AnomalyTracker>>& allAnomalyTrackers,
                       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;
-    unordered_map<string, int> metricProducerMap;
+                      unordered_map<int, std::vector<int>>& trackerToConditionMap,
+                      std::set<int64_t> &noReportMetricIds) {
+    unordered_map<int64_t, int> logTrackerMap;
+    unordered_map<int64_t, int> conditionTrackerMap;
+    unordered_map<int64_t, int> metricProducerMap;
 
-    if (!initLogTrackers(config, logTrackerMap, allAtomMatchers, allTagIds)) {
+    if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) {
         ALOGE("initLogMatchingTrackers failed");
         return false;
     }
@@ -515,7 +548,7 @@
 
     if (!initMetrics(key, config, timeBaseSec, logTrackerMap, conditionTrackerMap, allAtomMatchers,
                      allConditionTrackers, allMetricProducers, conditionToMetricMap,
-                     trackerToMetricMap, metricProducerMap)) {
+                     trackerToMetricMap, metricProducerMap, noReportMetricIds)) {
         ALOGE("initMetricProducers failed");
         return false;
     }
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 3337332..4f19ada 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -43,7 +43,8 @@
 // [allAtomMatchers]: 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,
+                     const UidMap& uidMap,
+                     std::unordered_map<int64_t, int>& logTrackerMap,
                      std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
                      std::set<int>& allTagIds);
 
@@ -58,8 +59,8 @@
 // [trackerToConditionMap]: contain the mapping from index of
 //                        log tracker to condition trackers that use the log tracker
 bool initConditions(const ConfigKey& key, const StatsdConfig& config,
-                    const std::unordered_map<std::string, int>& logTrackerMap,
-                    std::unordered_map<std::string, int>& conditionTrackerMap,
+                    const std::unordered_map<int64_t, int>& logTrackerMap,
+                    std::unordered_map<int64_t, int>& conditionTrackerMap,
                     std::vector<sp<ConditionTracker>>& allConditionTrackers,
                     std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
                     std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks);
@@ -78,25 +79,29 @@
 // [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
 bool initMetrics(
         const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec,
-        const std::unordered_map<std::string, int>& logTrackerMap,
-        const std::unordered_map<std::string, int>& conditionTrackerMap,
+        const std::unordered_map<int64_t, int>& logTrackerMap,
+        const std::unordered_map<int64_t, int>& conditionTrackerMap,
         const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
         const vector<sp<LogMatchingTracker>>& allAtomMatchers,
         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>>& trackerToMetricMap,
+        std::set<int64_t> &noReportMetricIds);
 
 // Initialize MetricsManager from StatsdConfig.
 // Parameters are the members of MetricsManager. See MetricsManager for declaration.
-bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, std::set<int>& allTagIds,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config,
+                      const UidMap& uidMap,
+                      const long timeBaseSec, std::set<int>& allTagIds,
                       std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
                       std::vector<sp<ConditionTracker>>& allConditionTrackers,
                       std::vector<sp<MetricProducer>>& allMetricProducers,
                       vector<sp<AnomalyTracker>>& allAnomalyTrackers,
                       std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
                       std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
-                      std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
+                      std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
+                      std::set<int64_t> &noReportMetricIds);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 21a9cf3..517d21d 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -402,6 +402,79 @@
     return results;
 }
 
+// Note not all the following AIDs are used as uids. Some are used only for gids.
+// It's ok to leave them in the map, but we won't ever see them in the log's uid field.
+// App's uid starts from 10000, and will not overlap with the following AIDs.
+const std::map<string, uint32_t> UidMap::sAidToUidMapping = {{"AID_ROOT", 0},
+                                                             {"AID_SYSTEM", 1000},
+                                                             {"AID_RADIO", 1001},
+                                                             {"AID_BLUETOOTH", 1002},
+                                                             {"AID_GRAPHICS", 1003},
+                                                             {"AID_INPUT", 1004},
+                                                             {"AID_AUDIO", 1005},
+                                                             {"AID_CAMERA", 1006},
+                                                             {"AID_LOG", 1007},
+                                                             {"AID_COMPASS", 1008},
+                                                             {"AID_MOUNT", 1009},
+                                                             {"AID_WIFI", 1010},
+                                                             {"AID_ADB", 1011},
+                                                             {"AID_INSTALL", 1012},
+                                                             {"AID_MEDIA", 1013},
+                                                             {"AID_DHCP", 1014},
+                                                             {"AID_SDCARD_RW", 1015},
+                                                             {"AID_VPN", 1016},
+                                                             {"AID_KEYSTORE", 1017},
+                                                             {"AID_USB", 1018},
+                                                             {"AID_DRM", 1019},
+                                                             {"AID_MDNSR", 1020},
+                                                             {"AID_GPS", 1021},
+                                                             // {"AID_UNUSED1", 1022},
+                                                             {"AID_MEDIA_RW", 1023},
+                                                             {"AID_MTP", 1024},
+                                                             // {"AID_UNUSED2", 1025},
+                                                             {"AID_DRMRPC", 1026},
+                                                             {"AID_NFC", 1027},
+                                                             {"AID_SDCARD_R", 1028},
+                                                             {"AID_CLAT", 1029},
+                                                             {"AID_LOOP_RADIO", 1030},
+                                                             {"AID_MEDIA_DRM", 1031},
+                                                             {"AID_PACKAGE_INFO", 1032},
+                                                             {"AID_SDCARD_PICS", 1033},
+                                                             {"AID_SDCARD_AV", 1034},
+                                                             {"AID_SDCARD_ALL", 1035},
+                                                             {"AID_LOGD", 1036},
+                                                             {"AID_SHARED_RELRO", 1037},
+                                                             {"AID_DBUS", 1038},
+                                                             {"AID_TLSDATE", 1039},
+                                                             {"AID_MEDIA_EX", 1040},
+                                                             {"AID_AUDIOSERVER", 1041},
+                                                             {"AID_METRICS_COLL", 1042},
+                                                             {"AID_METRICSD", 1043},
+                                                             {"AID_WEBSERV", 1044},
+                                                             {"AID_DEBUGGERD", 1045},
+                                                             {"AID_MEDIA_CODEC", 1046},
+                                                             {"AID_CAMERASERVER", 1047},
+                                                             {"AID_FIREWALL", 1048},
+                                                             {"AID_TRUNKS", 1049},
+                                                             {"AID_NVRAM", 1050},
+                                                             {"AID_DNS", 1051},
+                                                             {"AID_DNS_TETHER", 1052},
+                                                             {"AID_WEBVIEW_ZYGOTE", 1053},
+                                                             {"AID_VEHICLE_NETWORK", 1054},
+                                                             {"AID_MEDIA_AUDIO", 1055},
+                                                             {"AID_MEDIA_VIDEO", 1056},
+                                                             {"AID_MEDIA_IMAGE", 1057},
+                                                             {"AID_TOMBSTONED", 1058},
+                                                             {"AID_MEDIA_OBB", 1059},
+                                                             {"AID_ESE", 1060},
+                                                             {"AID_OTA_UPDATE", 1061},
+                                                             {"AID_AUTOMOTIVE_EVS", 1062},
+                                                             {"AID_LOWPAN", 1063},
+                                                             {"AID_HSM", 1064},
+                                                             {"AID_SHELL", 2000},
+                                                             {"AID_CACHE", 2001},
+                                                             {"AID_DIAG", 2002}};
+
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index a9aec94..07e13e0 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -51,7 +51,7 @@
 public:
     UidMap();
     ~UidMap();
-
+    static const std::map<std::string, uint32_t> sAidToUidMapping;
     /*
      * 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].
@@ -168,4 +168,3 @@
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 3c85c57..2596a5f 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -24,8 +24,8 @@
 
 import "frameworks/base/cmds/statsd/src/atoms.proto";
 
-message KeyValuePair {
-  optional int32 key = 1;
+message DimensionsValue {
+  optional int32 field = 1;
 
   oneof value {
     string value_str = 2;
@@ -33,9 +33,14 @@
     int64 value_long = 4;
     bool value_bool = 5;
     float value_float = 6;
+    DimensionsValueTuple value_tuple = 7;
   }
 }
 
+message DimensionsValueTuple {
+  repeated DimensionsValue dimensions_value = 1;
+}
+
 message EventMetricData {
   optional int64 timestamp_nanos = 1;
 
@@ -51,7 +56,7 @@
 }
 
 message CountMetricData {
-  repeated KeyValuePair dimension = 1;
+  optional DimensionsValue dimension = 1;
 
   repeated CountBucketInfo bucket_info = 2;
 }
@@ -65,7 +70,7 @@
 }
 
 message DurationMetricData {
-  repeated KeyValuePair dimension = 1;
+  optional DimensionsValue dimension = 1;
 
   repeated DurationBucketInfo bucket_info = 2;
 }
@@ -79,7 +84,7 @@
 }
 
 message ValueMetricData {
-  repeated KeyValuePair dimension = 1;
+  optional DimensionsValue dimension = 1;
 
   repeated ValueBucketInfo bucket_info = 2;
 }
@@ -93,7 +98,7 @@
 }
 
 message GaugeMetricData {
-  repeated KeyValuePair dimension = 1;
+  optional DimensionsValue dimension = 1;
 
   repeated GaugeBucketInfo bucket_info = 2;
 }
@@ -126,7 +131,7 @@
 }
 
 message StatsLogReport {
-  optional string metric_name = 1;
+  optional int64 metric_id = 1;
 
   optional int64 start_report_nanos = 2;
 
@@ -167,7 +172,7 @@
 message ConfigMetricsReportList {
   message ConfigKey {
     optional int32 uid = 1;
-    optional string name = 2;
+    optional int64 id = 2;
   }
   optional ConfigKey config_key = 1;
 
@@ -180,28 +185,28 @@
     optional int32 stats_end_time_sec = 2;
 
     message MatcherStats {
-        optional string name = 1;
+        optional int64 id = 1;
         optional int32 matched_times = 2;
     }
 
     message ConditionStats {
-        optional string name = 1;
+        optional int64 id = 1;
         optional int32 max_tuple_counts = 2;
     }
 
     message MetricStats {
-        optional string name = 1;
+        optional int64 id = 1;
         optional int32 max_tuple_counts = 2;
     }
 
     message AlertStats {
-        optional string name = 1;
+        optional int64 id = 1;
         optional int32 alerted_times = 2;
     }
 
     message ConfigStats {
         optional int32 uid = 1;
-        optional string name = 2;
+        optional int64 id = 2;
         optional int32 creation_time_sec = 3;
         optional int32 deletion_time_sec = 4;
         optional int32 metric_count = 5;
@@ -241,4 +246,12 @@
         optional int32 alarms_registered = 1;
     }
     optional AnomalyAlarmStats anomaly_alarm_stats = 9;
+
+    message PulledAtomStats {
+        optional int32 atom_id = 1;
+        optional int64 total_pull = 2;
+        optional int64 total_pull_from_cache = 3;
+        optional int64 min_pull_interval_sec = 4;
+    }
+    repeated PulledAtomStats pulled_atom_stats = 10;
 }
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
new file mode 100644
index 0000000..a41f30c
--- /dev/null
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -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.
+ */
+
+#include "stats_log_util.h"
+
+#include <set>
+#include <stack>
+#include <utils/Log.h>
+
+using android::util::FIELD_COUNT_REPEATED;
+using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FLOAT;
+using android::util::FIELD_TYPE_INT32;
+using android::util::FIELD_TYPE_INT64;
+using android::util::FIELD_TYPE_MESSAGE;
+using android::util::FIELD_TYPE_STRING;
+using android::util::ProtoOutputStream;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// for DimensionsValue Proto
+const int DIMENSIONS_VALUE_FIELD = 1;
+const int DIMENSIONS_VALUE_VALUE_STR = 2;
+const int DIMENSIONS_VALUE_VALUE_INT = 3;
+const int DIMENSIONS_VALUE_VALUE_LONG = 4;
+const int DIMENSIONS_VALUE_VALUE_BOOL = 5;
+const int DIMENSIONS_VALUE_VALUE_FLOAT = 6;
+const int DIMENSIONS_VALUE_VALUE_TUPLE = 7;
+
+// for MessageValue Proto
+const int FIELD_ID_FIELD_VALUE_IN_MESSAGE_VALUE_PROTO = 1;
+
+// for PulledAtomStats proto
+const int FIELD_ID_PULLED_ATOM_STATS = 10;
+const int FIELD_ID_PULL_ATOM_ID = 1;
+const int FIELD_ID_TOTAL_PULL = 2;
+const int FIELD_ID_TOTAL_PULL_FROM_CACHE = 3;
+const int FIELD_ID_MIN_PULL_INTERVAL_SEC = 4;
+
+void writeDimensionsValueProtoToStream(const DimensionsValue& dimensionsValue,
+                                       ProtoOutputStream* protoOutput) {
+    protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, dimensionsValue.field());
+    switch (dimensionsValue.value_case()) {
+        case DimensionsValue::ValueCase::kValueStr:
+            protoOutput->write(FIELD_TYPE_STRING | DIMENSIONS_VALUE_VALUE_STR,
+                dimensionsValue.value_str());
+            break;
+        case DimensionsValue::ValueCase::kValueInt:
+            protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_VALUE_INT,
+                dimensionsValue.value_int());
+            break;
+        case DimensionsValue::ValueCase::kValueLong:
+            protoOutput->write(FIELD_TYPE_INT64 | DIMENSIONS_VALUE_VALUE_LONG,
+                dimensionsValue.value_long());
+            break;
+        case DimensionsValue::ValueCase::kValueBool:
+            protoOutput->write(FIELD_TYPE_BOOL | DIMENSIONS_VALUE_VALUE_BOOL,
+                dimensionsValue.value_bool());
+            break;
+        case DimensionsValue::ValueCase::kValueFloat:
+            protoOutput->write(FIELD_TYPE_FLOAT | DIMENSIONS_VALUE_VALUE_FLOAT,
+                dimensionsValue.value_float());
+            break;
+        case DimensionsValue::ValueCase::kValueTuple:
+            {
+                long long tupleToken = protoOutput->start(
+                    FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE);
+                for (int i = 0; i < dimensionsValue.value_tuple().dimensions_value_size(); ++i) {
+                    long long token = protoOutput->start(
+                        FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED
+                        | FIELD_ID_FIELD_VALUE_IN_MESSAGE_VALUE_PROTO);
+                    writeDimensionsValueProtoToStream(
+                        dimensionsValue.value_tuple().dimensions_value(i), protoOutput);
+                    protoOutput->end(token);
+                }
+                protoOutput->end(tupleToken);
+            }
+            break;
+        default:
+            break;
+    }
+}
+
+// for Field Proto
+const int FIELD_FIELD = 1;
+const int FIELD_POSITION_INDEX = 2;
+const int FIELD_CHILD = 3;
+
+void writeFieldProtoToStream(
+    const Field& field, util::ProtoOutputStream* protoOutput) {
+    protoOutput->write(FIELD_TYPE_INT32 | FIELD_FIELD, field.field());
+    if (field.has_position_index()) {
+      protoOutput->write(FIELD_TYPE_INT32 | FIELD_POSITION_INDEX, field.position_index());
+    }
+    for (int i = 0; i < field.child_size(); ++i) {
+        long long childToken = protoOutput->start(
+            FIELD_TYPE_MESSAGE| FIELD_COUNT_REPEATED | FIELD_CHILD);
+        writeFieldProtoToStream(field.child(i), protoOutput);
+        protoOutput->end(childToken);
+    }
+}
+
+namespace {
+
+void addOrUpdateChildrenMap(
+    const Field& root,
+    const Field& node,
+    std::map<Field, std::set<Field, FieldCmp>, FieldCmp> *childrenMap) {
+    Field parentNode = root;
+    if (node.has_position_index()) {
+        appendLeaf(&parentNode, node.field(), node.position_index());
+    } else {
+        appendLeaf(&parentNode, node.field());
+    }
+    if (childrenMap->find(parentNode) == childrenMap->end()) {
+        childrenMap->insert(std::make_pair(parentNode, std::set<Field, FieldCmp>{}));
+    }
+    auto it = childrenMap->find(parentNode);
+    for (int i = 0; i < node.child_size(); ++i) {
+        auto child = node.child(i);
+        Field childNode = parentNode;
+        if (child.has_position_index()) {
+            appendLeaf(&childNode, child.field(), child.position_index());
+        } else {
+            appendLeaf(&childNode, child.field());
+        }
+        it->second.insert(childNode);
+        addOrUpdateChildrenMap(parentNode, child, childrenMap);
+    }
+}
+
+void addOrUpdateChildrenMap(
+    const Field& field,
+    std::map<Field, std::set<Field, FieldCmp>, FieldCmp> *childrenMap) {
+    Field root;
+    addOrUpdateChildrenMap(root, field, childrenMap);
+}
+
+} // namespace
+
+void writeFieldValueTreeToStream(const FieldValueMap &fieldValueMap,
+                                 util::ProtoOutputStream* protoOutput) {
+    std::map<Field, std::set<Field, FieldCmp>, FieldCmp> childrenMap;
+    // Rebuild the field tree.
+    for (auto it = fieldValueMap.begin(); it != fieldValueMap.end(); ++it) {
+        addOrUpdateChildrenMap(it->first, &childrenMap);
+    }
+    std::stack<std::pair<long long, Field>> tokenStack;
+    // Iterate over the node tree to fill the Atom proto.
+    for (auto it = childrenMap.begin(); it != childrenMap.end(); ++it) {
+        const Field* nodeLeaf = getSingleLeaf(&it->first);
+        const int fieldNum = nodeLeaf->field();
+        while (!tokenStack.empty()) {
+            auto currentMsgNode = tokenStack.top().second;
+            auto currentMsgNodeChildrenIt = childrenMap.find(currentMsgNode);
+            if (currentMsgNodeChildrenIt->second.find(it->first) ==
+                currentMsgNodeChildrenIt->second.end()) {
+                protoOutput->end(tokenStack.top().first);
+                tokenStack.pop();
+            } else {
+                break;
+            }
+        }
+        if (it->second.size() == 0) {
+            auto itValue = fieldValueMap.find(it->first);
+            if (itValue != fieldValueMap.end()) {
+                const DimensionsValue& value = itValue->second;
+                switch (value.value_case()) {
+                        case DimensionsValue::ValueCase::kValueStr:
+                            protoOutput->write(FIELD_TYPE_STRING | fieldNum,
+                                value.value_str());
+                            break;
+                        case DimensionsValue::ValueCase::kValueInt:
+                            protoOutput->write(FIELD_TYPE_INT32 | fieldNum,
+                                value.value_int());
+                            break;
+                        case DimensionsValue::ValueCase::kValueLong:
+                            protoOutput->write(FIELD_TYPE_INT64 | fieldNum,
+                                value.value_long());
+                            break;
+                        case DimensionsValue::ValueCase::kValueBool:
+                            protoOutput->write(FIELD_TYPE_BOOL | fieldNum,
+                                value.value_bool());
+                            break;
+                        case DimensionsValue::ValueCase::kValueFloat:
+                            protoOutput->write(FIELD_TYPE_FLOAT | fieldNum,
+                                value.value_float());
+                            break;
+                        // This would not happen as the node has no child.
+                        case DimensionsValue::ValueCase::kValueTuple:
+                            break;
+                        default:
+                            break;
+                }
+            } else {
+                ALOGE("Leaf node value not found. This should never happen.");
+            }
+        } else {
+            long long token;
+            if (nodeLeaf->has_position_index()) {
+                token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | fieldNum);
+            } else {
+                token = protoOutput->start(FIELD_TYPE_MESSAGE | fieldNum);
+            }
+            tokenStack.push(std::make_pair(token, it->first));
+        }
+    }
+
+    while (!tokenStack.empty()) {
+        protoOutput->end(tokenStack.top().first);
+        tokenStack.pop();
+    }
+}
+
+int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit) {
+    switch (unit) {
+        case ONE_MINUTE:
+            return 60 * 1000LL;
+        case FIVE_MINUTES:
+            return 5 * 60 * 1000LL;
+        case TEN_MINUTES:
+            return 10 * 60 * 1000LL;
+        case THIRTY_MINUTES:
+            return 30 * 60 * 1000LL;
+        case ONE_HOUR:
+            return 60 * 60 * 1000LL;
+        case THREE_HOURS:
+            return 3 * 60 * 60 * 1000LL;
+        case SIX_HOURS:
+            return 6 * 60 * 60 * 1000LL;
+        case TWELVE_HOURS:
+            return 12 * 60 * 60 * 1000LL;
+        case ONE_DAY:
+            return 24 * 60 * 60 * 1000LL;
+        case CTS:
+            return 1000;
+        case TIME_UNIT_UNSPECIFIED:
+        default:
+            return -1;
+    }
+}
+
+void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>& pair,
+                              util::ProtoOutputStream* protoOutput) {
+    long long token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_PULLED_ATOM_STATS |
+                                         FIELD_COUNT_REPEATED);
+    protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_PULL_ATOM_ID, (int32_t)pair.first);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TOTAL_PULL, (long long)pair.second.totalPull);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TOTAL_PULL_FROM_CACHE,
+                       (long long)pair.second.totalPullFromCache);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MIN_PULL_INTERVAL_SEC,
+                       (long long)pair.second.minPullIntervalSec);
+    protoOutput->end(token);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
new file mode 100644
index 0000000..09a43f5
--- /dev/null
+++ b/cmds/statsd/src/stats_log_util.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.
+ */
+
+#pragma once
+
+#include <android/util/ProtoOutputStream.h>
+#include "field_util.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "guardrail/StatsdStats.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Helper function to write DimensionsValue proto to ProtoOutputStream.
+void writeDimensionsValueProtoToStream(const DimensionsValue& fieldValue,
+                                       util::ProtoOutputStream* protoOutput);
+
+// Helper function to write Field proto to ProtoOutputStream.
+void writeFieldProtoToStream(const Field& field, util::ProtoOutputStream* protoOutput);
+
+// Helper function to construct the field value tree and write to ProtoOutputStream
+void writeFieldValueTreeToStream(const FieldValueMap& fieldValueMap,
+                                 util::ProtoOutputStream* protoOutput);
+
+// Convert the TimeUnit enum to the bucket size in millis.
+int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit);
+
+// Helper function to write PulledAtomStats to ProtoOutputStream
+void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>& pair,
+                              util::ProtoOutputStream* protoOutput);
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 1cdf031..160b1f4 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -27,45 +27,15 @@
 namespace os {
 namespace statsd {
 
-const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(vector<KeyValuePair>());
+const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey();
 
 // Minimum bucket size in seconds
 const long kMinBucketSizeSec = 5 * 60;
 
-typedef std::map<std::string, HashableDimensionKey> ConditionKey;
+typedef std::map<int64_t, std::vector<HashableDimensionKey>> ConditionKey;
 
 typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
 
-/*
- * In memory rep for LogEvent. Uses much less memory than LogEvent
- */
-typedef struct EventKV {
-    std::vector<KeyValuePair> kv;
-    string ToString() const {
-        std::ostringstream result;
-        result << "{ ";
-        const size_t N = kv.size();
-        for (size_t i = 0; i < N; i++) {
-            result << " ";
-            result << (i + 1);
-            result << "->";
-            const auto& pair = kv[i];
-            if (pair.has_value_int()) {
-                result << pair.value_int();
-            } else if (pair.has_value_long()) {
-                result << pair.value_long();
-            } else if (pair.has_value_float()) {
-                result << pair.value_float();
-            } else if (pair.has_value_str()) {
-                result << pair.value_str().c_str();
-            }
-        }
-        result << " }";
-        return result.str();
-    }
-} EventKV;
-
-typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<EventKV>> DimToEventKVMap;
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 4729f6a..d45a6b0 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -22,30 +22,66 @@
 option java_package = "com.android.internal.os";
 option java_outer_classname = "StatsdConfigProto";
 
-message KeyMatcher {
-    optional int32 key = 1;
-
-    optional bool as_package_name = 2 [default = false];
+enum Position {
+    POSITION_UNKNOWN = 0;
+    FIRST = 1;
+    LAST = 2;
+    ANY = 3;
 }
 
-message KeyValueMatcher {
-    optional KeyMatcher key_matcher = 1;
+enum TimeUnit {
+    TIME_UNIT_UNSPECIFIED = 0;
+    ONE_MINUTE = 1;
+    FIVE_MINUTES = 2;
+    TEN_MINUTES = 3;
+    THIRTY_MINUTES = 4;
+    ONE_HOUR = 5;
+    THREE_HOURS = 6;
+    SIX_HOURS = 7;
+    TWELVE_HOURS = 8;
+    ONE_DAY = 9;
+    CTS = 1000;
+}
+
+message FieldMatcher {
+    optional int32 field = 1;
+
+    optional Position position = 2;
+
+    repeated FieldMatcher child = 3;
+}
+
+message FieldValueMatcher {
+    // Field id, as specified in the atom proto message.
+    optional int32 field = 1;
+
+    // For repeated fields, specifies the position in the array.
+    // FIRST and LAST mean that if the values are found at the first
+    // or last position, it's a match. ANY means that if the values are found
+    // anywhere in the array, then it's a match.
+    optional Position position = 2;
 
     oneof value_matcher {
-        bool eq_bool = 2;
-        string eq_string = 3;
-        int32 eq_int = 4;
+        bool eq_bool = 3;
+        string eq_string = 4;
+        int32 eq_int = 5;
 
-        int64 lt_int = 5;
-        int64 gt_int = 6;
-        float lt_float = 7;
-        float gt_float = 8;
+        int64 lt_int = 6;
+        int64 gt_int = 7;
+        float lt_float = 8;
+        float gt_float = 9;
 
-        int64 lte_int = 9;
-        int64 gte_int = 10;
+        int64 lte_int = 10;
+        int64 gte_int = 11;
+
+        MessageMatcher matches_tuple = 12;
     }
 }
 
+message MessageMatcher {
+    repeated FieldValueMatcher field_value_matcher = 1;
+}
+
 enum LogicalOperation {
     LOGICAL_OPERATION_UNSPECIFIED = 0;
     AND = 1;
@@ -56,18 +92,18 @@
 }
 
 message SimpleAtomMatcher {
-    optional int32 tag = 1;
+    optional int32 atom_id = 1;
 
-    repeated KeyValueMatcher key_value_matcher = 2;
+    repeated FieldValueMatcher field_value_matcher = 2;
 }
 
 message AtomMatcher {
-    optional string name = 1;
+    optional int64 id = 1;
 
     message Combination {
         optional LogicalOperation operation = 1;
 
-        repeated string matcher = 2;
+        repeated int64 matcher = 2;
     }
     oneof contents {
         SimpleAtomMatcher simple_atom_matcher = 2;
@@ -76,13 +112,13 @@
 }
 
 message SimplePredicate {
-    optional string start = 1;
+    optional int64 start = 1;
 
-    optional string stop = 2;
+    optional int64 stop = 2;
 
     optional bool count_nesting = 3 [default = true];
 
-    optional string stop_all = 4;
+    optional int64 stop_all = 4;
 
     enum InitialValue {
         UNKNOWN = 0;
@@ -90,16 +126,16 @@
     }
     optional InitialValue initial_value = 5 [default = FALSE];
 
-    repeated KeyMatcher dimension = 6;
+    optional FieldMatcher dimensions = 6;
 }
 
 message Predicate {
-    optional string name = 1;
+    optional int64 id = 1;
 
     message Combination {
         optional LogicalOperation operation = 1;
 
-        repeated string predicate = 2;
+        repeated int64 predicate = 2;
     }
 
     oneof contents {
@@ -113,48 +149,48 @@
 }
 
 message MetricConditionLink {
-    optional string condition = 1;
+    optional int64 condition = 1;
 
-    repeated KeyMatcher key_in_what = 2;
+    optional FieldMatcher dimensions_in_what = 2;
 
-    repeated KeyMatcher key_in_condition = 3;
+    optional FieldMatcher dimensions_in_condition = 3;
 }
 
 message FieldFilter {
-    optional bool include_all = 1;
-    repeated int32 field_num = 2;
+    optional bool include_all = 1 [default = false];
+    optional FieldMatcher fields = 2;
 }
 
 message EventMetric {
-    optional string name = 1;
+    optional int64 id = 1;
 
-    optional string what = 2;
+    optional int64 what = 2;
 
-    optional string condition = 3;
+    optional int64 condition = 3;
 
     repeated MetricConditionLink links = 4;
 }
 
 message CountMetric {
-    optional string name = 1;
+    optional int64 id = 1;
 
-    optional string what = 2;
+    optional int64 what = 2;
 
-    optional string condition = 3;
+    optional int64 condition = 3;
 
-    repeated KeyMatcher dimension = 4;
+    optional FieldMatcher dimensions = 4;
 
-    optional Bucket bucket = 5;
+    optional TimeUnit bucket = 5;
 
     repeated MetricConditionLink links = 6;
 }
 
 message DurationMetric {
-    optional string name = 1;
+    optional int64 id = 1;
 
-    optional string what = 2;
+    optional int64 what = 2;
 
-    optional string condition = 3;
+    optional int64 condition = 3;
 
     repeated MetricConditionLink links = 4;
 
@@ -165,39 +201,39 @@
     }
     optional AggregationType aggregation_type = 5 [default = SUM];
 
-    repeated KeyMatcher dimension = 6;
+    optional FieldMatcher dimensions = 6;
 
-    optional Bucket bucket = 7;
+    optional TimeUnit bucket = 7;
 }
 
 message GaugeMetric {
-    optional string name = 1;
+    optional int64 id = 1;
 
-    optional string what = 2;
+    optional int64 what = 2;
 
-    optional FieldFilter gauge_fields = 3;
+    optional FieldFilter gauge_fields_filter = 3;
 
-    optional string condition = 4;
+    optional int64 condition = 4;
 
-    repeated KeyMatcher dimension = 5;
+    optional FieldMatcher dimensions = 5;
 
-    optional Bucket bucket = 6;
+    optional TimeUnit bucket = 6;
 
     repeated MetricConditionLink links = 7;
 }
 
 message ValueMetric {
-    optional string name = 1;
+    optional int64 id = 1;
 
-    optional string what = 2;
+    optional int64 what = 2;
 
-    optional int32 value_field = 3;
+    optional FieldMatcher value_field = 3;
 
-    optional string condition = 4;
+    optional int64 condition = 4;
 
-    repeated KeyMatcher dimension = 5;
+    optional FieldMatcher dimensions = 5;
 
-    optional Bucket bucket = 6;
+    optional TimeUnit bucket = 6;
 
     repeated MetricConditionLink links = 7;
 
@@ -206,29 +242,51 @@
 }
 
 message Alert {
-    optional string name = 1;
+    optional int64 id = 1;
 
-    optional string metric_name = 2;
+    optional int64 metric_id = 2;
 
-    message IncidentdDetails {
-        repeated int32 section = 1;
-    }
-    optional IncidentdDetails incidentd_details = 3;
+    optional int32 num_buckets = 3;
 
-    optional int32 number_of_buckets = 4;
+    optional int32 refractory_period_secs = 4;
 
-    optional int32 refractory_period_secs = 5;
-
-    optional int64 trigger_if_sum_gt = 6;
+    optional double trigger_if_sum_gt = 5;
 }
 
-message AllowedLogSource {
-    repeated int32 uid = 1;
-    repeated string package = 2;
+message Alarm {
+    optional int64 id = 1;
+    optional int64 offset_millis = 2;
+    optional int64 period_millis = 3;
+}
+
+message IncidentdDetails {
+    repeated int32 section = 1;
+}
+
+message PerfettoDetails {
+    optional int32 perfetto_stuff = 1;
+}
+
+message Subscription {
+    optional int64 id = 1;
+
+    enum RuleType {
+        RULE_TYPE_UNSPECIFIED = 0;
+        ALARM = 1;
+        ALERT = 2;
+    }
+    optional RuleType rule_type = 2;
+
+    optional int64 rule_id = 3;
+
+    oneof subscriber_information {
+        IncidentdDetails incidentd_details = 4;
+        PerfettoDetails perfetto_details = 5;
+    }
 }
 
 message StatsdConfig {
-    optional string name = 1;
+    optional int64 id = 1;
 
     repeated EventMetric event_metric = 2;
 
@@ -246,5 +304,11 @@
 
     repeated Alert alert = 9;
 
-    optional AllowedLogSource log_source = 10;
+    repeated Alarm alarm = 10;
+
+    repeated Subscription subscription = 11;
+
+    repeated string allowed_log_source = 12;
+
+    repeated int64 no_report_metric = 13;
 }
diff --git a/core/java/android/service/autofill/Scorer.java b/cmds/statsd/src/statsd_internal.proto
similarity index 61%
copy from core/java/android/service/autofill/Scorer.java
copy to cmds/statsd/src/statsd_internal.proto
index c401855..25aacee 100644
--- a/core/java/android/service/autofill/Scorer.java
+++ b/cmds/statsd/src/statsd_internal.proto
@@ -13,16 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.service.autofill;
 
-/**
- * Helper class used to calculate a score.
- *
- * <p>Typically used to calculate the
- * <a href="AutofillService.html#FieldClassification">field classification</a> score between an
- * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value
- * predicted by an autofill service.
- */
-public interface Scorer {
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
 
+package android.os.statsd;
+
+option java_package = "com.android.os";
+option java_outer_classname = "StatsdInternalProto";
+
+message Field {
+  optional int32 field = 1;
+  optional int32 position_index = 2 [default = -1];
+  repeated Field child = 3;
 }
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 9919abf..c542db2 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -124,7 +124,7 @@
         }
         if (index < 2) continue;
 
-        sendBroadcast(ConfigKey(uid, configName));
+        sendBroadcast(ConfigKey(uid, StrToInt64(configName)));
     }
 }
 
@@ -198,6 +198,7 @@
             index++;
         }
         if (index < 2) continue;
+
         string file_name = StringPrintf("%s/%s", STATS_SERVICE_DIR, name);
         VLOG("full file %s", file_name.c_str());
         int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
@@ -206,7 +207,7 @@
             if (android::base::ReadFdToString(fd, &content)) {
                 StatsdConfig config;
                 if (config.ParseFromString(content)) {
-                    configsMap[ConfigKey(uid, configName)] = config;
+                    configsMap[ConfigKey(uid, StrToInt64(configName))] = config;
                     VLOG("map key uid=%d|name=%s", uid, name);
                 }
             }
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
index 3d923e2..cc02f34 100644
--- a/cmds/statsd/tests/ConfigManager_test.cpp
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -14,6 +14,7 @@
 
 #include "src/config/ConfigManager.h"
 #include "src/metrics/MetricsManager.h"
+#include "statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -31,7 +32,7 @@
 namespace statsd {
 
 static ostream& operator<<(ostream& os, const StatsdConfig& config) {
-    return os << "StatsdConfig{name=" << config.name().c_str() << "}";
+    return os << "StatsdConfig{id=" << config.id() << "}";
 }
 
 }  // namespace statsd
@@ -50,19 +51,21 @@
 /**
  * Validate that the ConfigKey is the one we wanted.
  */
-MATCHER_P2(ConfigKeyEq, uid, name, "") {
-    return arg.GetUid() == uid && arg.GetName() == name;
+MATCHER_P2(ConfigKeyEq, uid, id, "") {
+    return arg.GetUid() == uid && (long long)arg.GetId() == (long long)id;
 }
 
 /**
  * Validate that the StatsdConfig is the one we wanted.
  */
-MATCHER_P(StatsdConfigEq, name, "") {
-    return arg.name() == name;
+MATCHER_P(StatsdConfigEq, id, 0) {
+    return (long long)arg.id() == (long long)id;
 }
 
+const int64_t testConfigId = 12345;
+
 TEST(ConfigManagerTest, TestFakeConfig) {
-    auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, "test"),
+    auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, testConfigId),
                                                            build_fake_config(), 1000, new UidMap());
     EXPECT_TRUE(metricsManager->isConfigValid());
 }
@@ -77,13 +80,13 @@
     manager->AddListener(listener);
 
     StatsdConfig config91;
-    config91.set_name("91");
+    config91.set_id(91);
     StatsdConfig config92;
-    config92.set_name("92");
+    config92.set_id(92);
     StatsdConfig config93;
-    config93.set_name("93");
+    config93.set_id(93);
     StatsdConfig config94;
-    config94.set_name("94");
+    config94.set_id(94);
 
     {
         InSequence s;
@@ -91,42 +94,46 @@
         manager->Startup();
 
         // Add another one
-        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "zzz"), StatsdConfigEq("91")))
+        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("zzz")),
+            StatsdConfigEq(91)))
                 .RetiresOnSaturation();
-        manager->UpdateConfig(ConfigKey(1, "zzz"), config91);
+        manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config91);
 
         // Update It
-        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "zzz"), StatsdConfigEq("92")))
+        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("zzz")),
+            StatsdConfigEq(92)))
                 .RetiresOnSaturation();
-        manager->UpdateConfig(ConfigKey(1, "zzz"), config92);
+        manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config92);
 
         // Add one with the same uid but a different name
-        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "yyy"), StatsdConfigEq("93")))
+        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("yyy")),
+            StatsdConfigEq(93)))
                 .RetiresOnSaturation();
-        manager->UpdateConfig(ConfigKey(1, "yyy"), config93);
+        manager->UpdateConfig(ConfigKey(1, StringToId("yyy")), config93);
 
         // Add one with the same name but a different uid
-        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(2, "zzz"), StatsdConfigEq("94")))
+        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(2, StringToId("zzz")),
+            StatsdConfigEq(94)))
                 .RetiresOnSaturation();
-        manager->UpdateConfig(ConfigKey(2, "zzz"), config94);
+        manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config94);
 
         // Remove (1,yyy)
-        EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, "yyy")))
+        EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("yyy"))))
                 .RetiresOnSaturation();
-        manager->RemoveConfig(ConfigKey(1, "yyy"));
+        manager->RemoveConfig(ConfigKey(1, StringToId("yyy")));
 
         // Remove (2,zzz)
-        EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz")))
+        EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz"))))
                 .RetiresOnSaturation();
-        manager->RemoveConfig(ConfigKey(2, "zzz"));
+        manager->RemoveConfig(ConfigKey(2, StringToId("zzz")));
 
         // Remove (1,zzz)
-        EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, "zzz")))
+        EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("zzz"))))
                 .RetiresOnSaturation();
-        manager->RemoveConfig(ConfigKey(1, "zzz"));
+        manager->RemoveConfig(ConfigKey(1, StringToId("zzz")));
 
         // Remove (2,zzz) again and we shouldn't get the callback
-        manager->RemoveConfig(ConfigKey(2, "zzz"));
+        manager->RemoveConfig(ConfigKey(2, StringToId("zzz")));
     }
 }
 
@@ -142,16 +149,16 @@
     StatsdConfig config;
 
     EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _)).Times(5);
-    EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "xxx")));
-    EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "yyy")));
-    EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz")));
+    EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("xxx"))));
+    EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("yyy"))));
+    EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("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->StartupForTest();
+    manager->UpdateConfig(ConfigKey(1, StringToId("aaa")), config);
+    manager->UpdateConfig(ConfigKey(2, StringToId("xxx")), config);
+    manager->UpdateConfig(ConfigKey(2, StringToId("yyy")), config);
+    manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config);
+    manager->UpdateConfig(ConfigKey(3, StringToId("bbb")), config);
 
     manager->RemoveConfigs(2);
 }
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 1ec9155..111b4ba 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -32,65 +32,313 @@
 const int FIELD_ID_2 = 2;
 const int FIELD_ID_3 = 2;
 
+const int ATTRIBUTION_UID_FIELD_ID = 1;
+const int ATTRIBUTION_TAG_FIELD_ID = 2;
+
 // Private API from liblog.
 extern "C" void android_log_rewind(android_log_context ctx);
 
 #ifdef __ANDROID__
 TEST(AtomMatcherTest, TestSimpleMatcher) {
+    UidMap uidMap;
+
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
-    simpleMatcher->set_tag(TAG_ID);
+    simpleMatcher->set_atom_id(TAG_ID);
 
     LogEvent event(TAG_ID, 0);
+    EXPECT_TRUE(event.write(11));
     event.init();
 
     // Test
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Wrong tag id.
+    simpleMatcher->set_atom_id(TAG_ID + 1);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 }
 
-TEST(AtomMatcherTest, TestBoolMatcher) {
-    // Set up the matcher
-    AtomMatcher matcher;
-    auto simpleMatcher = matcher.mutable_simple_atom_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);
+TEST(AtomMatcherTest, TestAttributionMatcher) {
+    UidMap uidMap;
+    AttributionNode attribution_node1;
+    attribution_node1.set_uid(1111);
+    attribution_node1.set_tag("location1");
+
+    AttributionNode attribution_node2;
+    attribution_node2.set_uid(2222);
+    attribution_node2.set_tag("location2");
+
+    AttributionNode attribution_node3;
+    attribution_node3.set_uid(3333);
+    attribution_node3.set_tag("location3");
+    std::vector<AttributionNode> attribution_nodes =
+        { attribution_node1, attribution_node2, attribution_node3 };
 
     // Set up the event
     LogEvent event(TAG_ID, 0);
-    event.write(true);
-    event.write(false);
+    event.write(attribution_nodes);
+    event.write("some value");
+    // Convert to a LogEvent
+    event.init();
+
+    // Set up the matcher
+    AtomMatcher matcher;
+    auto simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+
+    // Match first node.
+    auto attributionMatcher = simpleMatcher->add_field_value_matcher();
+    attributionMatcher->set_field(FIELD_ID_1);
+    attributionMatcher->set_position(Position::FIRST);
+    attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field(
+        ATTRIBUTION_TAG_FIELD_ID);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("tag");
+
+    auto fieldMatcher = simpleMatcher->add_field_value_matcher();
+    fieldMatcher->set_field(FIELD_ID_2);
+    fieldMatcher->set_eq_string("some value");
+
+    // Tag not matched.
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("location3");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("location1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Match last node.
+    attributionMatcher->set_position(Position::LAST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("location3");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Match any node.
+    attributionMatcher->set_position(Position::ANY);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("location1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("location2");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("location3");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("location4");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Attribution match but primitive field not match.
+    attributionMatcher->set_position(Position::ANY);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("location2");
+    fieldMatcher->set_eq_string("wrong value");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldMatcher->set_eq_string("some value");
+
+    // Uid match.
+    attributionMatcher->set_position(Position::ANY);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_field(
+        ATTRIBUTION_UID_FIELD_ID);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("pkg0");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    uidMap.updateMap({1111, 1111, 2222, 3333, 3333} /* uid list */,
+        {1, 1, 2, 1, 2} /* version list */,
+        {android::String16("pkg0"), android::String16("pkg1"),
+         android::String16("pkg1"), android::String16("Pkg2"),
+         android::String16("PkG3")} /* package name list */);
+
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg3");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg2");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg0");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    attributionMatcher->set_position(Position::FIRST);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg0");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg3");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg2");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    attributionMatcher->set_position(Position::LAST);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg0");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg3");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg2");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg1");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Uid + tag.
+    attributionMatcher->set_position(Position::ANY);
+    attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field(
+        ATTRIBUTION_TAG_FIELD_ID);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg0");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location2");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location3");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location3");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location1");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    attributionMatcher->set_position(Position::FIRST);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg0");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location2");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location3");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location3");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location1");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    attributionMatcher->set_position(Position::LAST);
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg0");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location1");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location1");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg1");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location2");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg2");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location3");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location3");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+        ->set_eq_string("pkg3");
+    attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+        ->set_eq_string("location1");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+}
+
+TEST(AtomMatcherTest, TestBoolMatcher) {
+    UidMap uidMap;
+    // Set up the matcher
+    AtomMatcher matcher;
+    auto simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+    auto keyValue1 = simpleMatcher->add_field_value_matcher();
+    keyValue1->set_field(FIELD_ID_1);
+    auto keyValue2 = simpleMatcher->add_field_value_matcher();
+    keyValue2->set_field(FIELD_ID_2);
+
+    // Set up the event
+    LogEvent event(TAG_ID, 0);
+    EXPECT_TRUE(event.write(true));
+    EXPECT_TRUE(event.write(false));
     // Convert to a LogEvent
     event.init();
 
     // Test
     keyValue1->set_eq_bool(true);
     keyValue2->set_eq_bool(false);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
 
     keyValue1->set_eq_bool(false);
     keyValue2->set_eq_bool(false);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
-    keyValue1->set_eq_bool(true);
-    keyValue2->set_eq_bool(false);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    keyValue1->set_eq_bool(false);
+    keyValue2->set_eq_bool(true);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     keyValue1->set_eq_bool(true);
     keyValue2->set_eq_bool(true);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 }
 
 TEST(AtomMatcherTest, TestStringMatcher) {
+    UidMap uidMap;
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
-    simpleMatcher->set_tag(TAG_ID);
-    auto keyValue = simpleMatcher->add_key_value_matcher();
-    keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
+    simpleMatcher->set_atom_id(TAG_ID);
+    auto keyValue = simpleMatcher->add_field_value_matcher();
+    keyValue->set_field(FIELD_ID_1);
     keyValue->set_eq_string("some value");
 
     // Set up the event
@@ -100,18 +348,19 @@
     event.init();
 
     // Test
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
 }
 
 TEST(AtomMatcherTest, TestMultiFieldsMatcher) {
+    UidMap uidMap;
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_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);
+    simpleMatcher->set_atom_id(TAG_ID);
+    auto keyValue1 = simpleMatcher->add_field_value_matcher();
+    keyValue1->set_field(FIELD_ID_1);
+    auto keyValue2 = simpleMatcher->add_field_value_matcher();
+    keyValue2->set_field(FIELD_ID_2);
 
     // Set up the event
     LogEvent event(TAG_ID, 0);
@@ -124,25 +373,26 @@
     // Test
     keyValue1->set_eq_int(2);
     keyValue2->set_eq_int(3);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
 
     keyValue1->set_eq_int(2);
     keyValue2->set_eq_int(4);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     keyValue1->set_eq_int(4);
     keyValue2->set_eq_int(3);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 }
 
 TEST(AtomMatcherTest, TestIntComparisonMatcher) {
+    UidMap uidMap;
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
 
-    simpleMatcher->set_tag(TAG_ID);
-    auto keyValue = simpleMatcher->add_key_value_matcher();
-    keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
+    simpleMatcher->set_atom_id(TAG_ID);
+    auto keyValue = simpleMatcher->add_field_value_matcher();
+    keyValue->set_field(FIELD_ID_1);
 
     // Set up the event
     LogEvent event(TAG_ID, 0);
@@ -153,82 +403,83 @@
 
     // eq_int
     keyValue->set_eq_int(10);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
     keyValue->set_eq_int(11);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
     keyValue->set_eq_int(12);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // lt_int
     keyValue->set_lt_int(10);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
     keyValue->set_lt_int(11);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
     keyValue->set_lt_int(12);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // lte_int
     keyValue->set_lte_int(10);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
     keyValue->set_lte_int(11);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
     keyValue->set_lte_int(12);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // gt_int
     keyValue->set_gt_int(10);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
     keyValue->set_gt_int(11);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
     keyValue->set_gt_int(12);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // gte_int
     keyValue->set_gte_int(10);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
     keyValue->set_gte_int(11);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
     keyValue->set_gte_int(12);
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 }
 
 TEST(AtomMatcherTest, TestFloatComparisonMatcher) {
+    UidMap uidMap;
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
-    simpleMatcher->set_tag(TAG_ID);
+    simpleMatcher->set_atom_id(TAG_ID);
 
-    auto keyValue = simpleMatcher->add_key_value_matcher();
-    keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
+    auto keyValue = simpleMatcher->add_field_value_matcher();
+    keyValue->set_field(FIELD_ID_1);
 
     LogEvent event1(TAG_ID, 0);
     keyValue->set_lt_float(10.0);
     event1.write(10.1f);
     event1.init();
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event1));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
 
     LogEvent event2(TAG_ID, 0);
     event2.write(9.9f);
     event2.init();
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event2));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
 
     LogEvent event3(TAG_ID, 0);
     event3.write(10.1f);
     event3.init();
     keyValue->set_gt_float(10.0);
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event3));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3));
 
     LogEvent event4(TAG_ID, 0);
     event4.write(9.9f);
     event4.init();
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event4));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4));
 }
 
 // Helper for the composite matchers.
 void addSimpleMatcher(SimpleAtomMatcher* 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);
+    simpleMatcher->set_atom_id(tag);
+    auto keyValue = simpleMatcher->add_field_value_matcher();
+    keyValue->set_field(key);
     keyValue->set_eq_int(val);
 }
 
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
new file mode 100644
index 0000000..fd28460
--- /dev/null
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -0,0 +1,573 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <log/log_event_list.h>
+#include "src/logd/LogEvent.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(LogEventTest, testEmptyEvent) {
+    const int32_t TAG_ID = 123;
+    LogEvent event(TAG_ID, 0);
+    event.init();
+
+    DimensionsValue dimensionsValue;
+    EXPECT_FALSE(event.GetSimpleAtomDimensionsValueProto(234, &dimensionsValue));
+    FieldMatcher dimensions;
+    dimensions.set_field(event.GetTagId());
+    EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue));
+
+    dimensions.add_child()->set_field(3);
+    dimensions.mutable_child(0)->set_position(Position::FIRST);
+    EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue));
+
+    dimensions.mutable_child(0)->set_position(Position::ANY);
+    EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue));
+
+    dimensions.mutable_child(0)->set_position(Position::LAST);
+    EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue));
+}
+
+TEST(LogEventTest, testRepeatedAttributionNode) {
+    const int32_t TAG_ID = 123;
+    LogEvent event(TAG_ID, 0);
+    AttributionNode attribution_node1;
+    attribution_node1.set_uid(1111);
+    attribution_node1.set_tag("locationService");
+
+    AttributionNode attribution_node2;
+    attribution_node2.set_uid(2222);
+    attribution_node2.set_tag("locationService2");
+
+    AttributionNode attribution_node3;
+    attribution_node3.set_uid(3333);
+    attribution_node3.set_tag("locationService3");
+    std::vector<AttributionNode> attribution_nodes =
+        {attribution_node1, attribution_node2, attribution_node3};
+
+    // 1nd field: int32.
+    EXPECT_TRUE(event.write(int32_t(11)));
+    // 2rd field: float.
+    EXPECT_TRUE(event.write(3.45f));
+    // Here it assume that the atom proto contains a repeated AttributionNode field.
+    // 3rd field: attribution node. This is repeated field.
+    EXPECT_TRUE(event.write(attribution_nodes));
+    // 4th field: bool.
+    EXPECT_TRUE(event.write(true));
+    // 5th field: long.
+    EXPECT_TRUE(event.write(uint64_t(1234)));
+
+    event.init();
+
+    DimensionsValue dimensionsValue;
+    // Query single primitive fields.
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11);
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f);
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(4, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4);
+    // The bool value is stored in value_int field as logD does not support bool.
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true);
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234));
+
+    // First attribution.
+    FieldMatcher first_uid_dimensions;
+    first_uid_dimensions.set_field(event.GetTagId());
+    first_uid_dimensions.add_child()->set_field(3);
+    first_uid_dimensions.mutable_child(0)->set_position(Position::FIRST);
+    first_uid_dimensions.mutable_child(0)->add_child()->set_field(1);
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_uid_dimensions, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 1111);
+
+    FieldMatcher first_tag_dimensions = first_uid_dimensions;
+    first_tag_dimensions.mutable_child(0)->mutable_child(0)->set_field(2);
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_tag_dimensions, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_str(), "locationService");
+
+    FieldMatcher first_attribution_dimensions = first_uid_dimensions;
+    first_attribution_dimensions.mutable_child(0)->add_child()->set_field(2);
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_attribution_dimensions, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 1111);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).value_str(), "locationService");
+
+    FieldMatcher last_attribution_dimensions = first_attribution_dimensions;
+    last_attribution_dimensions.mutable_child(0)->set_position(Position::LAST);
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(last_attribution_dimensions, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 3333);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).value_str(), "locationService3");
+
+    FieldMatcher any_attribution_dimensions = first_attribution_dimensions;
+    any_attribution_dimensions.mutable_child(0)->set_position(Position::ANY);
+    std::vector<DimensionsValue> dimensionsValues;
+    event.GetAtomDimensionsValueProtos(any_attribution_dimensions, &dimensionsValues);
+    EXPECT_EQ(dimensionsValues.size(), 3u);
+    EXPECT_EQ(dimensionsValues[0].field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 1111);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).value_str(), "locationService");
+    EXPECT_EQ(dimensionsValues[1].field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 2222);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).value_str(), "locationService2");
+    EXPECT_EQ(dimensionsValues[2].field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 3333);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).value_str(), "locationService3");
+
+    FieldMatcher mixed_dimensions = any_attribution_dimensions;
+    mixed_dimensions.add_child()->set_field(1000);
+    mixed_dimensions.add_child()->set_field(6); // missing field.
+    mixed_dimensions.add_child()->set_field(3); // position not set.
+    mixed_dimensions.add_child()->set_field(5);
+    mixed_dimensions.add_child()->set_field(1);
+    dimensionsValues.clear();
+    event.GetAtomDimensionsValueProtos(mixed_dimensions, &dimensionsValues);
+    EXPECT_EQ(dimensionsValues.size(), 3u);
+    EXPECT_EQ(dimensionsValues[0].field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value_size(), 3);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+              1111);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(),
+              "locationService");
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(1).field(), 5);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(1).value_long(), long(1234));
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(2).field(), 1);
+    EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(2).value_int(), 11);
+
+    EXPECT_EQ(dimensionsValues[1].field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value_size(), 3);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+              2222);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(),
+              "locationService2");
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(1).field(), 5);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(1).value_long(), long(1234));
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(2).field(), 1);
+    EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(2).value_int(), 11);
+
+    EXPECT_EQ(dimensionsValues[2].field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value_size(), 3);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+              3333);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(),
+              "locationService3");
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(1).field(), 5);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(1).value_long(), long(1234));
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(2).field(), 1);
+    EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(2).value_int(), 11);
+
+    FieldMatcher wrong_dimensions = mixed_dimensions;
+    // Wrong tagId.
+    wrong_dimensions.set_field(event.GetTagId() + 100);
+    dimensionsValues.clear();
+    event.GetAtomDimensionsValueProtos(wrong_dimensions, &dimensionsValues);
+    EXPECT_TRUE(dimensionsValues.empty());
+}
+
+TEST(LogEventTest, testMessageField) {
+    const int32_t TAG_ID = 123;
+    LogEvent event(TAG_ID, 0);
+    AttributionNode attribution_node1;
+    attribution_node1.set_uid(1111);
+    attribution_node1.set_tag("locationService");
+
+    AttributionNode attribution_node2;
+    attribution_node2.set_uid(2222);
+    attribution_node2.set_tag("locationService2");
+
+    // 1nd field: int32.
+    EXPECT_TRUE(event.write(int32_t(11)));
+    // 2rd field: float.
+    EXPECT_TRUE(event.write(3.45f));
+    // Here it assume that the atom proto contains two optional AttributionNode fields.
+    // 3rd field: attribution node. This is not repeated field.
+    EXPECT_TRUE(event.write(attribution_node1));
+    // 4th field: another attribution field. This is not repeated field.
+    EXPECT_TRUE(event.write(attribution_node2));
+    // 5th field: bool.
+    EXPECT_TRUE(event.write(true));
+    // 6th field: long.
+    EXPECT_TRUE(event.write(uint64_t(1234)));
+
+    event.init();
+
+    FieldMatcher uid_dimensions1;
+    uid_dimensions1.set_field(event.GetTagId());
+    uid_dimensions1.add_child()->set_field(3);
+    uid_dimensions1.mutable_child(0)->add_child()->set_field(1);
+
+    FieldMatcher tag_dimensions1;
+    tag_dimensions1.set_field(event.GetTagId());
+    tag_dimensions1.add_child()->set_field(3);
+    tag_dimensions1.mutable_child(0)->add_child()->set_field(2);
+
+    FieldMatcher attribution_dimensions1;
+    attribution_dimensions1.set_field(event.GetTagId());
+    attribution_dimensions1.add_child()->set_field(3);
+    attribution_dimensions1.mutable_child(0)->add_child()->set_field(1);
+    attribution_dimensions1.mutable_child(0)->add_child()->set_field(2);
+
+    FieldMatcher uid_dimensions2 = uid_dimensions1;
+    uid_dimensions2.mutable_child(0)->set_field(4);
+
+    FieldMatcher tag_dimensions2 = tag_dimensions1;
+    tag_dimensions2.mutable_child(0)->set_field(4);
+
+    FieldMatcher attribution_dimensions2 = attribution_dimensions1;
+    attribution_dimensions2.mutable_child(0)->set_field(4);
+
+    FieldMatcher mixed_dimensions = attribution_dimensions1;
+    mixed_dimensions.add_child()->set_field(4);
+    mixed_dimensions.mutable_child(1)->add_child()->set_field(1);
+    mixed_dimensions.add_child()->set_field(1000);
+    mixed_dimensions.add_child()->set_field(5);
+    mixed_dimensions.add_child()->set_field(1);
+
+    DimensionsValue dimensionsValue;
+
+    // Query single primitive fields.
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11);
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f);
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5);
+    // The bool value is stored in value_int field as logD does not support bool.
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true);
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(6, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 6);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234));
+
+    // Query atom field 3: attribution node uid field only.
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(uid_dimensions1, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 1111);
+
+    // Query atom field 3: attribution node tag field only.
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(tag_dimensions1, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_str(), "locationService");
+
+    // Query atom field 3: attribution node uid + tag fields.
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(attribution_dimensions1, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 1111);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).value_str(), "locationService");
+
+    // Query atom field 4: attribution node uid field only.
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(uid_dimensions2, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 2222);
+
+    // Query atom field 4: attribution node tag field only.
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(tag_dimensions2, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_str(), "locationService2");
+
+    // Query atom field 4: attribution node uid + tag fields.
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(attribution_dimensions2, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 2222);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).value_str(), "locationService2");
+
+    // Query multiple fields:
+    // 1/ Field 3: attribution uid + tag.
+    // 2/ Field 4: attribution uid only.
+    // 3/ Field not exist.
+    // 4/ Primitive fields #5
+    // 5/ Primitive fields #1
+    EXPECT_TRUE(event.GetAtomDimensionsValueProto(mixed_dimensions, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 4);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value_size(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(0).value_int(), 1111);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+        .dimensions_value(1).value_str(), "locationService");
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).field(), 4);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple()
+        .dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple()
+        .dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple()
+        .dimensions_value(0).value_int(), 2222);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(2).field(), 5);
+    // The bool value is stored in value_int field as logD does not support bool.
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(2).value_int(), true);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(3).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(3).value_int(), 11);
+}
+
+TEST(LogEventTest, testAllPrimitiveFields) {
+    const int32_t TAG_ID = 123;
+    LogEvent event(TAG_ID, 0);
+
+    // 1nd field: int32.
+    EXPECT_TRUE(event.write(int32_t(11)));
+    // 2rd field: float.
+    EXPECT_TRUE(event.write(3.45f));
+    // 3th field: string.
+    EXPECT_TRUE(event.write("test"));
+    // 4th field: bool.
+    EXPECT_TRUE(event.write(true));
+    // 5th field: bool.
+    EXPECT_TRUE(event.write(false));
+    // 6th field: long.
+    EXPECT_TRUE(event.write(uint64_t(1234)));
+
+    event.init();
+
+    DimensionsValue dimensionsValue;
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11);
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f);
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(3, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_str(), "test");
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(4, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4);
+    // The bool value is stored in value_int field as logD does not support bool.
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true);
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5);
+    // The bool value is stored in value_int field as logD does not support bool.
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), false);
+
+    EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(6, &dimensionsValue));
+    EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 6);
+    EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234));
+
+    // Field not exist.
+    EXPECT_FALSE(event.GetSimpleAtomDimensionsValueProto(7, &dimensionsValue));
+}
+
+TEST(LogEventTest, testWriteAtomProtoToStream) {
+    AttributionNode attribution_node1;
+    attribution_node1.set_uid(1111);
+    attribution_node1.set_tag("locationService");
+
+    AttributionNode attribution_node2;
+    attribution_node2.set_uid(2222);
+    attribution_node2.set_tag("locationService2");
+
+    AttributionNode attribution_node3;
+    attribution_node3.set_uid(3333);
+    attribution_node3.set_tag("locationService3");
+    std::vector<AttributionNode> attribution_nodes =
+        {attribution_node1, attribution_node2, attribution_node3};
+
+    LogEvent event(1, 0);
+    EXPECT_TRUE(event.write("222"));
+    EXPECT_TRUE(event.write(attribution_nodes));
+    EXPECT_TRUE(event.write(345));
+    EXPECT_TRUE(event.write(attribution_node3));
+    EXPECT_TRUE(event.write("hello"));
+    event.init();
+
+    util::ProtoOutputStream protoOutput;
+    // For now only see whether it will crash.
+    // TODO(yanglu): test parsing from stream.
+    event.ToProto(protoOutput);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
\ No newline at end of file
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 3c8ccab..fe0f59d 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -21,6 +21,7 @@
 #include "src/metrics/MetricProducer.h"
 #include "src/metrics/ValueMetricProducer.h"
 #include "src/metrics/metrics_manager_util.h"
+#include "statsd_test_util.h"
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
@@ -40,53 +41,55 @@
 
 // TODO: ADD MORE TEST CASES.
 
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
 
 const long timeBaseSec = 1000;
 
 StatsdConfig buildGoodConfig() {
     StatsdConfig config;
-    config.set_name("12345");
+    config.set_id(12345);
 
     AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_IS_ON");
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
 
     SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
             2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_IS_OFF");
+    eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
 
     simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_ON_OR_OFF");
+    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
 
     AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
     combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher("SCREEN_IS_ON");
-    combination->add_matcher("SCREEN_IS_OFF");
+    combination->add_matcher(StringToId("SCREEN_IS_ON"));
+    combination->add_matcher(StringToId("SCREEN_IS_OFF"));
 
     CountMetric* metric = config.add_count_metric();
-    metric->set_name("3");
-    metric->set_what("SCREEN_IS_ON");
-    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
-    KeyMatcher* keyMatcher = metric->add_dimension();
-    keyMatcher->set_key(1);
+    metric->set_id(3);
+    metric->set_what(StringToId("SCREEN_IS_ON"));
+    metric->set_bucket(ONE_MINUTE);
+    metric->mutable_dimensions()->set_field(2 /*SCREEN_STATE_CHANGE*/);
+    metric->mutable_dimensions()->add_child()->set_field(1);
+
+    config.add_no_report_metric(3);
 
     auto alert = config.add_alert();
-    alert->set_name("3");
-    alert->set_metric_name("3");
-    alert->set_number_of_buckets(10);
+    alert->set_id(3);
+    alert->set_metric_id(3);
+    alert->set_num_buckets(10);
     alert->set_refractory_period_secs(100);
     alert->set_trigger_if_sum_gt(100);
     return config;
@@ -94,48 +97,48 @@
 
 StatsdConfig buildCircleMatchers() {
     StatsdConfig config;
-    config.set_name("12345");
+    config.set_id(12345);
 
     AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_IS_ON");
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
 
     SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
             2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_ON_OR_OFF");
+    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
 
     AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
     combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher("SCREEN_IS_ON");
+    combination->add_matcher(StringToId("SCREEN_IS_ON"));
     // Circle dependency
-    combination->add_matcher("SCREEN_ON_OR_OFF");
+    combination->add_matcher(StringToId("SCREEN_ON_OR_OFF"));
 
     return config;
 }
 
 StatsdConfig buildAlertWithUnknownMetric() {
     StatsdConfig config;
-    config.set_name("12345");
+    config.set_id(12345);
 
     AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_IS_ON");
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
 
     CountMetric* metric = config.add_count_metric();
-    metric->set_name("3");
-    metric->set_what("SCREEN_IS_ON");
-    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
-    KeyMatcher* keyMatcher = metric->add_dimension();
-    keyMatcher->set_key(1);
+    metric->set_id(3);
+    metric->set_what(StringToId("SCREEN_IS_ON"));
+    metric->set_bucket(ONE_MINUTE);
+    metric->mutable_dimensions()->set_field(2 /*SCREEN_STATE_CHANGE*/);
+    metric->mutable_dimensions()->add_child()->set_field(1);
 
     auto alert = config.add_alert();
-    alert->set_name("3");
-    alert->set_metric_name("2");
-    alert->set_number_of_buckets(10);
+    alert->set_id(3);
+    alert->set_metric_id(2);
+    alert->set_num_buckets(10);
     alert->set_refractory_period_secs(100);
     alert->set_trigger_if_sum_gt(100);
     return config;
@@ -143,83 +146,83 @@
 
 StatsdConfig buildMissingMatchers() {
     StatsdConfig config;
-    config.set_name("12345");
+    config.set_id(12345);
 
     AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_IS_ON");
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
 
     SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
             2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_ON_OR_OFF");
+    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
 
     AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
     combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher("SCREEN_IS_ON");
+    combination->add_matcher(StringToId("SCREEN_IS_ON"));
     // undefined matcher
-    combination->add_matcher("ABC");
+    combination->add_matcher(StringToId("ABC"));
 
     return config;
 }
 
 StatsdConfig buildMissingPredicate() {
     StatsdConfig config;
-    config.set_name("12345");
+    config.set_id(12345);
 
     CountMetric* metric = config.add_count_metric();
-    metric->set_name("3");
-    metric->set_what("SCREEN_EVENT");
-    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
-    metric->set_condition("SOME_CONDITION");
+    metric->set_id(3);
+    metric->set_what(StringToId("SCREEN_EVENT"));
+    metric->set_bucket(ONE_MINUTE);
+    metric->set_condition(StringToId("SOME_CONDITION"));
 
     AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_EVENT");
+    eventMatcher->set_id(StringToId("SCREEN_EVENT"));
 
     SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(2);
+    simpleAtomMatcher->set_atom_id(2);
 
     return config;
 }
 
 StatsdConfig buildDimensionMetricsWithMultiTags() {
     StatsdConfig config;
-    config.set_name("12345");
+    config.set_id(12345);
 
     AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("BATTERY_VERY_LOW");
+    eventMatcher->set_id(StringToId("BATTERY_VERY_LOW"));
     SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(2);
+    simpleAtomMatcher->set_atom_id(2);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("BATTERY_VERY_VERY_LOW");
+    eventMatcher->set_id(StringToId("BATTERY_VERY_VERY_LOW"));
     simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(3);
+    simpleAtomMatcher->set_atom_id(3);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("BATTERY_LOW");
+    eventMatcher->set_id(StringToId("BATTERY_LOW"));
 
     AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
     combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher("BATTERY_VERY_LOW");
-    combination->add_matcher("BATTERY_VERY_VERY_LOW");
+    combination->add_matcher(StringToId("BATTERY_VERY_LOW"));
+    combination->add_matcher(StringToId("BATTERY_VERY_VERY_LOW"));
 
     // Count process state changes, slice by uid, while SCREEN_IS_OFF
     CountMetric* metric = config.add_count_metric();
-    metric->set_name("3");
-    metric->set_what("BATTERY_LOW");
-    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
-    KeyMatcher* keyMatcher = metric->add_dimension();
-    keyMatcher->set_key(1);
+    metric->set_id(3);
+    metric->set_what(StringToId("BATTERY_LOW"));
+    metric->set_bucket(ONE_MINUTE);
+    // This case is interesting. We want to dimension across two atoms.
+    metric->mutable_dimensions()->add_child()->set_field(1);
 
     auto alert = config.add_alert();
-    alert->set_name("3");
-    alert->set_metric_name("3");
-    alert->set_number_of_buckets(10);
+    alert->set_id(103);
+    alert->set_metric_id(3);
+    alert->set_num_buckets(10);
     alert->set_refractory_period_secs(100);
     alert->set_trigger_if_sum_gt(100);
     return config;
@@ -227,46 +230,47 @@
 
 StatsdConfig buildCirclePredicates() {
     StatsdConfig config;
-    config.set_name("12345");
+    config.set_id(12345);
 
     AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_IS_ON");
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
 
     SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
             2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
 
     eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_name("SCREEN_IS_OFF");
+    eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
 
     simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
 
     auto condition = config.add_predicate();
-    condition->set_name("SCREEN_IS_ON");
+    condition->set_id(StringToId("SCREEN_IS_ON"));
     SimplePredicate* simplePredicate = condition->mutable_simple_predicate();
-    simplePredicate->set_start("SCREEN_IS_ON");
-    simplePredicate->set_stop("SCREEN_IS_OFF");
+    simplePredicate->set_start(StringToId("SCREEN_IS_ON"));
+    simplePredicate->set_stop(StringToId("SCREEN_IS_OFF"));
 
     condition = config.add_predicate();
-    condition->set_name("SCREEN_IS_EITHER_ON_OFF");
+    condition->set_id(StringToId("SCREEN_IS_EITHER_ON_OFF"));
 
     Predicate_Combination* combination = condition->mutable_combination();
     combination->set_operation(LogicalOperation::OR);
-    combination->add_predicate("SCREEN_IS_ON");
-    combination->add_predicate("SCREEN_IS_EITHER_ON_OFF");
+    combination->add_predicate(StringToId("SCREEN_IS_ON"));
+    combination->add_predicate(StringToId("SCREEN_IS_EITHER_ON_OFF"));
 
     return config;
 }
 
 TEST(MetricsManagerTest, TestGoodConfig) {
+    UidMap uidMap;
     StatsdConfig config = buildGoodConfig();
     set<int> allTagIds;
     vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -276,15 +280,19 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
+    std::set<int64_t> noReportMetricIds;
 
-    EXPECT_TRUE(initStatsdConfig(kConfigKey, config, timeBaseSec,  allTagIds, allAtomMatchers,
+    EXPECT_TRUE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
                                  allConditionTrackers, allMetricProducers, allAnomalyTrackers,
-                                 conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+                                 conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+                                 noReportMetricIds));
     EXPECT_EQ(1u, allMetricProducers.size());
     EXPECT_EQ(1u, allAnomalyTrackers.size());
+    EXPECT_EQ(1u, noReportMetricIds.size());
 }
 
 TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
+    UidMap uidMap;
     StatsdConfig config = buildDimensionMetricsWithMultiTags();
     set<int> allTagIds;
     vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -294,13 +302,16 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
+    std::set<int64_t> noReportMetricIds;
 
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
-                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+                                  noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
+    UidMap uidMap;
     StatsdConfig config = buildCircleMatchers();
     set<int> allTagIds;
     vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -310,13 +321,16 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
+    std::set<int64_t> noReportMetricIds;
 
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
-                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+                                  noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestMissingMatchers) {
+    UidMap uidMap;
     StatsdConfig config = buildMissingMatchers();
     set<int> allTagIds;
     vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -326,12 +340,15 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+    std::set<int64_t> noReportMetricIds;
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
-                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+                                  noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestMissingPredicate) {
+    UidMap uidMap;
     StatsdConfig config = buildMissingPredicate();
     set<int> allTagIds;
     vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -341,12 +358,15 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+    std::set<int64_t> noReportMetricIds;
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
-                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+                                  noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestCirclePredicateDependency) {
+    UidMap uidMap;
     StatsdConfig config = buildCirclePredicates();
     set<int> allTagIds;
     vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -356,13 +376,16 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
+    std::set<int64_t> noReportMetricIds;
 
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
-                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+                                  noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
+    UidMap uidMap;
     StatsdConfig config = buildAlertWithUnknownMetric();
     set<int> allTagIds;
     vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -372,10 +395,12 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
+    std::set<int64_t> noReportMetricIds;
 
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
                                   allConditionTrackers, allMetricProducers, allAnomalyTrackers,
-                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+                                  noReportMetricIds));
 }
 
 #else
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 9b96bb7..aab5bed 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -41,7 +41,7 @@
  */
 class MockMetricsManager : public MetricsManager {
 public:
-    MockMetricsManager() : MetricsManager(ConfigKey(1, "key"), StatsdConfig(), 1000, new UidMap()) {
+    MockMetricsManager() : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, new UidMap()) {
     }
 
     MOCK_METHOD0(byteSize, size_t());
@@ -52,40 +52,40 @@
     sp<UidMap> m = new UidMap();
     sp<AnomalyMonitor> anomalyMonitor;
     // Construct the processor with a dummy sendBroadcast function that does nothing.
-    StatsLogProcessor p(m, anomalyMonitor, [](const ConfigKey& key) {});
+    StatsLogProcessor p(m, anomalyMonitor, 0, [](const ConfigKey& key) {});
 
     MockMetricsManager mockMetricsManager;
 
-    ConfigKey key(100, "key");
+    ConfigKey key(100, 12345);
     // Expect only the first flush to trigger a check for byte size since the last two are
     // rate-limited.
     EXPECT_CALL(mockMetricsManager, byteSize()).Times(1);
-    p.flushIfNecessary(99, key, mockMetricsManager);
-    p.flushIfNecessary(100, key, mockMetricsManager);
-    p.flushIfNecessary(101, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(99, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(100, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(101, key, mockMetricsManager);
 }
 
 TEST(StatsLogProcessorTest, TestRateLimitBroadcast) {
     sp<UidMap> m = new UidMap();
     sp<AnomalyMonitor> anomalyMonitor;
     int broadcastCount = 0;
-    StatsLogProcessor p(m, anomalyMonitor,
+    StatsLogProcessor p(m, anomalyMonitor, 0,
                         [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
 
     MockMetricsManager mockMetricsManager;
 
-    ConfigKey key(100, "key");
+    ConfigKey key(100, 12345);
     EXPECT_CALL(mockMetricsManager, byteSize())
             .Times(2)
             .WillRepeatedly(Return(int(StatsdStats::kMaxMetricsBytesPerConfig * .95)));
 
     // Expect only one broadcast despite always returning a size that should trigger broadcast.
-    p.flushIfNecessary(1, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(1, key, mockMetricsManager);
     EXPECT_EQ(1, broadcastCount);
 
     // This next call to flush should not trigger a broadcast.
     p.mLastByteSizeTimes.clear();  // Force another check for byte size.
-    p.flushIfNecessary(2, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(2, key, mockMetricsManager);
     EXPECT_EQ(1, broadcastCount);
 }
 
@@ -93,12 +93,12 @@
     sp<UidMap> m = new UidMap();
     sp<AnomalyMonitor> anomalyMonitor;
     int broadcastCount = 0;
-    StatsLogProcessor p(m, anomalyMonitor,
+    StatsLogProcessor p(m, anomalyMonitor, 0,
                         [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
 
     MockMetricsManager mockMetricsManager;
 
-    ConfigKey key(100, "key");
+    ConfigKey key(100, 12345);
     EXPECT_CALL(mockMetricsManager, byteSize())
             .Times(1)
             .WillRepeatedly(Return(int(StatsdStats::kMaxMetricsBytesPerConfig * 1.2)));
@@ -106,7 +106,7 @@
     EXPECT_CALL(mockMetricsManager, onDumpReport(_)).Times(1);
 
     // Expect to call the onDumpReport and skip the broadcast.
-    p.flushIfNecessary(1, key, mockMetricsManager);
+    p.flushIfNecessaryLocked(1, key, mockMetricsManager);
     EXPECT_EQ(0, broadcastCount);
 }
 
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 3fa96d3..945af27 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -18,6 +18,7 @@
 #include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
 #include "statslog.h"
+#include "statsd_test_util.h"
 
 #include <gtest/gtest.h>
 
@@ -37,7 +38,7 @@
     sp<UidMap> m = new UidMap();
     sp<AnomalyMonitor> anomalyMonitor;
     // Construct the processor with a dummy sendBroadcast function that does nothing.
-    StatsLogProcessor p(m, anomalyMonitor, [](const ConfigKey& key) {});
+    StatsLogProcessor p(m, anomalyMonitor, 0, [](const ConfigKey& key) {});
     LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
     addEvent.write(100);  // parent UID
     addEvent.write(101);  // isolated UID
@@ -156,8 +157,8 @@
 TEST(UidMapTest, TestClearingOutput) {
     UidMap m;
 
-    ConfigKey config1(1, "config1");
-    ConfigKey config2(1, "config2");
+    ConfigKey config1(1, StringToId("config1"));
+    ConfigKey config2(1, StringToId("config2"));
 
     m.OnConfigUpdated(config1);
 
@@ -211,7 +212,7 @@
 TEST(UidMapTest, TestMemoryComputed) {
     UidMap m;
 
-    ConfigKey config1(1, "config1");
+    ConfigKey config1(1, StringToId("config1"));
     m.OnConfigUpdated(config1);
 
     size_t startBytes = m.mBytesUsed;
@@ -241,7 +242,7 @@
     UidMap m;
     string buf;
 
-    ConfigKey config1(1, "config1");
+    ConfigKey config1(1, StringToId("config1"));
     m.OnConfigUpdated(config1);
 
     size_t startBytes = m.mBytesUsed;
@@ -273,4 +274,4 @@
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index f62171d..66bfa68 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -31,17 +31,13 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
 
 HashableDimensionKey getMockDimensionKey(int key, string value) {
-    KeyValuePair pair;
-    pair.set_key(key);
-    pair.set_value_str(value);
-
-    vector<KeyValuePair> pairs;
-    pairs.push_back(pair);
-
-    return HashableDimensionKey(pairs);
+    DimensionsValue dimensionsValue;
+    dimensionsValue.set_field(key);
+    dimensionsValue.set_value_str(value);
+    return HashableDimensionKey(dimensionsValue);
 }
 
 void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list,
@@ -58,11 +54,73 @@
     return bucket;
 }
 
+// Returns the value, for the given key, in that bucket, or 0 if not present.
+int64_t getBucketValue(const std::shared_ptr<DimToValMap>& bucket,
+                       const HashableDimensionKey& key) {
+    const auto& itr = bucket->find(key);
+    if (itr != bucket->end()) {
+        return itr->second;
+    }
+    return 0;
+}
+
+// Returns true if keys in trueList are detected as anomalies and keys in falseList are not.
+bool detectAnomaliesPass(AnomalyTracker& tracker,
+                         const int64_t& bucketNum,
+                         const std::shared_ptr<DimToValMap>& currentBucket,
+                         const std::set<const HashableDimensionKey>& trueList,
+                         const std::set<const HashableDimensionKey>& falseList) {
+    for (HashableDimensionKey key : trueList) {
+        if (!tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) {
+            return false;
+        }
+    }
+    for (HashableDimensionKey key : falseList) {
+        if (tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// Calls tracker.detectAndDeclareAnomaly on each key in the bucket.
+void detectAndDeclareAnomalies(AnomalyTracker& tracker,
+                               const int64_t& bucketNum,
+                               const std::shared_ptr<DimToValMap>& bucket,
+                               const int64_t& eventTimestamp) {
+    for (const auto& kv : *bucket) {
+        tracker.detectAndDeclareAnomaly(eventTimestamp, bucketNum, kv.first, kv.second);
+    }
+}
+
+// Asserts that the refractory time for each key in timestamps is the corresponding
+// timestamp (in ns) + refractoryPeriodSec.
+// If a timestamp value is negative, instead asserts that the refractory period is inapplicable
+// (either non-existant or already past).
+void checkRefractoryTimes(AnomalyTracker& tracker,
+                          const int64_t& currTimestampNs,
+                          const int32_t& refractoryPeriodSec,
+                          const std::unordered_map<HashableDimensionKey, int64_t>& timestamps) {
+    for (const auto& kv : timestamps) {
+        if (kv.second < 0) {
+            // Make sure that, if there is a refractory period, it is already past.
+            EXPECT_LT(tracker.getRefractoryPeriodEndsSec(kv.first),
+                    currTimestampNs / NS_PER_SEC + 1)
+                    << "Failure was at currTimestampNs " << currTimestampNs;
+        } else {
+            EXPECT_EQ(tracker.getRefractoryPeriodEndsSec(kv.first),
+                      kv.second / NS_PER_SEC + refractoryPeriodSec)
+                      << "Failure was at currTimestampNs " << currTimestampNs;
+        }
+    }
+}
+
 TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
     const int64_t bucketSizeNs = 30 * NS_PER_SEC;
+    const int32_t refractoryPeriodSec = 2 * bucketSizeNs / NS_PER_SEC;
     Alert alert;
-    alert.set_number_of_buckets(3);
-    alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
+    alert.set_num_buckets(3);
+    alert.set_refractory_period_secs(refractoryPeriodSec);
     alert.set_trigger_if_sum_gt(2);
 
     AnomalyTracker anomalyTracker(alert, kConfigKey);
@@ -70,26 +128,31 @@
     HashableDimensionKey keyB = getMockDimensionKey(1, "b");
     HashableDimensionKey keyC = getMockDimensionKey(1, "c");
 
-    std::shared_ptr<DimToValMap> bucket0 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
-    int64_t eventTimestamp0 = 10;
-    std::shared_ptr<DimToValMap> bucket1 = MockBucket({{keyA, 1}});
-    int64_t eventTimestamp1 = bucketSizeNs + 11;
-    std::shared_ptr<DimToValMap> bucket2 = MockBucket({{keyB, 1}});
-    int64_t eventTimestamp2 = 2 * bucketSizeNs + 12;
-    std::shared_ptr<DimToValMap> bucket3 = MockBucket({{keyA, 2}});
-    int64_t eventTimestamp3 = 3 * bucketSizeNs + 13;
-    std::shared_ptr<DimToValMap> bucket4 = MockBucket({{keyB, 1}});
-    int64_t eventTimestamp4 = 4 * bucketSizeNs + 14;
-    std::shared_ptr<DimToValMap> bucket5 = MockBucket({{keyA, 2}});
-    int64_t eventTimestamp5 = 5 * bucketSizeNs + 15;
-    std::shared_ptr<DimToValMap> bucket6 = MockBucket({{keyA, 2}});
-    int64_t eventTimestamp6 = 6 * bucketSizeNs + 16;
+    int64_t eventTimestamp0 = 10 * NS_PER_SEC;
+    int64_t eventTimestamp1 = bucketSizeNs + 11 * NS_PER_SEC;
+    int64_t eventTimestamp2 = 2 * bucketSizeNs + 12 * NS_PER_SEC;
+    int64_t eventTimestamp3 = 3 * bucketSizeNs + 13 * NS_PER_SEC;
+    int64_t eventTimestamp4 = 4 * bucketSizeNs + 14 * NS_PER_SEC;
+    int64_t eventTimestamp5 = 5 * bucketSizeNs + 5 * NS_PER_SEC;
+    int64_t eventTimestamp6 = 6 * bucketSizeNs + 16 * NS_PER_SEC;
 
+    std::shared_ptr<DimToValMap> bucket0 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
+    std::shared_ptr<DimToValMap> bucket1 = MockBucket({{keyA, 1}});
+    std::shared_ptr<DimToValMap> bucket2 = MockBucket({{keyB, 1}});
+    std::shared_ptr<DimToValMap> bucket3 = MockBucket({{keyA, 2}});
+    std::shared_ptr<DimToValMap> bucket4 = MockBucket({{keyB, 5}});
+    std::shared_ptr<DimToValMap> bucket5 = MockBucket({{keyA, 2}});
+    std::shared_ptr<DimToValMap> bucket6 = MockBucket({{keyA, 2}});
+
+    // Start time with no events.
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
-    EXPECT_FALSE(anomalyTracker.detectAnomaly(0, *bucket0));
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp0, 0, *bucket0);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
+
+    // Event from bucket #0 occurs.
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 0, bucket0, {}, {keyA, keyB, keyC}));
+    detectAndDeclareAnomalies(anomalyTracker, 0, bucket0, eventTimestamp1);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp0, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, -1}, {keyC, -1}});
 
     // Adds past bucket #0
     anomalyTracker.addPastBucket(bucket0, 0);
@@ -98,9 +161,12 @@
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
-    EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
+
+    // Event from bucket #1 occurs.
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 1, bucket1, {}, {keyA, keyB, keyC}));
+    detectAndDeclareAnomalies(anomalyTracker, 1, bucket1, eventTimestamp1);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, -1}, {keyC, -1}});
 
     // Adds past bucket #0 again. The sum does not change.
     anomalyTracker.addPastBucket(bucket0, 0);
@@ -109,9 +175,10 @@
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
-    EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 1, bucket1, {}, {keyA, keyB, keyC}));
+    detectAndDeclareAnomalies(anomalyTracker, 1, bucket1, eventTimestamp1 + 1);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, -1}, {keyC, -1}});
 
     // Adds past bucket #1.
     anomalyTracker.addPastBucket(bucket1, 1);
@@ -120,9 +187,12 @@
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
-    EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+
+    // Event from bucket #2 occurs. New anomaly on keyB.
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 2, bucket2, {keyB}, {keyA, keyC}));
+    detectAndDeclareAnomalies(anomalyTracker, 2, bucket2, eventTimestamp2);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}});
 
     // Adds past bucket #1 again. Nothing changes.
     anomalyTracker.addPastBucket(bucket1, 1);
@@ -131,9 +201,11 @@
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
-    EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+    // Event from bucket #2 occurs (again).
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 2, bucket2, {keyB}, {keyA, keyC}));
+    detectAndDeclareAnomalies(anomalyTracker, 2, bucket2, eventTimestamp2 + 1);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}});
 
     // Adds past bucket #2.
     anomalyTracker.addPastBucket(bucket2, 2);
@@ -141,10 +213,12 @@
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
-    EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3));
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3);
-    // Within refractory period.
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+
+    // Event from bucket #3 occurs. New anomaly on keyA.
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 3, bucket3, {keyA}, {keyB, keyC}));
+    detectAndDeclareAnomalies(anomalyTracker, 3, bucket3, eventTimestamp3);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp3, refractoryPeriodSec,
+            {{keyA, eventTimestamp3}, {keyB, eventTimestamp2}, {keyC, -1}});
 
     // Adds bucket #3.
     anomalyTracker.addPastBucket(bucket3, 3L);
@@ -152,37 +226,46 @@
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
-    EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4));
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+
+    // Event from bucket #4 occurs. New anomaly on keyB.
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 4, bucket4, {keyB}, {keyA, keyC}));
+    detectAndDeclareAnomalies(anomalyTracker, 4, bucket4, eventTimestamp4);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp4, refractoryPeriodSec,
+            {{keyA, eventTimestamp3}, {keyB, eventTimestamp4}, {keyC, -1}});
 
     // Adds bucket #4.
     anomalyTracker.addPastBucket(bucket4, 4);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
-    EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5));
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL);
+
+    // Event from bucket #5 occurs. New anomaly on keyA, which is still in refractory.
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 5, bucket5, {keyA, keyB}, {keyC}));
+    detectAndDeclareAnomalies(anomalyTracker, 5, bucket5, eventTimestamp5);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp5, refractoryPeriodSec,
+            {{keyA, eventTimestamp3}, {keyB, eventTimestamp4}, {keyC, -1}});
 
     // Adds bucket #5.
     anomalyTracker.addPastBucket(bucket5, 5);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
-    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
-    EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6));
-    // Within refractory period.
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL);
+
+    // Event from bucket #6 occurs. New anomaly on keyA, which is now out of refractory.
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 6, bucket6, {keyA, keyB}, {keyC}));
+    detectAndDeclareAnomalies(anomalyTracker, 6, bucket6, eventTimestamp6);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec,
+            {{keyA, eventTimestamp6}, {keyB, eventTimestamp4}, {keyC, -1}});
 }
 
 TEST(AnomalyTrackerTest, TestSparseBuckets) {
     const int64_t bucketSizeNs = 30 * NS_PER_SEC;
+    const int32_t refractoryPeriodSec = 2 * bucketSizeNs / NS_PER_SEC;
     Alert alert;
-    alert.set_number_of_buckets(3);
-    alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
+    alert.set_num_buckets(3);
+    alert.set_refractory_period_secs(refractoryPeriodSec);
     alert.set_trigger_if_sum_gt(2);
 
     AnomalyTracker anomalyTracker(alert, kConfigKey);
@@ -208,9 +291,10 @@
 
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
-    EXPECT_FALSE(anomalyTracker.detectAnomaly(9, *bucket9));
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 9, *bucket9);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1);
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 9, bucket9, {}, {keyA, keyB, keyC, keyD}));
+    detectAndDeclareAnomalies(anomalyTracker, 9, bucket9, eventTimestamp1);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
 
     // Add past bucket #9
     anomalyTracker.addPastBucket(bucket9, 9);
@@ -219,25 +303,27 @@
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
-    EXPECT_TRUE(anomalyTracker.detectAnomaly(16, *bucket16));
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 16, bucket16, {keyB}, {keyA, keyC, keyD}));
+    // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 16, *bucket16);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+    detectAndDeclareAnomalies(anomalyTracker, 16, bucket16, eventTimestamp2);
+    // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
 
     // Add past bucket #16
     anomalyTracker.addPastBucket(bucket16, 16);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
-    EXPECT_TRUE(anomalyTracker.detectAnomaly(18, *bucket18));
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 18, bucket18, {keyB}, {keyA, keyC, keyD}));
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
     // Within refractory period.
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+    detectAndDeclareAnomalies(anomalyTracker, 18, bucket18, eventTimestamp3);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp3, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
 
@@ -247,13 +333,14 @@
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
-    EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD}));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+    detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp4, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
 
     // Add bucket #18 again. Nothing changes.
     anomalyTracker.addPastBucket(bucket18, 18);
@@ -261,13 +348,14 @@
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
-    EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD}));
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20);
+    detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4 + 1);
     // Within refractory period.
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp4 + 1, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
 
     // Add past bucket #20
     anomalyTracker.addPastBucket(bucket20, 20);
@@ -275,32 +363,37 @@
     EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 3LL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
-    EXPECT_FALSE(anomalyTracker.detectAnomaly(25, *bucket25));
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 25, bucket25, {}, {keyA, keyB, keyC, keyD}));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 25, *bucket25);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+    // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    detectAndDeclareAnomalies(anomalyTracker, 25, bucket25, eventTimestamp5);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp5, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
 
     // Add past bucket #25
     anomalyTracker.addPastBucket(bucket25, 25);
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
     EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyD), 1LL);
-    EXPECT_FALSE(anomalyTracker.detectAnomaly(28, *bucket28));
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {},
+            {keyA, keyB, keyC, keyD, keyE}));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 28, *bucket28);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+    // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6);
+    // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
 
     // Updates current bucket #28.
     (*bucket28)[keyE] = 5;
-    EXPECT_TRUE(anomalyTracker.detectAnomaly(28, *bucket28));
+    EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {keyE},
+            {keyA, keyB, keyC, keyD}));
     EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
-    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6 + 7, 28, *bucket28);
-    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
-    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp6 + 7);
+    // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6 + 7);
+    // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec,
+            {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, eventTimestamp6 + 7}});
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index eb0fafe..819f2be 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -11,12 +11,15 @@
 // WITHOUT 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/condition/SimpleConditionTracker.h"
+#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
 #include <vector>
+#include <numeric>
 
 using std::map;
 using std::unordered_map;
@@ -28,17 +31,24 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
+
+const int ATTRIBUTION_NODE_FIELD_ID = 1;
+const int ATTRIBUTION_UID_FIELD_ID = 1;
+const int TAG_ID = 1;
 
 SimplePredicate getWakeLockHeldCondition(bool countNesting, bool defaultFalse,
-                                         bool outputSlicedUid) {
+                                         bool outputSlicedUid, Position position) {
     SimplePredicate simplePredicate;
-    simplePredicate.set_start("WAKE_LOCK_ACQUIRE");
-    simplePredicate.set_stop("WAKE_LOCK_RELEASE");
-    simplePredicate.set_stop_all("RELEASE_ALL");
+    simplePredicate.set_start(StringToId("WAKE_LOCK_ACQUIRE"));
+    simplePredicate.set_stop(StringToId("WAKE_LOCK_RELEASE"));
+    simplePredicate.set_stop_all(StringToId("RELEASE_ALL"));
     if (outputSlicedUid) {
-        KeyMatcher* keyMatcher = simplePredicate.add_dimension();
-        keyMatcher->set_key(1);
+        simplePredicate.mutable_dimensions()->set_field(TAG_ID);
+        simplePredicate.mutable_dimensions()->add_child()->set_field(ATTRIBUTION_NODE_FIELD_ID);
+        simplePredicate.mutable_dimensions()->mutable_child(0)->set_position(position);
+        simplePredicate.mutable_dimensions()->mutable_child(0)->add_child()->set_field(
+            ATTRIBUTION_UID_FIELD_ID);
     }
 
     simplePredicate.set_count_nesting(countNesting);
@@ -47,38 +57,70 @@
     return simplePredicate;
 }
 
-void makeWakeLockEvent(LogEvent* event, int uid, const string& wl, int acquire) {
-    event->write(uid);  // uid
+void writeAttributionNodesToEvent(LogEvent* event, const std::vector<int> &uids) {
+    std::vector<AttributionNode> nodes;
+    for (size_t i = 0; i < uids.size(); ++i) {
+        AttributionNode node;
+        node.set_uid(uids[i]);
+        nodes.push_back(node);
+    }
+    event->write(nodes);  // attribution chain.
+}
+
+void makeWakeLockEvent(
+        LogEvent* event, const std::vector<int> &uids, const string& wl, int acquire) {
+    writeAttributionNodesToEvent(event, uids);
     event->write(wl);
     event->write(acquire);
     event->init();
 }
 
-map<string, HashableDimensionKey> getWakeLockQueryKey(int key, int uid,
-                                                      const string& conditionName) {
-    // test query
-    KeyValuePair kv1;
-    kv1.set_key(key);
-    kv1.set_value_int(uid);
-    vector<KeyValuePair> kv_list;
-    kv_list.push_back(kv1);
-    map<string, HashableDimensionKey> queryKey;
-    queryKey[conditionName] = HashableDimensionKey(kv_list);
-    return queryKey;
+std::map<int64_t, std::vector<HashableDimensionKey>> getWakeLockQueryKey(
+    const Position position,
+    const std::vector<int> &uids, const string& conditionName) {
+    std::map<int64_t, std::vector<HashableDimensionKey>>  outputKeyMap;
+    std::vector<int> uid_indexes;
+    switch(position) {
+        case Position::FIRST:
+            uid_indexes.push_back(0);
+            break;
+        case Position::LAST:
+            uid_indexes.push_back(uids.size() - 1);
+            break;
+        case Position::ANY:
+            uid_indexes.resize(uids.size());
+            std::iota(uid_indexes.begin(), uid_indexes.end(), 0);
+            break;
+        default:
+            break;
+    }
+
+    for (const int idx : uid_indexes) {
+        DimensionsValue dimensionsValue;
+        dimensionsValue.set_field(TAG_ID);
+        dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field(
+            ATTRIBUTION_NODE_FIELD_ID);
+        dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)
+            ->mutable_value_tuple()->add_dimensions_value()->set_field(ATTRIBUTION_NODE_FIELD_ID);
+        dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)
+            ->mutable_value_tuple()->mutable_dimensions_value(0)->set_value_int(uids[idx]);
+        outputKeyMap[StringToId(conditionName)].push_back(HashableDimensionKey(dimensionsValue));
+    }
+    return outputKeyMap;
 }
 
 TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) {
     SimplePredicate simplePredicate;
-    simplePredicate.set_start("SCREEN_TURNED_ON");
-    simplePredicate.set_stop("SCREEN_TURNED_OFF");
+    simplePredicate.set_start(StringToId("SCREEN_TURNED_ON"));
+    simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF"));
     simplePredicate.set_count_nesting(false);
     simplePredicate.set_initial_value(SimplePredicate_InitialValue_UNKNOWN);
 
-    unordered_map<string, int> trackerNameIndexMap;
-    trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
-    trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
+    unordered_map<int64_t, int> trackerNameIndexMap;
+    trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0;
+    trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1;
 
-    SimpleConditionTracker conditionTracker(kConfigKey, "SCREEN_IS_ON", 0 /*tracker index*/,
+    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), 0 /*tracker index*/,
                                             simplePredicate, trackerNameIndexMap);
 
     LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
@@ -152,15 +194,15 @@
 
 TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) {
     SimplePredicate simplePredicate;
-    simplePredicate.set_start("SCREEN_TURNED_ON");
-    simplePredicate.set_stop("SCREEN_TURNED_OFF");
+    simplePredicate.set_start(StringToId("SCREEN_TURNED_ON"));
+    simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF"));
     simplePredicate.set_count_nesting(true);
 
-    unordered_map<string, int> trackerNameIndexMap;
-    trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
-    trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
+    unordered_map<int64_t, int> trackerNameIndexMap;
+    trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0;
+    trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1;
 
-    SimpleConditionTracker conditionTracker(kConfigKey, "SCREEN_IS_ON",
+    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"),
                                             0 /*condition tracker index*/, simplePredicate,
                                             trackerNameIndexMap);
 
@@ -221,108 +263,133 @@
 }
 
 TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
-    SimplePredicate simplePredicate = getWakeLockHeldCondition(
-            true /*nesting*/, true /*default to false*/, true /*output slice by uid*/);
-    string conditionName = "WL_HELD_BY_UID2";
+    for (Position position :
+            { Position::ANY, Position::FIRST, Position::LAST}) {
+        SimplePredicate simplePredicate = getWakeLockHeldCondition(
+                true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
+                position);
+        string conditionName = "WL_HELD_BY_UID2";
 
-    unordered_map<string, int> trackerNameIndexMap;
-    trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
-    trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
-    trackerNameIndexMap["RELEASE_ALL"] = 2;
+        unordered_map<int64_t, int> trackerNameIndexMap;
+        trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0;
+        trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
+        trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
 
-    SimpleConditionTracker conditionTracker(kConfigKey, conditionName,
-                                            0 /*condition tracker index*/, simplePredicate,
-                                            trackerNameIndexMap);
-    int uid = 111;
+        SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
+                                                0 /*condition tracker index*/, simplePredicate,
+                                                trackerNameIndexMap);
+        std::vector<int> uids = {111, 222, 333};
 
-    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event, uid, "wl1", 1);
+        LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+        makeWakeLockEvent(&event, uids, "wl1", 1);
 
-    // one matched start
-    vector<MatchingState> matcherState;
-    matcherState.push_back(MatchingState::kMatched);
-    matcherState.push_back(MatchingState::kNotMatched);
-    matcherState.push_back(MatchingState::kNotMatched);
-    vector<sp<ConditionTracker>> allPredicates;
-    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-    vector<bool> changedCache(1, false);
+        // one matched start
+        vector<MatchingState> matcherState;
+        matcherState.push_back(MatchingState::kMatched);
+        matcherState.push_back(MatchingState::kNotMatched);
+        matcherState.push_back(MatchingState::kNotMatched);
+        vector<sp<ConditionTracker>> allPredicates;
+        vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+        vector<bool> changedCache(1, false);
 
-    conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
-                                       changedCache);
+        conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
+                                           changedCache);
 
-    EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
-    EXPECT_TRUE(changedCache[0]);
+        if (position == Position::FIRST ||
+            position == Position::LAST) {
+            EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+        } else {
+            EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+        }
+        EXPECT_TRUE(changedCache[0]);
 
-    // Now test query
-    const auto queryKey = getWakeLockQueryKey(1, uid, conditionName);
-    conditionCache[0] = ConditionState::kNotEvaluated;
+        // Now test query
+        const auto queryKey = getWakeLockQueryKey(position, uids, conditionName);
+        conditionCache[0] = ConditionState::kNotEvaluated;
 
-    conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
-    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
-    // another wake lock acquired by this uid
-    LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event2, uid, "wl2", 1);
-    matcherState.clear();
-    matcherState.push_back(MatchingState::kMatched);
-    matcherState.push_back(MatchingState::kNotMatched);
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    changedCache[0] = false;
-    conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
-                                       changedCache);
-    EXPECT_FALSE(changedCache[0]);
+        // another wake lock acquired by this uid
+        LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+        makeWakeLockEvent(&event2, uids, "wl2", 1);
+        matcherState.clear();
+        matcherState.push_back(MatchingState::kMatched);
+        matcherState.push_back(MatchingState::kNotMatched);
+        conditionCache[0] = ConditionState::kNotEvaluated;
+        changedCache[0] = false;
+        conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
+                                           changedCache);
+        EXPECT_FALSE(changedCache[0]);
+        if (position == Position::FIRST ||
+            position == Position::LAST) {
+            EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+        } else {
+            EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+        }
 
-    // wake lock 1 release
-    LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event3, uid, "wl1", 0);  // now release it.
-    matcherState.clear();
-    matcherState.push_back(MatchingState::kNotMatched);
-    matcherState.push_back(MatchingState::kMatched);
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    changedCache[0] = false;
-    conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
-                                       changedCache);
-    // nothing changes, because wake lock 2 is still held for this uid
-    EXPECT_FALSE(changedCache[0]);
+        // wake lock 1 release
+        LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
+        makeWakeLockEvent(&event3, uids, "wl1", 0);  // now release it.
+        matcherState.clear();
+        matcherState.push_back(MatchingState::kNotMatched);
+        matcherState.push_back(MatchingState::kMatched);
+        conditionCache[0] = ConditionState::kNotEvaluated;
+        changedCache[0] = false;
+        conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
+                                           changedCache);
+        // nothing changes, because wake lock 2 is still held for this uid
+        EXPECT_FALSE(changedCache[0]);
+        if (position == Position::FIRST ||
+            position == Position::LAST) {
+            EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+        } else {
+            EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+        }
 
-    LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event4, uid, "wl2", 0);  // now release it.
-    matcherState.clear();
-    matcherState.push_back(MatchingState::kNotMatched);
-    matcherState.push_back(MatchingState::kMatched);
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    changedCache[0] = false;
-    conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache,
-                                       changedCache);
-    EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
-    EXPECT_TRUE(changedCache[0]);
+        LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
+        makeWakeLockEvent(&event4, uids, "wl2", 0);  // now release it.
+        matcherState.clear();
+        matcherState.push_back(MatchingState::kNotMatched);
+        matcherState.push_back(MatchingState::kMatched);
+        conditionCache[0] = ConditionState::kNotEvaluated;
+        changedCache[0] = false;
+        conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache,
+                                           changedCache);
+        EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+        EXPECT_TRUE(changedCache[0]);
 
-    // query again
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
-    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+        // query again
+        conditionCache[0] = ConditionState::kNotEvaluated;
+        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+
+    }
+
 }
 
 TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
     SimplePredicate simplePredicate = getWakeLockHeldCondition(
-            true /*nesting*/, true /*default to false*/, false /*slice output by uid*/);
+            true /*nesting*/, true /*default to false*/, false /*slice output by uid*/,
+            Position::ANY /* position */);
     string conditionName = "WL_HELD";
 
-    unordered_map<string, int> trackerNameIndexMap;
-    trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
-    trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
-    trackerNameIndexMap["RELEASE_ALL"] = 2;
+    unordered_map<int64_t, int> trackerNameIndexMap;
+    trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0;
+    trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
+    trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
 
-    SimpleConditionTracker conditionTracker(kConfigKey, conditionName,
+    SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
                                             0 /*condition tracker index*/, simplePredicate,
                                             trackerNameIndexMap);
-    int uid1 = 111;
+
+    std::vector<int> uid_list1 = {111, 1111, 11111};
     string uid1_wl1 = "wl1_1";
-    int uid2 = 222;
+    std::vector<int> uid_list2 = {222, 2222, 22222};
     string uid2_wl1 = "wl2_1";
 
     LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event, uid1, uid1_wl1, 1);
+    makeWakeLockEvent(&event, uid_list1, uid1_wl1, 1);
 
     // one matched start for uid1
     vector<MatchingState> matcherState;
@@ -340,7 +407,7 @@
     EXPECT_TRUE(changedCache[0]);
 
     // Now test query
-    map<string, HashableDimensionKey> queryKey;
+    ConditionKey queryKey;
     conditionCache[0] = ConditionState::kNotEvaluated;
 
     conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
@@ -348,7 +415,7 @@
 
     // another wake lock acquired by this uid
     LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event2, uid2, uid2_wl1, 1);
+    makeWakeLockEvent(&event2, uid_list2, uid2_wl1, 1);
     matcherState.clear();
     matcherState.push_back(MatchingState::kMatched);
     matcherState.push_back(MatchingState::kNotMatched);
@@ -360,7 +427,7 @@
 
     // uid1 wake lock 1 release
     LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event3, uid1, uid1_wl1, 0);  // now release it.
+    makeWakeLockEvent(&event3, uid_list1, uid1_wl1, 0);  // now release it.
     matcherState.clear();
     matcherState.push_back(MatchingState::kNotMatched);
     matcherState.push_back(MatchingState::kMatched);
@@ -372,7 +439,7 @@
     EXPECT_FALSE(changedCache[0]);
 
     LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event4, uid2, uid2_wl1, 0);  // now release it.
+    makeWakeLockEvent(&event4, uid_list2, uid2_wl1, 0);  // now release it.
     matcherState.clear();
     matcherState.push_back(MatchingState::kNotMatched);
     matcherState.push_back(MatchingState::kMatched);
@@ -390,95 +457,111 @@
 }
 
 TEST(SimpleConditionTrackerTest, TestStopAll) {
-    SimplePredicate simplePredicate = getWakeLockHeldCondition(
-            true /*nesting*/, true /*default to false*/, true /*output slice by uid*/);
-    string conditionName = "WL_HELD_BY_UID3";
+    for (Position position :
+            {Position::ANY, Position::FIRST, Position::LAST}) {
+        SimplePredicate simplePredicate = getWakeLockHeldCondition(
+                true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
+                position);
+        string conditionName = "WL_HELD_BY_UID3";
 
-    unordered_map<string, int> trackerNameIndexMap;
-    trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
-    trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
-    trackerNameIndexMap["RELEASE_ALL"] = 2;
+        unordered_map<int64_t, int> trackerNameIndexMap;
+        trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0;
+        trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
+        trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
 
-    SimpleConditionTracker conditionTracker(kConfigKey, conditionName,
-                                            0 /*condition tracker index*/, simplePredicate,
-                                            trackerNameIndexMap);
-    int uid1 = 111;
-    int uid2 = 222;
+        SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
+                                                0 /*condition tracker index*/, simplePredicate,
+                                                trackerNameIndexMap);
 
-    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event, uid1, "wl1", 1);
+        std::vector<int> uid_list1 = {111, 1111, 11111};
+        std::vector<int> uid_list2 = {222, 2222, 22222};
 
-    // one matched start
-    vector<MatchingState> matcherState;
-    matcherState.push_back(MatchingState::kMatched);
-    matcherState.push_back(MatchingState::kNotMatched);
-    matcherState.push_back(MatchingState::kNotMatched);
-    vector<sp<ConditionTracker>> allPredicates;
-    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-    vector<bool> changedCache(1, false);
+        LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+        makeWakeLockEvent(&event, uid_list1, "wl1", 1);
 
-    conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
-                                       changedCache);
+        // one matched start
+        vector<MatchingState> matcherState;
+        matcherState.push_back(MatchingState::kMatched);
+        matcherState.push_back(MatchingState::kNotMatched);
+        matcherState.push_back(MatchingState::kNotMatched);
+        vector<sp<ConditionTracker>> allPredicates;
+        vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+        vector<bool> changedCache(1, false);
 
-    EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
-    EXPECT_TRUE(changedCache[0]);
+        conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
+                                           changedCache);
+        if (position == Position::FIRST ||
+            position == Position::LAST) {
+            EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+        } else {
+            EXPECT_EQ(uid_list1.size(), conditionTracker.mSlicedConditionState.size());
+        }
+        EXPECT_TRUE(changedCache[0]);
 
-    // Now test query
-    const auto queryKey = getWakeLockQueryKey(1, uid1, conditionName);
-    conditionCache[0] = ConditionState::kNotEvaluated;
+        // Now test query
+        const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName);
+        conditionCache[0] = ConditionState::kNotEvaluated;
 
-    conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
-    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
-    // another wake lock acquired by uid2
-    LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
-    makeWakeLockEvent(&event2, uid2, "wl2", 1);
-    matcherState.clear();
-    matcherState.push_back(MatchingState::kMatched);
-    matcherState.push_back(MatchingState::kNotMatched);
-    matcherState.push_back(MatchingState::kNotMatched);
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    changedCache[0] = false;
-    conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
-                                       changedCache);
-    EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size());
-    EXPECT_TRUE(changedCache[0]);
+        // another wake lock acquired by uid2
+        LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+        makeWakeLockEvent(&event2, uid_list2, "wl2", 1);
+        matcherState.clear();
+        matcherState.push_back(MatchingState::kMatched);
+        matcherState.push_back(MatchingState::kNotMatched);
+        matcherState.push_back(MatchingState::kNotMatched);
+        conditionCache[0] = ConditionState::kNotEvaluated;
+        changedCache[0] = false;
+        conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
+                                           changedCache);
+        if (position == Position::FIRST ||
+            position == Position::LAST) {
+            EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size());
+        } else {
+            EXPECT_EQ(uid_list1.size() + uid_list2.size(),
+                      conditionTracker.mSlicedConditionState.size());
+        }
+        EXPECT_TRUE(changedCache[0]);
 
-    // TEST QUERY
-    const auto queryKey2 = getWakeLockQueryKey(1, uid2, conditionName);
-    conditionCache[0] = ConditionState::kNotEvaluated;
+        // TEST QUERY
+        const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName);
+        conditionCache[0] = ConditionState::kNotEvaluated;
 
-    conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
-    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
 
-    // stop all event
-    LogEvent event3(2 /*tagId*/, 0 /*timestamp*/);
-    matcherState.clear();
-    matcherState.push_back(MatchingState::kNotMatched);
-    matcherState.push_back(MatchingState::kNotMatched);
-    matcherState.push_back(MatchingState::kMatched);
+        // stop all event
+        LogEvent event3(2 /*tagId*/, 0 /*timestamp*/);
+        matcherState.clear();
+        matcherState.push_back(MatchingState::kNotMatched);
+        matcherState.push_back(MatchingState::kNotMatched);
+        matcherState.push_back(MatchingState::kMatched);
 
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    changedCache[0] = false;
-    conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
-                                       changedCache);
-    EXPECT_TRUE(changedCache[0]);
-    EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+        conditionCache[0] = ConditionState::kNotEvaluated;
+        changedCache[0] = false;
+        conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
+                                           changedCache);
+        EXPECT_TRUE(changedCache[0]);
+        EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
 
-    // TEST QUERY
-    const auto queryKey3 = getWakeLockQueryKey(1, uid1, conditionName);
-    conditionCache[0] = ConditionState::kNotEvaluated;
+        // TEST QUERY
+        const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName);
+        conditionCache[0] = ConditionState::kNotEvaluated;
 
-    conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
-    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
 
-    // TEST QUERY
-    const auto queryKey4 = getWakeLockQueryKey(1, uid2, conditionName);
-    conditionCache[0] = ConditionState::kNotEvaluated;
+        // TEST QUERY
+        const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName);
+        conditionCache[0] = ConditionState::kNotEvaluated;
 
-    conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
-    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+    }
+
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
new file mode 100644
index 0000000..e28dd31
--- /dev/null
+++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
@@ -0,0 +1,181 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+
+StatsdConfig CreateStatsdConfig() {
+    StatsdConfig config;
+    auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
+    auto attributionNodeMatcher =
+        wakelockAcquireMatcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    attributionNodeMatcher->set_field(1);
+    attributionNodeMatcher->set_position(Position::ANY);
+    auto uidMatcher = attributionNodeMatcher->mutable_matches_tuple()->add_field_value_matcher();
+    uidMatcher->set_field(1);  // uid field.
+    uidMatcher->set_eq_string("com.android.gmscore");
+
+    *config.add_atom_matcher() = wakelockAcquireMatcher;
+
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(123456);
+    countMetric->set_what(wakelockAcquireMatcher.id());
+    *countMetric->mutable_dimensions() =
+        CreateAttributionUidAndTagDimensions(
+            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    countMetric->set_bucket(ONE_MINUTE);
+    return config;
+}
+
+}  // namespace
+
+TEST(AttributionE2eTest, TestAttributionMatchAndSlice) {
+    auto config = CreateStatsdConfig();
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs =
+        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+    // Here it assumes that GMS core has two uids.
+    processor->getUidMap()->updateApp(
+        android::String16("com.android.gmscore"), 222 /* uid */, 1 /* version code*/);
+    processor->getUidMap()->updateApp(
+        android::String16("com.android.gmscore"), 444 /* uid */, 1 /* version code*/);
+    processor->getUidMap()->updateApp(
+        android::String16("app1"), 111 /* uid */, 2 /* version code*/);
+    processor->getUidMap()->updateApp(
+        android::String16("APP3"), 333 /* uid */, 2 /* version code*/);
+
+    // GMS core node is in the middle.
+    std::vector<AttributionNode> attributions1 =
+        {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+         CreateAttribution(333, "App3")};
+
+    // GMS core node is the last one.
+    std::vector<AttributionNode> attributions2 =
+        {CreateAttribution(111, "App1"), CreateAttribution(333, "App3"),
+         CreateAttribution(222, "GMSCoreModule1")};
+
+    // GMS core node is the first one.
+    std::vector<AttributionNode> attributions3 =
+        {CreateAttribution(222, "GMSCoreModule1"), CreateAttribution(333, "App3")};
+
+    // Single GMS core node.
+    std::vector<AttributionNode> attributions4 =
+        {CreateAttribution(222, "GMSCoreModule1")};
+
+    // GMS core has another uid.
+    std::vector<AttributionNode> attributions5 =
+        {CreateAttribution(111, "App1"), CreateAttribution(444, "GMSCoreModule2"),
+         CreateAttribution(333, "App3")};
+
+    // Multiple GMS core nodes.
+    std::vector<AttributionNode> attributions6 =
+        {CreateAttribution(444, "GMSCoreModule2"), CreateAttribution(222, "GMSCoreModule1")};
+
+    // No GMS core nodes.
+    std::vector<AttributionNode> attributions7 =
+        {CreateAttribution(111, "App1"), CreateAttribution(333, "App3")};
+    std::vector<AttributionNode> attributions8 = {CreateAttribution(111, "App1")};
+
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    // Events 1~4 are in the 1st bucket.
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions1, "wl1", bucketStartTimeNs + 2));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions2, "wl1", bucketStartTimeNs + 200));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions3, "wl1", bucketStartTimeNs + bucketSizeNs - 1));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions4, "wl1", bucketStartTimeNs + bucketSizeNs));
+
+    // Events 5~8 are in the 3rd bucket.
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions5, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 1));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions6, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 100));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions7, "wl2", bucketStartTimeNs + 3 * bucketSizeNs - 1));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions8, "wl2", bucketStartTimeNs + 3 * bucketSizeNs));
+
+    sortLogEventsByTimestamp(&events);
+
+    for (const auto& event : events) {
+        processor->OnLogEvent(*event);
+    }
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs + 1, &reports);
+    EXPECT_EQ(reports.reports_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    EXPECT_EQ(countMetrics.data_size(), 3);
+
+    auto data = countMetrics.data(0);
+    ValidateAttributionUidAndTagDimension(
+        data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111, "App1");
+    EXPECT_EQ(data.bucket_info_size(), 2);
+    EXPECT_EQ(data.bucket_info(0).count(), 2);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).count(), 1);
+    EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
+
+    data = countMetrics.data(1);
+    ValidateAttributionUidAndTagDimension(
+        data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
+    EXPECT_EQ(data.bucket_info_size(), 2);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).count(), 1);
+    EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+
+    data = countMetrics.data(2);
+    ValidateAttributionUidAndTagDimension(
+        data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 444, "GMSCoreModule2");
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
new file mode 100644
index 0000000..a81bbb9
--- /dev/null
+++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
@@ -0,0 +1,233 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+namespace {
+
+StatsdConfig CreateStatsdConfig() {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+
+    *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
+    *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
+
+    auto appCrashMatcher = CreateProcessCrashAtomMatcher();
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+
+    auto isSyncingPredicate = CreateIsSyncingPredicate();
+    *isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions() =
+        CreateDimensions(
+            android::util::SYNC_STATE_CHANGED, {1 /* uid field */});
+
+    auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
+    *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
+        CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
+
+    *config.add_predicate() = screenIsOffPredicate;
+    *config.add_predicate() = isSyncingPredicate;
+    *config.add_predicate() = isInBackgroundPredicate;
+
+    auto combinationPredicate = config.add_predicate();
+    combinationPredicate->set_id(StringToId("combinationPredicate"));
+    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+    addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
+    addPredicateToPredicateCombination(isInBackgroundPredicate, combinationPredicate);
+
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(StringToId("AppCrashes"));
+    countMetric->set_what(appCrashMatcher.id());
+    countMetric->set_condition(combinationPredicate->id());
+    // The metric is dimensioning by uid only.
+    *countMetric->mutable_dimensions() =
+        CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1});
+    countMetric->set_bucket(ONE_MINUTE);
+
+    // Links between crash atom and condition of app is in syncing.
+    auto links = countMetric->add_links();
+    links->set_condition(isSyncingPredicate.id());
+    auto dimensionWhat = links->mutable_dimensions_in_what();
+    dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    dimensionWhat->add_child()->set_field(1);  // uid field.
+    auto dimensionCondition = links->mutable_dimensions_in_condition();
+    dimensionCondition->set_field(android::util::SYNC_STATE_CHANGED);
+    dimensionCondition->add_child()->set_field(1);  // uid field.
+
+    // Links between crash atom and condition of app is in background.
+    links = countMetric->add_links();
+    links->set_condition(isInBackgroundPredicate.id());
+    dimensionWhat = links->mutable_dimensions_in_what();
+    dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    dimensionWhat->add_child()->set_field(1);  // uid field.
+    dimensionCondition = links->mutable_dimensions_in_condition();
+    dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+    dimensionCondition->add_child()->set_field(1);  // uid field.
+    return config;
+}
+}  // namespace
+
+
+TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks) {
+    auto config = CreateStatsdConfig();
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t bucketSizeNs =
+        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+
+    ConfigKey cfgKey;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+    int appUid = 123;
+    auto crashEvent1 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 1);
+    auto crashEvent2 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 201);
+    auto crashEvent3= CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 101);
+
+    auto crashEvent4 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 51);
+    auto crashEvent5 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 299);
+    auto crashEvent6 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 2001);
+
+    auto crashEvent7 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 16);
+    auto crashEvent8 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 249);
+
+    auto crashEvent9 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 351);
+    auto crashEvent10 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 2);
+
+    auto screenTurnedOnEvent =
+        CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, bucketStartTimeNs + 2);
+    auto screenTurnedOffEvent =
+        CreateScreenStateChangedEvent(ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 200);
+    auto screenTurnedOnEvent2 =
+        CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON,
+                                      bucketStartTimeNs + 2 * bucketSizeNs - 100);
+
+    auto syncOnEvent1 =
+        CreateSyncStartEvent(appUid, "ReadEmail", bucketStartTimeNs + 50);
+    auto syncOffEvent1 =
+        CreateSyncEndEvent(appUid, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300);
+    auto syncOnEvent2 =
+        CreateSyncStartEvent(appUid, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000);
+
+    auto moveToBackgroundEvent1 =
+        CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15);
+    auto moveToForegroundEvent1 =
+        CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 250);
+
+    auto moveToBackgroundEvent2 =
+        CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 350);
+    auto moveToForegroundEvent2 =
+        CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 1);
+
+    /*
+                    bucket #1                               bucket #2
+
+
+       |      |   |  |                      |   |          |        |   |   |     (crashEvents)
+    |-------------------------------------|-----------------------------------|---------
+
+             |                                           |                        (MoveToBkground)
+
+                                             |                               |    (MoveToForeground)
+
+                |                                                 |                (SyncIsOn)
+                                                  |                                (SyncIsOff)
+          |                                                               |        (ScreenIsOn)
+                   |                                                               (ScreenIsOff)
+    */
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(std::move(crashEvent1));
+    events.push_back(std::move(crashEvent2));
+    events.push_back(std::move(crashEvent3));
+    events.push_back(std::move(crashEvent4));
+    events.push_back(std::move(crashEvent5));
+    events.push_back(std::move(crashEvent6));
+    events.push_back(std::move(crashEvent7));
+    events.push_back(std::move(crashEvent8));
+    events.push_back(std::move(crashEvent9));
+    events.push_back(std::move(crashEvent10));
+    events.push_back(std::move(screenTurnedOnEvent));
+    events.push_back(std::move(screenTurnedOffEvent));
+    events.push_back(std::move(screenTurnedOnEvent2));
+    events.push_back(std::move(syncOnEvent1));
+    events.push_back(std::move(syncOffEvent1));
+    events.push_back(std::move(syncOnEvent2));
+    events.push_back(std::move(moveToBackgroundEvent1));
+    events.push_back(std::move(moveToForegroundEvent1));
+    events.push_back(std::move(moveToBackgroundEvent2));
+    events.push_back(std::move(moveToForegroundEvent2));
+
+    sortLogEventsByTimestamp(&events);
+
+    for (const auto& event : events) {
+        processor->OnLogEvent(*event);
+    }
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports);
+    EXPECT_EQ(reports.reports_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1);
+    auto data = reports.reports(0).metrics(0).count_metrics().data(0);
+    // Validate dimension value.
+    EXPECT_EQ(data.dimension().field(),
+              android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+    // Uid field.
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid);
+
+    reports.Clear();
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+    EXPECT_EQ(reports.reports_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 2);
+    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1);
+    EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(1).count(), 3);
+    data = reports.reports(0).metrics(0).count_metrics().data(0);
+    // Validate dimension value.
+    EXPECT_EQ(data.dimension().field(),
+              android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+    // Uid field.
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid);
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
new file mode 100644
index 0000000..47e8a72
--- /dev/null
+++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -0,0 +1,179 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+    *config.add_predicate() = screenIsOffPredicate;
+
+    auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+    // The predicate is dimensioning by any attribution node and both by uid and tag.
+    *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
+        CreateAttributionUidAndTagDimensions(
+            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST});
+    *config.add_predicate() = holdingWakelockPredicate;
+
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("WakelockDuration"));
+    durationMetric->set_what(holdingWakelockPredicate.id());
+    durationMetric->set_condition(screenIsOffPredicate.id());
+    durationMetric->set_aggregation_type(aggregationType);
+    // The metric is dimensioning by first attribution node and only by uid.
+    *durationMetric->mutable_dimensions() =
+        CreateAttributionUidDimensions(
+            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    durationMetric->set_bucket(ONE_MINUTE);
+    return config;
+}
+
+TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) {
+    ConfigKey cfgKey;
+    for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) {
+        auto config = CreateStatsdConfig(aggregationType);
+        uint64_t bucketStartTimeNs = 10000000000;
+        uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+        auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+        EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+        EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+        auto screenTurnedOnEvent =
+            CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, bucketStartTimeNs + 1);
+        auto screenTurnedOffEvent =
+            CreateScreenStateChangedEvent(ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 200);
+        auto screenTurnedOnEvent2 =
+            CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON,
+                                          bucketStartTimeNs + bucketSizeNs + 500);
+
+        std::vector<AttributionNode> attributions1 =
+            {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+             CreateAttribution(222, "GMSCoreModule2")};
+
+        std::vector<AttributionNode> attributions2 =
+            {CreateAttribution(111, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+             CreateAttribution(222, "GMSCoreModule2")};
+
+        auto acquireEvent1 = CreateAcquireWakelockEvent(
+            attributions1, "wl1", bucketStartTimeNs + 2);
+        auto acquireEvent2 = CreateAcquireWakelockEvent(
+            attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10);
+
+        auto releaseEvent1 = CreateReleaseWakelockEvent(
+            attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2);
+        auto releaseEvent2 = CreateReleaseWakelockEvent(
+            attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15);
+
+        std::vector<std::unique_ptr<LogEvent>> events;
+
+        events.push_back(std::move(screenTurnedOnEvent));
+        events.push_back(std::move(screenTurnedOffEvent));
+        events.push_back(std::move(screenTurnedOnEvent2));
+        events.push_back(std::move(acquireEvent1));
+        events.push_back(std::move(acquireEvent2));
+        events.push_back(std::move(releaseEvent1));
+        events.push_back(std::move(releaseEvent2));
+
+        sortLogEventsByTimestamp(&events);
+
+        for (const auto& event : events) {
+            processor->OnLogEvent(*event);
+        }
+
+        ConfigMetricsReportList reports;
+        processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports);
+        EXPECT_EQ(reports.reports_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+        // Only 1 dimension output. The tag dimension in the predicate has been aggregated.
+        EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+
+        auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+        // Validate dimension value.
+        EXPECT_EQ(data.dimension().field(),
+                  android::util::WAKELOCK_STATE_CHANGED);
+        EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+        // Attribution field.
+        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
+        // Uid only.
+        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+            .value_tuple().dimensions_value_size(), 1);
+        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+            .value_tuple().dimensions_value(0).field(), 1);
+        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+            .value_tuple().dimensions_value(0).value_int(), 111);
+        // Validate bucket info.
+        EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
+        data = reports.reports(0).metrics(0).duration_metrics().data(0);
+        // The wakelock holding interval starts from the screen off event and to the end of the 1st
+        // bucket.
+        EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200);
+
+        reports.Clear();
+        processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+        EXPECT_EQ(reports.reports_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+        // Dump the report after the end of 2nd bucket.
+        EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
+        data = reports.reports(0).metrics(0).duration_metrics().data(0);
+        // Validate dimension value.
+        EXPECT_EQ(data.dimension().field(),
+                  android::util::WAKELOCK_STATE_CHANGED);
+        EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+        // Attribution field.
+        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
+        // Uid only.
+        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+            .value_tuple().dimensions_value_size(), 1);
+        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+            .value_tuple().dimensions_value(0).field(), 1);
+        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+            .value_tuple().dimensions_value(0).value_int(), 111);
+        // Two output buckets.
+        // The wakelock holding interval in the 1st bucket starts from the screen off event and to
+        // the end of the 1st bucket.
+        EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(),
+            bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200));
+        // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and
+        // ends at the second screen on event.
+        EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL);
+    }
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
index 7658044..a134300 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -14,6 +14,7 @@
 
 #include "src/guardrail/StatsdStats.h"
 #include "statslog.h"
+#include "tests/statsd_test_util.h"
 
 #include <gtest/gtest.h>
 #include <vector>
@@ -28,8 +29,7 @@
 
 TEST(StatsdStatsTest, TestValidConfigAdd) {
     StatsdStats stats;
-    string name = "StatsdTest";
-    ConfigKey key(0, name);
+    ConfigKey key(0, 12345);
     const int metricsCount = 10;
     const int conditionsCount = 20;
     const int matchersCount = 30;
@@ -45,7 +45,7 @@
     EXPECT_EQ(1, report.config_stats_size());
     const auto& configReport = report.config_stats(0);
     EXPECT_EQ(0, configReport.uid());
-    EXPECT_EQ(name, configReport.name());
+    EXPECT_EQ(12345, configReport.id());
     EXPECT_EQ(metricsCount, configReport.metric_count());
     EXPECT_EQ(conditionsCount, configReport.condition_count());
     EXPECT_EQ(matchersCount, configReport.matcher_count());
@@ -56,8 +56,7 @@
 
 TEST(StatsdStatsTest, TestInvalidConfigAdd) {
     StatsdStats stats;
-    string name = "StatsdTest";
-    ConfigKey key(0, name);
+    ConfigKey key(0, 12345);
     const int metricsCount = 10;
     const int conditionsCount = 20;
     const int matchersCount = 30;
@@ -78,8 +77,7 @@
 
 TEST(StatsdStatsTest, TestConfigRemove) {
     StatsdStats stats;
-    string name = "StatsdTest";
-    ConfigKey key(0, name);
+    ConfigKey key(0, 12345);
     const int metricsCount = 10;
     const int conditionsCount = 20;
     const int matchersCount = 30;
@@ -105,22 +103,22 @@
 
 TEST(StatsdStatsTest, TestSubStats) {
     StatsdStats stats;
-    ConfigKey key(0, "test");
+    ConfigKey key(0, 12345);
     stats.noteConfigReceived(key, 2, 3, 4, 5, true);
 
-    stats.noteMatcherMatched(key, "matcher1");
-    stats.noteMatcherMatched(key, "matcher1");
-    stats.noteMatcherMatched(key, "matcher2");
+    stats.noteMatcherMatched(key, StringToId("matcher1"));
+    stats.noteMatcherMatched(key, StringToId("matcher1"));
+    stats.noteMatcherMatched(key, StringToId("matcher2"));
 
-    stats.noteConditionDimensionSize(key, "condition1", 250);
-    stats.noteConditionDimensionSize(key, "condition1", 240);
+    stats.noteConditionDimensionSize(key, StringToId("condition1"), 250);
+    stats.noteConditionDimensionSize(key, StringToId("condition1"), 240);
 
-    stats.noteMetricDimensionSize(key, "metric1", 201);
-    stats.noteMetricDimensionSize(key, "metric1", 202);
+    stats.noteMetricDimensionSize(key, StringToId("metric1"), 201);
+    stats.noteMetricDimensionSize(key, StringToId("metric1"), 202);
 
-    stats.noteAnomalyDeclared(key, "alert1");
-    stats.noteAnomalyDeclared(key, "alert1");
-    stats.noteAnomalyDeclared(key, "alert2");
+    stats.noteAnomalyDeclared(key, StringToId("alert1"));
+    stats.noteAnomalyDeclared(key, StringToId("alert1"));
+    stats.noteAnomalyDeclared(key, StringToId("alert2"));
 
     // broadcast-> 2
     stats.noteBroadcastSent(key);
@@ -147,39 +145,39 @@
 
     EXPECT_EQ(2, configReport.matcher_stats_size());
     // matcher1 is the first in the list
-    if (!configReport.matcher_stats(0).name().compare("matcher1")) {
+    if (configReport.matcher_stats(0).id() == StringToId("matcher1")) {
         EXPECT_EQ(2, configReport.matcher_stats(0).matched_times());
         EXPECT_EQ(1, configReport.matcher_stats(1).matched_times());
-        EXPECT_EQ("matcher2", configReport.matcher_stats(1).name());
+        EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(1).id());
     } else {
         // matcher1 is the second in the list.
         EXPECT_EQ(1, configReport.matcher_stats(0).matched_times());
-        EXPECT_EQ("matcher2", configReport.matcher_stats(0).name());
+        EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(0).id());
 
         EXPECT_EQ(2, configReport.matcher_stats(1).matched_times());
-        EXPECT_EQ("matcher1", configReport.matcher_stats(1).name());
+        EXPECT_EQ(StringToId("matcher1"), configReport.matcher_stats(1).id());
     }
 
     EXPECT_EQ(2, configReport.alert_stats_size());
-    bool alert1first = !configReport.alert_stats(0).name().compare("alert1");
-    EXPECT_EQ("alert1", configReport.alert_stats(alert1first ? 0 : 1).name());
+    bool alert1first = configReport.alert_stats(0).id() == StringToId("alert1");
+    EXPECT_EQ(StringToId("alert1"), configReport.alert_stats(alert1first ? 0 : 1).id());
     EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).alerted_times());
-    EXPECT_EQ("alert2", configReport.alert_stats(alert1first ? 1 : 0).name());
+    EXPECT_EQ(StringToId("alert2"), configReport.alert_stats(alert1first ? 1 : 0).id());
     EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).alerted_times());
 
     EXPECT_EQ(1, configReport.condition_stats_size());
-    EXPECT_EQ("condition1", configReport.condition_stats(0).name());
+    EXPECT_EQ(StringToId("condition1"), configReport.condition_stats(0).id());
     EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts());
 
     EXPECT_EQ(1, configReport.metric_stats_size());
-    EXPECT_EQ("metric1", configReport.metric_stats(0).name());
+    EXPECT_EQ(StringToId("metric1"), configReport.metric_stats(0).id());
     EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts());
 
     // after resetting the stats, some new events come
-    stats.noteMatcherMatched(key, "matcher99");
-    stats.noteConditionDimensionSize(key, "condition99", 300);
-    stats.noteMetricDimensionSize(key, "metric99", 270);
-    stats.noteAnomalyDeclared(key, "alert99");
+    stats.noteMatcherMatched(key, StringToId("matcher99"));
+    stats.noteConditionDimensionSize(key, StringToId("condition99"), 300);
+    stats.noteMetricDimensionSize(key, StringToId("metric99tion99"), 270);
+    stats.noteAnomalyDeclared(key, StringToId("alert99"));
 
     // now the config stats should only contain the stats about the new event.
     stats.dumpStats(&output, false);
@@ -188,19 +186,19 @@
     EXPECT_EQ(1, report.config_stats_size());
     const auto& configReport2 = report.config_stats(0);
     EXPECT_EQ(1, configReport2.matcher_stats_size());
-    EXPECT_EQ("matcher99", configReport2.matcher_stats(0).name());
+    EXPECT_EQ(StringToId("matcher99"), configReport2.matcher_stats(0).id());
     EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times());
 
     EXPECT_EQ(1, configReport2.condition_stats_size());
-    EXPECT_EQ("condition99", configReport2.condition_stats(0).name());
+    EXPECT_EQ(StringToId("condition99"), configReport2.condition_stats(0).id());
     EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts());
 
     EXPECT_EQ(1, configReport2.metric_stats_size());
-    EXPECT_EQ("metric99", configReport2.metric_stats(0).name());
+    EXPECT_EQ(StringToId("metric99tion99"), configReport2.metric_stats(0).id());
     EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts());
 
     EXPECT_EQ(1, configReport2.alert_stats_size());
-    EXPECT_EQ("alert99", configReport2.alert_stats(0).name());
+    EXPECT_EQ(StringToId("alert99"), configReport2.alert_stats(0).id());
     EXPECT_EQ(1, configReport2.alert_stats(0).alerted_times());
 }
 
@@ -260,7 +258,7 @@
     for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
         timestamps.push_back(i);
     }
-    ConfigKey key(0, "test");
+    ConfigKey key(0, 12345);
     stats.noteConfigReceived(key, 2, 3, 4, 5, true);
 
     for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index eec94539..c391513 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -13,7 +13,10 @@
 // limitations under the License.
 
 #include "src/metrics/CountMetricProducer.h"
+#include "src/dimension.h"
+#include "src/stats_log_util.h"
 #include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -32,18 +35,18 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
 
 TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
     int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
     int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
     int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
     int tagId = 1;
 
     CountMetric metric;
-    metric.set_name("1");
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_id(1);
+    metric.set_bucket(ONE_MINUTE);
 
     LogEvent event1(tagId, bucketStartTimeNs + 1);
     LogEvent event2(tagId, bucketStartTimeNs + 2);
@@ -96,12 +99,12 @@
 
 TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) {
     int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
 
     CountMetric metric;
-    metric.set_name("1");
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.set_condition("SCREEN_ON");
+    metric.set_id(1);
+    metric.set_bucket(ONE_MINUTE);
+    metric.set_condition(StringToId("SCREEN_ON"));
 
     LogEvent event1(1, bucketStartTimeNs + 1);
     LogEvent event2(1, bucketStartTimeNs + 10);
@@ -135,28 +138,33 @@
 
 TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
     int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+
+    int tagId = 1;
+    int conditionTagId = 2;
 
     CountMetric metric;
-    metric.set_name("1");
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON");
+    metric.set_id(1);
+    metric.set_bucket(ONE_MINUTE);
+    metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON"));
     MetricConditionLink* link = metric.add_links();
-    link->set_condition("APP_IN_BACKGROUND_PER_UID");
-    link->add_key_in_what()->set_key(1);
-    link->add_key_in_condition()->set_key(2);
+    link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID"));
+    *link->mutable_dimensions_in_what() = buildSimpleAtomFieldMatcher(tagId, 1);
+    *link->mutable_dimensions_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2);
 
-    LogEvent event1(1, bucketStartTimeNs + 1);
+    LogEvent event1(tagId, bucketStartTimeNs + 1);
     event1.write("111");  // uid
     event1.init();
     ConditionKey key1;
-    key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111");
+    key1[StringToId("APP_IN_BACKGROUND_PER_UID")] =
+        {getMockedDimensionKey(conditionTagId, 2, "111")};
 
-    LogEvent event2(1, bucketStartTimeNs + 10);
+    LogEvent event2(tagId, bucketStartTimeNs + 10);
     event2.write("222");  // uid
     event2.init();
     ConditionKey key2;
-    key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222");
+    key2[StringToId("APP_IN_BACKGROUND_PER_UID")] =
+        {getMockedDimensionKey(conditionTagId, 2, "222")};
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
@@ -183,29 +191,28 @@
     EXPECT_EQ(1LL, bucketInfo.mCount);
 }
 
-TEST(CountMetricProducerTest, TestAnomalyDetection) {
+TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) {
     Alert alert;
-    alert.set_name("alert");
-    alert.set_metric_name("1");
+    alert.set_id(11);
+    alert.set_metric_id(1);
     alert.set_trigger_if_sum_gt(2);
-    alert.set_number_of_buckets(2);
-    alert.set_refractory_period_secs(1);
+    alert.set_num_buckets(2);
+    const int32_t refPeriodSec = 1;
+    alert.set_refractory_period_secs(refPeriodSec);
 
     int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs = 30 * NS_PER_SEC;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
     int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
     int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
-
     CountMetric metric;
-    metric.set_name("1");
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_id(1);
+    metric.set_bucket(ONE_MINUTE);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
                                       bucketStartTimeNs);
-    countProducer.addAnomalyTracker(anomalyTracker);
+    sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert);
 
     int tagId = 1;
     LogEvent event1(tagId, bucketStartTimeNs + 1);
@@ -214,7 +221,7 @@
     LogEvent event4(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1);
     LogEvent event5(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2);
     LogEvent event6(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3);
-    LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3 + NS_PER_SEC);
+    LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC);
 
     // Two events in bucket #0.
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
@@ -222,13 +229,13 @@
 
     EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
 
     // One event in bucket #2. No alarm as bucket #0 is trashed out.
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second);
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
 
     // Two events in bucket #3.
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
@@ -237,12 +244,14 @@
     EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
     // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event5.GetTimestampNs());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+            event5.GetTimestampNs() / NS_PER_SEC + refPeriodSec);
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7);
     EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second);
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event7.GetTimestampNs());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+            event7.GetTimestampNs() / NS_PER_SEC + refPeriodSec);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index 58a4ac6..a59f1fe 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/metrics/DurationMetricProducer.h"
+#include "src/stats_log_util.h"
 #include "metrics_test_helper.h"
 #include "src/condition/ConditionWizard.h"
 
@@ -36,25 +37,26 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
 
 TEST(DurationMetricTrackerTest, TestNoCondition) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     uint64_t bucketStartTimeNs = 10000000000;
-    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
 
     DurationMetric metric;
-    metric.set_name("1");
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_id(1);
+    metric.set_bucket(ONE_MINUTE);
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
 
     int tagId = 1;
     LogEvent event1(tagId, bucketStartTimeNs + 1);
     LogEvent event2(tagId, bucketStartTimeNs + bucketSizeNs + 2);
 
+    FieldMatcher dimensions;
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, {}, bucketStartTimeNs);
+            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
 
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
@@ -75,11 +77,11 @@
 TEST(DurationMetricTrackerTest, TestNonSlicedCondition) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     uint64_t bucketStartTimeNs = 10000000000;
-    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
 
     DurationMetric metric;
-    metric.set_name("1");
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_id(1);
+    metric.set_bucket(ONE_MINUTE);
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
 
     int tagId = 1;
@@ -88,9 +90,10 @@
     LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 1);
     LogEvent event4(tagId, bucketStartTimeNs + bucketSizeNs + 3);
 
+    FieldMatcher dimensions;
     DurationMetricProducer durationProducer(
             kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */,
-            3 /* stop_all index */, false /*nesting*/, wizard, {}, bucketStartTimeNs);
+            3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
     EXPECT_FALSE(durationProducer.mCondition);
     EXPECT_FALSE(durationProducer.isConditionSliced());
 
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index baaac67..7171de9 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -13,7 +13,9 @@
 // limitations under the License.
 
 #include "src/metrics/EventMetricProducer.h"
+#include "src/dimension.h"
 #include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -32,7 +34,7 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
 
 TEST(EventMetricProducerTest, TestNoCondition) {
     uint64_t bucketStartTimeNs = 10000000000;
@@ -40,7 +42,7 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
     EventMetric metric;
-    metric.set_name("1");
+    metric.set_id(1);
 
     LogEvent event1(1 /*tag id*/, bucketStartTimeNs + 1);
     LogEvent event2(1 /*tag id*/, bucketStartTimeNs + 2);
@@ -63,8 +65,8 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
     EventMetric metric;
-    metric.set_name("1");
-    metric.set_condition("SCREEN_ON");
+    metric.set_id(1);
+    metric.set_condition(StringToId("SCREEN_ON"));
 
     LogEvent event1(1, bucketStartTimeNs + 1);
     LogEvent event2(1, bucketStartTimeNs + 10);
@@ -88,25 +90,28 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    EventMetric metric;
-    metric.set_name("1");
-    metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON");
-    MetricConditionLink* link = metric.add_links();
-    link->set_condition("APP_IN_BACKGROUND_PER_UID");
-    link->add_key_in_what()->set_key(1);
-    link->add_key_in_condition()->set_key(2);
+    int tagId = 1;
+    int conditionTagId = 2;
 
-    LogEvent event1(1, bucketStartTimeNs + 1);
-    event1.write("111");  // uid
+    EventMetric metric;
+    metric.set_id(1);
+    metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON"));
+    MetricConditionLink* link = metric.add_links();
+    link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID"));
+    *link->mutable_dimensions_in_what() = buildSimpleAtomFieldMatcher(tagId, 1);
+    *link->mutable_dimensions_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2);
+
+    LogEvent event1(tagId, bucketStartTimeNs + 1);
+    EXPECT_TRUE(event1.write("111"));
     event1.init();
     ConditionKey key1;
-    key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111");
+    key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "111")};
 
-    LogEvent event2(1, bucketStartTimeNs + 10);
-    event2.write("222");  // uid
+    LogEvent event2(tagId, bucketStartTimeNs + 10);
+    EXPECT_TRUE(event2.write("222"));
     event2.init();
     ConditionKey key2;
-    key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222");
+    key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")};
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 5204834..82772d8 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -13,8 +13,10 @@
 // limitations under the License.
 
 #include "src/metrics/GaugeMetricProducer.h"
+#include "src/stats_log_util.h"
 #include "logd/LogEvent.h"
 #include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -34,20 +36,24 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
 const int tagId = 1;
-const string metricName = "test_metric";
+const int64_t metricId = 123;
 const int64_t bucketStartTimeNs = 10000000000;
-const int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
 const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
 const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
 const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
 
 TEST(GaugeMetricProducerTest, TestNoCondition) {
     GaugeMetric metric;
-    metric.set_name(metricName);
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.mutable_gauge_fields()->add_field_num(2);
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    metric.mutable_gauge_fields_filter()->set_include_all(false);
+    auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields();
+    gaugeFieldMatcher->set_field(tagId);
+    gaugeFieldMatcher->add_child()->set_field(1);
+    gaugeFieldMatcher->add_child()->set_field(3);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
@@ -59,35 +65,46 @@
     EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
 
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      tagId, tagId, bucketStartTimeNs, pullerManager);
+                                      tagId, bucketStartTimeNs, pullerManager);
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
     shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(tagId);
+    event->write(10);
+    event->write("some value");
     event->write(11);
     event->init();
     allData.push_back(event);
 
     gaugeProducer.onDataPulled(allData);
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(11, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
+    auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin();
+    EXPECT_EQ(10, it->second.value_int());
+    it++;
+    EXPECT_EQ(11, it->second.value_int());
     EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
 
     allData.clear();
     std::shared_ptr<LogEvent> event2 =
             std::make_shared<LogEvent>(tagId, bucket3StartTimeNs + 10);
-    event2->write(tagId);
+    event2->write(24);
+    event2->write("some value");
     event2->write(25);
     event2->init();
     allData.push_back(event2);
     gaugeProducer.onDataPulled(allData);
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(25, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
+    it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin();
+    EXPECT_EQ(24, it->second.value_int());
+    it++;
+    EXPECT_EQ(25, it->second.value_int());
     // One dimension.
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size());
-    EXPECT_EQ(11L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+    it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin();
+    EXPECT_EQ(10L, it->second.value_int());
+    it++;
+    EXPECT_EQ(11L, it->second.value_int());
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
 
     gaugeProducer.flushIfNeededLocked(bucket4StartTimeNs);
@@ -95,16 +112,21 @@
     // One dimension.
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
     EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
-    EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+    it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin();
+    EXPECT_EQ(24L, it->second.value_int());
+    it++;
+    EXPECT_EQ(25L, it->second.value_int());
     EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
 }
 
 TEST(GaugeMetricProducerTest, TestWithCondition) {
     GaugeMetric metric;
-    metric.set_name(metricName);
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.mutable_gauge_fields()->add_field_num(2);
-    metric.set_condition("SCREEN_ON");
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields();
+    gaugeFieldMatcher->set_field(tagId);
+    gaugeFieldMatcher->add_child()->set_field(2);
+    metric.set_condition(StringToId("SCREEN_ON"));
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
@@ -116,40 +138,44 @@
             .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
                 data->clear();
                 shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-                event->write(tagId);
+                event->write("some value");
                 event->write(100);
                 event->init();
                 data->push_back(event);
                 return true;
             }));
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard, tagId, tagId,
+    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard, tagId,
                                       bucketStartTimeNs, pullerManager);
 
     gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8);
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(100, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
+    EXPECT_EQ(100,
+        gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
     EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
     shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(1);
+    event->write("some value");
     event->write(110);
     event->init();
     allData.push_back(event);
     gaugeProducer.onDataPulled(allData);
 
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(110, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
+    EXPECT_EQ(110,
+        gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
-    EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+    EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back()
+        .mGaugeFields->begin()->second.value_int());
 
     gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10);
     gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10);
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
     EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
-    EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+    EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back()
+        .mGaugeFields->begin()->second.value_int());
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
 }
 
@@ -162,61 +188,69 @@
     EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
 
     GaugeMetric metric;
-    metric.set_name(metricName);
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.mutable_gauge_fields()->add_field_num(2);
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields();
+    gaugeFieldMatcher->set_field(tagId);
+    gaugeFieldMatcher->add_child()->set_field(2);
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      tagId, tagId, bucketStartTimeNs, pullerManager);
+                                      tagId, bucketStartTimeNs, pullerManager);
 
     Alert alert;
-    alert.set_name("alert");
-    alert.set_metric_name(metricName);
+    alert.set_id(101);
+    alert.set_metric_id(metricId);
     alert.set_trigger_if_sum_gt(25);
-    alert.set_number_of_buckets(2);
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
-    gaugeProducer.addAnomalyTracker(anomalyTracker);
+    alert.set_num_buckets(2);
+    const int32_t refPeriodSec = 60;
+    alert.set_refractory_period_secs(refPeriodSec);
+    sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert);
 
-    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
-    event1->write(1);
+    int tagId = 1;
+    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
+    event1->write("some value");
     event1->write(13);
     event1->init();
 
     gaugeProducer.onDataPulled({event1});
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+    EXPECT_EQ(13L,
+        gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
 
     std::shared_ptr<LogEvent> event2 =
-            std::make_shared<LogEvent>(1, bucketStartTimeNs + bucketSizeNs + 10);
-    event2->write(1);
+            std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 20);
+    event2->write("some value");
     event2->write(15);
     event2->init();
 
     gaugeProducer.onDataPulled({event2});
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event2->GetTimestampNs());
+    EXPECT_EQ(15L,
+        gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+            event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
 
     std::shared_ptr<LogEvent> event3 =
-            std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
-    event3->write(1);
-    event3->write(24);
+            std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+    event3->write("some value");
+    event3->write(26);
     event3->init();
 
     gaugeProducer.onDataPulled({event3});
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(24L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
+    EXPECT_EQ(26L,
+        gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+            event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
 
     // The event4 does not have the gauge field. Thus the current bucket value is 0.
     std::shared_ptr<LogEvent> event4 =
-            std::make_shared<LogEvent>(1, bucketStartTimeNs + 3 * bucketSizeNs + 10);
-    event4->write(1);
+            std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10);
+    event4->write("some value");
     event4->init();
     gaugeProducer.onDataPulled({event4});
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    EXPECT_EQ(0, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
+    EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second->empty());
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 7dac0fb..0772b0d40 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -13,8 +13,9 @@
 // limitations under the License.
 
 #include "src/metrics/duration_helper/MaxDurationTracker.h"
-#include "metrics_test_helper.h"
 #include "src/condition/ConditionWizard.h"
+#include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -36,34 +37,35 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
 
-const HashableDimensionKey eventKey = getMockedDimensionKey(0, "1");
-const HashableDimensionKey conditionKey = getMockedDimensionKey(4, "1");
-const HashableDimensionKey key1 = getMockedDimensionKey(1, "1");
-const HashableDimensionKey key2 = getMockedDimensionKey(1, "2");
+const int TagId = 1;
+
+const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1");
+const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
+const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
+const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
 
 TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
-    ConditionKey conditionKey1;
-    conditionKey1["condition"] = conditionKey;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
-                               bucketSizeNs, {});
+    int64_t metricId = 1;
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketSizeNs, false, {});
 
-    tracker.noteStart(key1, true, bucketStartTimeNs, conditionKey1);
+    tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
     // Event starts again. This would not change anything as it already starts.
-    tracker.noteStart(key1, true, bucketStartTimeNs + 3, conditionKey1);
+    tracker.noteStart(key1, true, bucketStartTimeNs + 3, ConditionKey());
     // Stopped.
     tracker.noteStop(key1, bucketStartTimeNs + 10, false);
 
     // Another event starts in this bucket.
-    tracker.noteStart(key2, true, bucketStartTimeNs + 20, conditionKey1);
+    tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey());
     tracker.noteStop(key2, bucketStartTimeNs + 40, false /*stop all*/);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
@@ -76,19 +78,18 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
-    ConditionKey conditionKey1;
-    conditionKey1["condition"] = conditionKey;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
-                               bucketSizeNs, {});
+    int64_t metricId = 1;
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketSizeNs, false, {});
 
-    tracker.noteStart(key1, true, bucketStartTimeNs + 1, conditionKey1);
+    tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
 
     // Another event starts in this bucket.
-    tracker.noteStart(key2, true, bucketStartTimeNs + 20, conditionKey1);
+    tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey());
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, &buckets);
     tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
     EXPECT_TRUE(tracker.mInfos.empty());
@@ -107,21 +108,20 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
-    ConditionKey conditionKey1;
-    conditionKey1["condition"] = conditionKey;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
-                               bucketSizeNs, {});
+    int64_t metricId = 1;
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketSizeNs, false, {});
 
     // The event starts.
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, conditionKey1);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
 
     // Starts again. Does not change anything.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1,
-                      conditionKey1);
+                      ConditionKey());
 
     // The event stops at early 4th bucket.
     tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, &buckets);
@@ -138,18 +138,17 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
-    ConditionKey conditionKey1;
-    conditionKey1["condition"] = conditionKey;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs,
-                               bucketSizeNs, {});
+    int64_t metricId = 1;
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
+                               bucketSizeNs, false, {});
 
     // 2 starts
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, conditionKey1);
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 10, conditionKey1);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 10, ConditionKey());
     // one stop
     tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/);
 
@@ -174,8 +173,8 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey conditionKey1;
-    HashableDimensionKey eventKey = getMockedDimensionKey(2, "maps");
-    conditionKey1["APP_BACKGROUND"] = conditionKey;
+    HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 2, "maps");
+    conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
 
     EXPECT_CALL(*wizard, query(_, conditionKey1))  // #4
             .WillOnce(Return(ConditionState::kFalse));
@@ -187,8 +186,9 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     int64_t durationTimeNs = 2 * 1000;
 
-    MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false, bucketStartTimeNs,
-                               bucketSizeNs, {});
+    int64_t metricId = 1;
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               bucketSizeNs, true, {});
     EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
     tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
@@ -204,36 +204,38 @@
 }
 
 TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
+    int64_t metricId = 1;
     Alert alert;
-    alert.set_name("alert");
-    alert.set_metric_name("metric");
+    alert.set_id(101);
+    alert.set_metric_id(metricId);
     alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
-    alert.set_number_of_buckets(2);
-    alert.set_refractory_period_secs(1);
+    alert.set_num_buckets(2);
+    const int32_t refPeriodSec = 1;
+    alert.set_refractory_period_secs(refPeriodSec);
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    ConditionKey conditionKey1;
-    conditionKey1["APP_BACKGROUND"] = conditionKey;
+
     uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
-    MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs,
-                               bucketSizeNs, {anomalyTracker});
+    sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
+                               bucketSizeNs, false, {anomalyTracker});
 
-    tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
+    tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStop(key1, eventStartTimeNs + 10, false);
-    EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
     EXPECT_EQ(10LL, tracker.mDuration);
 
-    tracker.noteStart(key2, true, eventStartTimeNs + 20, conditionKey1);
+    tracker.noteStart(key2, true, eventStartTimeNs + 20, ConditionKey());
     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, &buckets);
     tracker.noteStop(key2, eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
     EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration);
-    EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs,
-              (long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC));
+
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey),
+              (eventStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC  + 3 + refPeriodSec);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 9ec302f..6b8893e 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -13,8 +13,9 @@
 // limitations under the License.
 
 #include "src/metrics/duration_helper/OringDurationTracker.h"
-#include "metrics_test_helper.h"
 #include "src/condition/ConditionWizard.h"
+#include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -34,19 +35,18 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, "test");
-const HashableDimensionKey eventKey = getMockedDimensionKey(0, "event");
+const ConfigKey kConfigKey(0, 12345);
+const int TagId = 1;
+const int64_t metricId = 123;
+const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event");
 
-const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(1, "maps");
-const HashableDimensionKey kEventKey1 = getMockedDimensionKey(2, "maps");
-const HashableDimensionKey kEventKey2 = getMockedDimensionKey(3, "maps");
+const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
+const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
 
 TEST(OringDurationTrackerTest, TestDurationOverlap) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    ConditionKey key1;
-    key1["APP_BACKGROUND"] = kConditionKey1;
-
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
@@ -54,12 +54,12 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
-                                 bucketStartTimeNs, bucketSizeNs, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketSizeNs, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, key1);  // overlapping wl
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
 
     tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
@@ -73,20 +73,17 @@
 TEST(OringDurationTrackerTest, TestDurationNested) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    ConditionKey key1;
-    key1["APP_BACKGROUND"] = kConditionKey1;
-
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
-                                 bucketSizeNs, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketSizeNs, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, key1);  // overlapping wl
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
 
     tracker.noteStop(kEventKey1, eventStartTimeNs + 2000, false);
     tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
@@ -100,20 +97,17 @@
 TEST(OringDurationTrackerTest, TestStopAll) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    ConditionKey key1;
-    key1["APP_BACKGROUND"] = kConditionKey1;
-
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
-                                 bucketSizeNs, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketSizeNs, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
-    tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, key1);  // overlapping wl
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
 
     tracker.noteStopAll(eventStartTimeNs + 2003);
 
@@ -126,9 +120,6 @@
 TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    ConditionKey key1;
-    key1["APP_BACKGROUND"] = kConditionKey1;
-
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
@@ -136,13 +127,13 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
-                                 bucketSizeNs, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketSizeNs, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, &buckets);
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, ConditionKey());
     EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
 
     EXPECT_EQ(2u, buckets[eventKey].size());
@@ -162,7 +153,7 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = kConditionKey1;
+    key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
 
     EXPECT_CALL(*wizard, query(_, key1))  // #4
             .WillOnce(Return(ConditionState::kFalse));
@@ -174,8 +165,8 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
-                                 bucketStartTimeNs, bucketSizeNs, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketSizeNs, true, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
 
@@ -193,7 +184,7 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = kConditionKey1;
+    key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
 
     EXPECT_CALL(*wizard, query(_, key1))
             .Times(2)
@@ -207,8 +198,8 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
-                                 bucketStartTimeNs, bucketSizeNs, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketSizeNs, true, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     // condition to false; record duration 5n
@@ -228,7 +219,7 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
-    key1["APP_BACKGROUND"] = kConditionKey1;
+    key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
 
     EXPECT_CALL(*wizard, query(_, key1))  // #4
             .WillOnce(Return(ConditionState::kFalse));
@@ -239,8 +230,8 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
-                                 bucketSizeNs, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketSizeNs, true, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -259,26 +250,25 @@
 
 TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
     Alert alert;
-    alert.set_name("alert");
-    alert.set_metric_name("1");
+    alert.set_id(101);
+    alert.set_metric_id(1);
     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
-    alert.set_number_of_buckets(2);
+    alert.set_num_buckets(2);
     alert.set_refractory_period_secs(1);
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    ConditionKey key1;
-    key1["APP_BACKGROUND"] = kConditionKey1;
+
     uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
-    OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
-                                 bucketSizeNs, {anomalyTracker});
+    sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketSizeNs, true, {anomalyTracker});
 
     // Nothing in the past bucket.
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
 
@@ -286,7 +276,7 @@
     EXPECT_EQ(0u, buckets[eventKey].size());
 
     uint64_t event1StartTimeNs = eventStartTimeNs + 10;
-    tracker.noteStart(kEventKey1, true, event1StartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, event1StartTimeNs, ConditionKey());
     // No past buckets. The anomaly will happen in bucket #0.
     EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs));
@@ -305,7 +295,7 @@
 
     // One past buckets. The anomaly will happen in bucket #1.
     uint64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
-    tracker.noteStart(kEventKey1, true, event2StartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey());
     EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration -
                           bucket1Duration),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
@@ -314,48 +304,111 @@
     // Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in
     // bucket #2.
     uint64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC;
-    tracker.noteStart(kEventKey1, true, event3StartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, event3StartTimeNs, ConditionKey());
     EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
 }
 
-TEST(OringDurationTrackerTest, TestAnomalyDetection) {
+TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
     Alert alert;
-    alert.set_name("alert");
-    alert.set_metric_name("1");
+    alert.set_id(101);
+    alert.set_metric_id(1);
     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
-    alert.set_number_of_buckets(2);
-    alert.set_refractory_period_secs(1);
+    alert.set_num_buckets(2);
+    const int32_t refPeriodSec = 45;
+    alert.set_refractory_period_secs(refPeriodSec);
 
     unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    ConditionKey key1;
-    key1["APP_BACKGROUND"] = kConditionKey1;
+
     uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
-    OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true /*nesting*/,
-                                 bucketStartTimeNs, bucketSizeNs, {anomalyTracker});
+    sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+                                 bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
 
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1);
-    tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 10, false);
-    EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
     EXPECT_TRUE(tracker.mStarted.empty());
     EXPECT_EQ(10LL, tracker.mDuration);
 
     EXPECT_EQ(0u, tracker.mStarted.size());
 
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs + 20, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 20, ConditionKey());
     EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
     EXPECT_EQ((long long)(51ULL * NS_PER_SEC),
               (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
+    // The alarm is set to fire at 51s, and when it does, an anomaly would be declared. However,
+    // because this is a unit test, the alarm won't actually fire at all. Since the alarm fails
+    // to fire in time, the anomaly is instead caught when noteStop is called, at around 71s.
     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, &buckets);
-    tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
+    tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
     EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs));
-    EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25),
-              anomalyTracker->mLastAlarmTimestampNs);
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey),
+              (eventStartTimeNs + 2 * bucketSizeNs + 25) / NS_PER_SEC + refPeriodSec);
+}
+
+TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
+    Alert alert;
+    alert.set_id(101);
+    alert.set_metric_id(1);
+    alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
+    alert.set_num_buckets(2);
+    const int32_t refPeriodSec = 45;
+    alert.set_refractory_period_secs(refPeriodSec);
+
+    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    ConditionKey conkey;
+    conkey[StringToId("APP_BACKGROUND")] = kConditionKey1;
+    uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+    uint64_t bucketSizeNs = 30 * NS_PER_SEC;
+
+    sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+                                 bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
+
+    tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
+    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    sp<const AnomalyAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
+    EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
+
+    tracker.noteStop(kEventKey1, 17 * NS_PER_SEC, false); // stop key1 (2 seconds later)
+    EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
+
+    tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again
+    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    alarm = anomalyTracker->mAlarms.begin()->second;
+    EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
+
+    tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2
+    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    alarm = anomalyTracker->mAlarms.begin()->second;
+    EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
+
+    tracker.noteStop(kEventKey1, 47 * NS_PER_SEC, false); // stop key1
+    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    alarm = anomalyTracker->mAlarms.begin()->second;
+    EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
+
+    // Now, at 60s, which is 38s after key1 started again, we have reached 40s of 'on' time.
+    std::unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> firedAlarms({alarm});
+    anomalyTracker->informAlarmsFired(62 * NS_PER_SEC, firedAlarms);
+    EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
+
+    tracker.noteStop(kEventKey2, 69 * NS_PER_SEC, false); // stop key2
+    EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 6f117d3..fff3dbf 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -13,7 +13,9 @@
 // limitations under the License.
 
 #include "src/metrics/ValueMetricProducer.h"
+#include "src/stats_log_util.h"
 #include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -34,11 +36,11 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
 const int tagId = 1;
-const string metricName = "test_metric";
+const int64_t metricId = 123;
 const int64_t bucketStartTimeNs = 10000000000;
-const int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
 const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
 const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
 const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
@@ -48,9 +50,10 @@
  */
 TEST(ValueMetricProducerTest, TestNonDimensionalEvents) {
     ValueMetric metric;
-    metric.set_name(metricName);
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.set_value_field(2);
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    metric.mutable_value_field()->set_field(tagId);
+    metric.mutable_value_field()->add_child()->set_field(2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     // TODO: pending refactor of StatsPullerManager
@@ -124,10 +127,11 @@
  */
 TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) {
     ValueMetric metric;
-    metric.set_name(metricName);
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.set_value_field(2);
-    metric.set_condition("SCREEN_ON");
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    metric.mutable_value_field()->set_field(tagId);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.set_condition(StringToId("SCREEN_ON"));
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     shared_ptr<MockStatsPullerManager> pullerManager =
@@ -200,9 +204,10 @@
 
 TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
     ValueMetric metric;
-    metric.set_name(metricName);
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.set_value_field(2);
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    metric.mutable_value_field()->set_field(tagId);
+    metric.mutable_value_field()->add_child()->set_field(2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     shared_ptr<MockStatsPullerManager> pullerManager =
@@ -240,22 +245,23 @@
 
 TEST(ValueMetricProducerTest, TestAnomalyDetection) {
     Alert alert;
-    alert.set_name("alert");
-    alert.set_metric_name(metricName);
+    alert.set_id(101);
+    alert.set_metric_id(metricId);
     alert.set_trigger_if_sum_gt(130);
-    alert.set_number_of_buckets(2);
-    alert.set_refractory_period_secs(3);
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
+    alert.set_num_buckets(2);
+    const int32_t refPeriodSec = 3;
+    alert.set_refractory_period_secs(refPeriodSec);
 
     ValueMetric metric;
-    metric.set_name(metricName);
-    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
-    metric.set_value_field(2);
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    metric.mutable_value_field()->set_field(tagId);
+    metric.mutable_value_field()->add_child()->set_field(2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
                                       -1 /*not pulled*/, bucketStartTimeNs);
-    valueProducer.addAnomalyTracker(anomalyTracker);
+    sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert);
 
 
     shared_ptr<LogEvent> event1
@@ -292,23 +298,28 @@
     // Two events in bucket #0.
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); // Value sum == 30 <= 130.
+    // Value sum == 30 <= 130.
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
 
     // One event in bucket #2. No alarm as bucket #0 is trashed out.
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); // Value sum == 130 <= 130.
+    // Value sum == 130 <= 130.
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
 
     // Three events in bucket #3.
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
     // Anomaly at event 4 since Value sum == 131 > 130!
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event4->GetTimestampNs());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+            event4->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5);
     // Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4.
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event4->GetTimestampNs());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+            event4->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
 
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6);
     // Anomaly at event 6 since Value sum == 160 > 130 and after refractory period.
-    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event6->GetTimestampNs());
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+            event6->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
index a0a854a..fc7245c 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.cpp
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
@@ -18,15 +18,12 @@
 namespace os {
 namespace statsd {
 
-HashableDimensionKey getMockedDimensionKey(int key, string value) {
-    KeyValuePair pair;
-    pair.set_key(key);
-    pair.set_value_str(value);
-
-    vector<KeyValuePair> pairs;
-    pairs.push_back(pair);
-
-    return HashableDimensionKey(pairs);
+HashableDimensionKey getMockedDimensionKey(int tagId, int key, string value) {
+    DimensionsValue dimensionsValue;
+    dimensionsValue.set_field(tagId);
+    dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field(key);
+    dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)->set_value_str(value);
+    return HashableDimensionKey(dimensionsValue);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index 7cb3329..23e86f9 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -28,7 +28,7 @@
     MOCK_METHOD2(
             query,
             ConditionState(const int conditionIndex,
-                           const std::map<std::string, HashableDimensionKey>& conditionParameters));
+                           const ConditionKey& conditionParameters));
 };
 
 class MockStatsPullerManager : public StatsPullerManager {
@@ -38,7 +38,7 @@
     MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data));
 };
 
-HashableDimensionKey getMockedDimensionKey(int key, std::string value);
+HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
new file mode 100644
index 0000000..f9ac6d6
--- /dev/null
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -0,0 +1,372 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 "statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name,
+                                                  WakelockStateChanged::State state) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(android::util::WAKELOCK_STATE_CHANGED);
+    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+    field_value_matcher->set_field(4);  // State field.
+    field_value_matcher->set_eq_int(state);
+    return atom_matcher;
+}
+
+AtomMatcher CreateAcquireWakelockAtomMatcher() {
+    return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE);
+}
+
+AtomMatcher CreateReleaseWakelockAtomMatcher() {
+    return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE);
+}
+
+AtomMatcher CreateScreenStateChangedAtomMatcher(
+    const string& name, ScreenStateChanged::State state) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(android::util::SCREEN_STATE_CHANGED);
+    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+    field_value_matcher->set_field(1);  // State field.
+    field_value_matcher->set_eq_int(state);
+    return atom_matcher;
+}
+
+AtomMatcher CreateScreenTurnedOnAtomMatcher() {
+    return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", ScreenStateChanged::STATE_ON);
+}
+
+AtomMatcher CreateScreenTurnedOffAtomMatcher() {
+    return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff", ScreenStateChanged::STATE_OFF);
+}
+
+AtomMatcher CreateSyncStateChangedAtomMatcher(
+    const string& name, SyncStateChanged::State state) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(android::util::SYNC_STATE_CHANGED);
+    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+    field_value_matcher->set_field(3);  // State field.
+    field_value_matcher->set_eq_int(state);
+    return atom_matcher;
+}
+
+AtomMatcher CreateSyncStartAtomMatcher() {
+    return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON);
+}
+
+AtomMatcher CreateSyncEndAtomMatcher() {
+    return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF);
+}
+
+AtomMatcher CreateActivityForegroundStateChangedAtomMatcher(
+    const string& name, ActivityForegroundStateChanged::Activity activity) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+    field_value_matcher->set_field(4);  // Activity field.
+    field_value_matcher->set_eq_int(activity);
+    return atom_matcher;
+}
+
+AtomMatcher CreateMoveToBackgroundAtomMatcher() {
+    return CreateActivityForegroundStateChangedAtomMatcher(
+        "MoveToBackground", ActivityForegroundStateChanged::MOVE_TO_BACKGROUND);
+}
+
+AtomMatcher CreateMoveToForegroundAtomMatcher() {
+    return CreateActivityForegroundStateChangedAtomMatcher(
+        "MoveToForeground", ActivityForegroundStateChanged::MOVE_TO_FOREGROUND);
+}
+
+AtomMatcher CreateProcessLifeCycleStateChangedAtomMatcher(
+    const string& name, ProcessLifeCycleStateChanged::Event event) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+    field_value_matcher->set_field(3);  // Process state field.
+    field_value_matcher->set_eq_int(event);
+    return atom_matcher;
+}
+
+AtomMatcher CreateProcessCrashAtomMatcher() {
+    return CreateProcessLifeCycleStateChangedAtomMatcher(
+        "ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED);
+}
+
+
+Predicate CreateScreenIsOnPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("ScreenIsOn"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff"));
+    return predicate;
+}
+
+Predicate CreateScreenIsOffPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("ScreenIsOff"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn"));
+    return predicate;
+}
+
+Predicate CreateHoldingWakelockPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("HoldingWakelock"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock"));
+    return predicate;
+}
+
+Predicate CreateIsSyncingPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("IsSyncing"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd"));
+    return predicate;
+}
+
+Predicate CreateIsInBackgroundPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("IsInBackground"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground"));
+    return predicate;
+}
+
+void addPredicateToPredicateCombination(const Predicate& predicate,
+                                        Predicate* combinationPredicate) {
+    combinationPredicate->mutable_combination()->add_predicate(predicate.id());
+}
+
+FieldMatcher CreateAttributionUidDimensions(const int atomId,
+                                            const std::vector<Position>& positions) {
+    FieldMatcher dimensions;
+    dimensions.set_field(atomId);
+    for (const auto position : positions) {
+        auto child = dimensions.add_child();
+        child->set_field(1);
+        child->set_position(position);
+        child->add_child()->set_field(1);
+    }
+    return dimensions;
+}
+
+FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
+                                                 const std::vector<Position>& positions) {
+    FieldMatcher dimensions;
+    dimensions.set_field(atomId);
+    for (const auto position : positions) {
+        auto child = dimensions.add_child();
+        child->set_field(1);
+        child->set_position(position);
+        child->add_child()->set_field(1);
+        child->add_child()->set_field(2);
+    }
+    return dimensions;
+}
+
+FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) {
+    FieldMatcher dimensions;
+    dimensions.set_field(atomId);
+    for (const int field : fields) {
+        dimensions.add_child()->set_field(field);
+    }
+    return dimensions;
+}
+
+std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
+    const ScreenStateChanged::State state, uint64_t timestampNs) {
+    auto event = std::make_unique<LogEvent>(android::util::SCREEN_STATE_CHANGED, timestampNs);
+    EXPECT_TRUE(event->write(state));
+    event->init();
+    return event;
+}
+
+std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(
+    const std::vector<AttributionNode>& attributions, const string& wakelockName,
+    const WakelockStateChanged::State state, uint64_t timestampNs) {
+    auto event = std::make_unique<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, timestampNs);
+    event->write(attributions);
+    event->write(WakelockStateChanged::PARTIAL);
+    event->write(wakelockName);
+    event->write(state);
+    event->init();
+    return event;
+}
+
+std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
+    const std::vector<AttributionNode>& attributions,
+    const string& wakelockName, uint64_t timestampNs) {
+    return CreateWakelockStateChangedEvent(
+        attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
+    const std::vector<AttributionNode>& attributions,
+    const string& wakelockName, uint64_t timestampNs) {
+    return CreateWakelockStateChangedEvent(
+        attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent(
+    const int uid, const ActivityForegroundStateChanged::Activity activity, uint64_t timestampNs) {
+    auto event = std::make_unique<LogEvent>(
+        android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs);
+    event->write(uid);
+    event->write("pkg_name");
+    event->write("class_name");
+    event->write(activity);
+    event->init();
+    return event;
+}
+
+std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) {
+    return CreateActivityForegroundStateChangedEvent(
+        uid, ActivityForegroundStateChanged::MOVE_TO_BACKGROUND, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) {
+    return CreateActivityForegroundStateChangedEvent(
+        uid, ActivityForegroundStateChanged::MOVE_TO_FOREGROUND, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(
+    const int uid, const string& name, const SyncStateChanged::State state, uint64_t timestampNs) {
+    auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs);
+    event->write(uid);
+    event->write(name);
+    event->write(state);
+    event->init();
+    return event;
+}
+
+std::unique_ptr<LogEvent> CreateSyncStartEvent(
+    const int uid, const string& name, uint64_t timestampNs){
+    return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::ON, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateSyncEndEvent(
+    const int uid, const string& name, uint64_t timestampNs) {
+    return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::OFF, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent(
+    const int uid, const ProcessLifeCycleStateChanged::Event event, uint64_t timestampNs) {
+    auto logEvent = std::make_unique<LogEvent>(
+        android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, timestampNs);
+    logEvent->write(uid);
+    logEvent->write("");
+    logEvent->write(event);
+    logEvent->init();
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateAppCrashEvent(const int uid, uint64_t timestampNs) {
+    return CreateProcessLifeCycleStateChangedEvent(
+        uid, ProcessLifeCycleStateChanged::PROCESS_CRASHED, timestampNs);
+}
+
+sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
+                                              const ConfigKey& key) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<AnomalyMonitor> anomalyMonitor = new AnomalyMonitor(10); // 10 seconds
+    sp<StatsLogProcessor> processor = new StatsLogProcessor(
+        uidMap, anomalyMonitor, timeBaseSec, [](const ConfigKey&){});
+    processor->OnConfigUpdated(key, config);
+    return processor;
+}
+
+AttributionNode CreateAttribution(const int& uid, const string& tag) {
+    AttributionNode attribution;
+    attribution.set_uid(uid);
+    attribution.set_tag(tag);
+    return attribution;
+}
+
+void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) {
+  std::sort(events->begin(), events->end(),
+            [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) {
+              return a->GetTimestampNs() < b->GetTimestampNs();
+            });
+}
+
+int64_t StringToId(const string& str) {
+    return static_cast<int64_t>(std::hash<std::string>()(str));
+}
+
+void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
+    EXPECT_EQ(value.field(), atomId);
+    EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
+    // Attribution field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
+    // Uid only.
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value(0).value_int(), uid);
+}
+
+void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) {
+    EXPECT_EQ(value.field(), atomId);
+    EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
+    // Attribution field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
+    // Uid only.
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value(0).value_int(), uid);
+}
+
+void ValidateAttributionUidAndTagDimension(
+    const DimensionsValue& value, int atomId, int uid, const std::string& tag) {
+    EXPECT_EQ(value.field(), atomId);
+    EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
+    // Attribution field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
+    // Uid only.
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value(0).value_int(), uid);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value(1).field(), 2);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0)
+        .value_tuple().dimensions_value(1).value_str(), tag);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
new file mode 100644
index 0000000..f1ce358
--- /dev/null
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -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.
+
+#pragma once
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "statslog.h"
+#include "src/logd/LogEvent.h"
+#include "src/StatsLogProcessor.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Create AtomMatcher proto for acquiring wakelock.
+AtomMatcher CreateAcquireWakelockAtomMatcher();
+
+// Create AtomMatcher proto for releasing wakelock.
+AtomMatcher CreateReleaseWakelockAtomMatcher() ;
+
+// Create AtomMatcher proto for screen turned on.
+AtomMatcher CreateScreenTurnedOnAtomMatcher();
+
+// Create AtomMatcher proto for screen turned off.
+AtomMatcher CreateScreenTurnedOffAtomMatcher();
+
+// Create AtomMatcher proto for app sync turned on.
+AtomMatcher CreateSyncStartAtomMatcher();
+
+// Create AtomMatcher proto for app sync turned off.
+AtomMatcher CreateSyncEndAtomMatcher();
+
+// Create AtomMatcher proto for app sync moves to background.
+AtomMatcher CreateMoveToBackgroundAtomMatcher();
+
+// Create AtomMatcher proto for app sync moves to foreground.
+AtomMatcher CreateMoveToForegroundAtomMatcher();
+
+// Create AtomMatcher proto for process crashes
+AtomMatcher CreateProcessCrashAtomMatcher() ;
+
+// Create Predicate proto for screen is on.
+Predicate CreateScreenIsOnPredicate();
+
+// Create Predicate proto for screen is off.
+Predicate CreateScreenIsOffPredicate();
+
+// Create Predicate proto for holding wakelock.
+Predicate CreateHoldingWakelockPredicate();
+
+// Create a Predicate proto for app syncing.
+Predicate CreateIsSyncingPredicate();
+
+// Create a Predicate proto for app is in background.
+Predicate CreateIsInBackgroundPredicate();
+
+// Add a predicate to the predicate combination.
+void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
+
+// Create dimensions from primitive fields.
+FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields);
+
+// Create dimensions by attribution uid and tag.
+FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
+                                                  const std::vector<Position>& positions);
+
+// Create dimensions by attribution uid only.
+FieldMatcher CreateAttributionUidDimensions(const int atomId,
+                                            const std::vector<Position>& positions);
+
+// Create log event for screen state changed.
+std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
+    const ScreenStateChanged::State state, uint64_t timestampNs);
+
+// Create log event for app moving to background.
+std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs);
+
+// Create log event for app moving to foreground.
+std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs);
+
+// Create log event when the app sync starts.
+std::unique_ptr<LogEvent> CreateSyncStartEvent(
+    const int uid, const string& name, uint64_t timestampNs);
+
+// Create log event when the app sync ends.
+std::unique_ptr<LogEvent> CreateSyncEndEvent(
+    const int uid, const string& name, uint64_t timestampNs);
+
+// Create log event when the app sync ends.
+std::unique_ptr<LogEvent> CreateAppCrashEvent(
+    const int uid, uint64_t timestampNs);
+
+// Create log event for acquiring wakelock.
+std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
+    const std::vector<AttributionNode>& attributions,
+    const string& wakelockName, uint64_t timestampNs);
+
+// Create log event for releasing wakelock.
+std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
+    const std::vector<AttributionNode>& attributions,
+    const string& wakelockName, uint64_t timestampNs);
+
+// Helper function to create an AttributionNode proto.
+AttributionNode CreateAttribution(const int& uid, const string& tag);
+
+// Create a statsd log event processor upon the start time in seconds, config and key.
+sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
+                                              const ConfigKey& key);
+
+// Util function to sort the log events by timestamp.
+void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
+
+int64_t StringToId(const string& str);
+
+void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid);
+void ValidateAttributionUidAndTagDimension(
+    const DimensionsValue& value, int atomId, int uid, const std::string& tag);
+
+template <typename T>
+void sortMetricDataByDimensionsValue(const T& metricData, T* sortedMetricData) {
+    std::map<HashableDimensionKey, int> dimensionIndexMap;
+    for (int i = 0; i < metricData.data_size(); ++i) {
+        dimensionIndexMap.insert(std::make_pair(metricData.data(i).dimension(), i));
+    }
+    for (const auto& itr : dimensionIndexMap) {
+        *sortedMetricData->add_data() = metricData.data(itr.second);
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk
index 7bd15d7..a4c0800 100644
--- a/cmds/statsd/tools/dogfood/Android.mk
+++ b/cmds/statsd/tools/dogfood/Android.mk
@@ -20,7 +20,8 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += ../../src/stats_log.proto \
-                   ../../src/atoms.proto
+                   ../../src/atoms.proto \
+                   ../../src/statsd_config.proto
 
 LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
index 0329992..c1c3914 100644
--- a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
+++ b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
index fe3d86d..93dba71 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
@@ -26,7 +26,7 @@
         sb.append("ConfigKey: ");
         if (reports.hasConfigKey()) {
             com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey();
-            sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName())
+            sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getId())
                     .append("\n");
         }
 
@@ -34,7 +34,7 @@
             sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
             for (StatsLog.StatsLogReport log : report.getMetricsList()) {
                 sb.append("\n\n");
-                sb.append("metric id: ").append(log.getMetricName()).append("\n");
+                sb.append("metric id: ").append(log.getMetricId()).append("\n");
                 sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n");
                 sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n");
 
@@ -71,20 +71,25 @@
         return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString();
     }
 
-    private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) {
-        for (com.android.os.StatsLog.KeyValuePair kv : pairs) {
-            sb.append(kv.getKey()).append(":");
-            if (kv.hasValueBool()) {
-                sb.append(kv.getValueBool());
-            } else if (kv.hasValueFloat()) {
-                sb.append(kv.getValueFloat());
-            } else if (kv.hasValueInt()) {
-                sb.append(kv.getValueInt());
-            } else if (kv.hasValueStr()) {
-                sb.append(kv.getValueStr());
+    private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) {
+        sb.append(dimensionValue.getField()).append(":");
+        if (dimensionValue.hasValueBool()) {
+            sb.append(dimensionValue.getValueBool());
+        } else if (dimensionValue.hasValueFloat()) {
+            sb.append(dimensionValue.getValueFloat());
+        } else if (dimensionValue.hasValueInt()) {
+            sb.append(dimensionValue.getValueInt());
+        } else if (dimensionValue.hasValueStr()) {
+            sb.append(dimensionValue.getValueStr());
+        } else if (dimensionValue.hasValueTuple()) {
+            sb.append("{");
+            for (StatsLog.DimensionsValue child :
+                    dimensionValue.getValueTuple().getDimensionsValueList()) {
+                displayDimension(sb, child);
             }
-            sb.append(" ");
+            sb.append("}");
         }
+        sb.append(" ");
     }
 
     public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
@@ -93,7 +98,7 @@
         sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n");
         for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) {
             sb.append("dimension: ");
-            displayDimension(sb, duration.getDimensionList());
+            displayDimension(sb, duration.getDimension());
             sb.append("\n");
 
             for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList())  {
@@ -120,7 +125,7 @@
         sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n");
         for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) {
             sb.append("dimension: ");
-            displayDimension(sb, count.getDimensionList());
+            displayDimension(sb, count.getDimension());
             sb.append("\n");
 
             for (StatsLog.CountBucketInfo info : count.getBucketInfoList())  {
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
index 70dd634..4f9032f 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -34,6 +34,7 @@
 
 public class MainActivity extends Activity {
     private final static String TAG = "StatsdDogfood";
+    private final static long CONFIG_ID = 987654321;
 
     final int[] mUids = {11111111, 2222222};
     StatsManager mStatsManager;
@@ -163,7 +164,7 @@
                     return;
                 }
                 if (mStatsManager != null) {
-                    byte[] data = mStatsManager.getData("fake");
+                    byte[] data = mStatsManager.getData(CONFIG_ID);
                     if (data != null) {
                         displayData(data);
                     } else {
@@ -186,7 +187,7 @@
                     byte[] config = new byte[inputStream.available()];
                     inputStream.read(config);
                     if (mStatsManager != null) {
-                        if (mStatsManager.addConfiguration("fake",
+                        if (mStatsManager.addConfiguration(CONFIG_ID,
                                 config, getPackageName(), MainActivity.this.getClass().getName())) {
                             Toast.makeText(
                                     MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show();
@@ -252,7 +253,9 @@
             Log.d(TAG, "invalid pkg id");
             return;
         }
-        StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, mUids[id], 0, name, 1);
+        int[] uids = new int[] {mUids[id]};
+        String[] tags  = new String[] {"acquire"};
+        StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, 0, name, 1);
         StringBuilder sb = new StringBuilder();
         sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
                 .append(", ").append(name).append(", 1);");
@@ -264,7 +267,9 @@
             Log.d(TAG, "invalid pkg id");
             return;
         }
-        StatsLog.write(10, mUids[id], 0, name, 0);
+        int[] uids = new int[] {mUids[id]};
+        String[] tags  = new String[] {"release"};
+        StatsLog.write(10, uids, tags, 0, name, 0);
         StringBuilder sb = new StringBuilder();
         sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
                 .append(", ").append(name).append(", 0);");
diff --git a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
index 2a254df..857853e 100644
--- a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
+++ b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
@@ -64,15 +64,11 @@
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:text="@string/bucket_label" />
-            <EditText
-                android:id="@+id/bucket"
-                android:inputType="number"
-                android:layout_weight="1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:maxLength="3"
-                android:text="@integer/bucket_default"
-                android:textSize="30dp"/>
+             <Spinner
+                 android:id="@+id/bucket_spinner"
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:prompt="@string/bucket_label"/>
         </LinearLayout>
 
         <LinearLayout
@@ -137,13 +133,50 @@
                 android:text="@integer/duration_default"
                 android:textSize="30dp"/>
         </LinearLayout>
-	<CheckBox
+        <CheckBox
             android:id="@+id/placebo"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/placebo"
             android:checked="false" />
 
+        <LinearLayout
+            android:gravity="center"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <CheckBox
+                android:id="@+id/include_count"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/count"
+                android:checked="true"/>
+            <CheckBox
+                android:id="@+id/include_duration"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/duration"
+                android:checked="true"/>
+            <CheckBox
+                android:id="@+id/include_event"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/event"
+                android:checked="true"/>
+            <CheckBox
+                android:id="@+id/include_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/value"
+                android:checked="true"/>
+            <CheckBox
+                android:id="@+id/include_gauge"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/gauge"
+                android:checked="true"/>
+        </LinearLayout>
+
         <Space
             android:layout_width="1dp"
             android:layout_height="30dp"/>
diff --git a/cmds/statsd/tools/loadtest/res/layout/spinner_item.xml b/cmds/statsd/tools/loadtest/res/layout/spinner_item.xml
new file mode 100644
index 0000000..b03da06
--- /dev/null
+++ b/cmds/statsd/tools/loadtest/res/layout/spinner_item.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:textSize="30dp"
+    android:gravity="left"
+    android:padding="5dip"
+    />
diff --git a/cmds/statsd/tools/loadtest/res/raw/loadtest_config b/cmds/statsd/tools/loadtest/res/raw/loadtest_config
index fbce0e8..2422190 100755
--- a/cmds/statsd/tools/loadtest/res/raw/loadtest_config
+++ b/cmds/statsd/tools/loadtest/res/raw/loadtest_config
Binary files differ
diff --git a/cmds/statsd/tools/loadtest/res/values/integers.xml b/cmds/statsd/tools/loadtest/res/values/integers.xml
index 76b5692..c2407d3 100644
--- a/cmds/statsd/tools/loadtest/res/values/integers.xml
+++ b/cmds/statsd/tools/loadtest/res/values/integers.xml
@@ -17,7 +17,6 @@
 */
 -->
 <resources>
-    <integer name="bucket_default">10</integer>
     <integer name="burst_default">1</integer>
     <integer name="period_default">2</integer>
     <integer name="replication_default">1</integer>
diff --git a/cmds/statsd/tools/loadtest/res/values/strings.xml b/cmds/statsd/tools/loadtest/res/values/strings.xml
index 522337e..e8ae3f8 100644
--- a/cmds/statsd/tools/loadtest/res/values/strings.xml
+++ b/cmds/statsd/tools/loadtest/res/values/strings.xml
@@ -20,11 +20,17 @@
     <string name="app_name">Statsd Loadtest</string>
     <string name="bucket_label">bucket size (mins):&#160;</string>
     <string name="burst_label">burst:&#160;</string>
+    <string name="bucket_default">FIVE_MINUTES</string>
     <string name="placebo">placebo</string>
     <string name="period_label">logging period (secs):&#160;</string>
     <string name="replication_label">metric replication:&#160;</string>
     <string name="duration_label">test duration (mins):&#160;</string>
     <string name="start"> &#160;Start&#160; </string>
     <string name="stop"> &#160;Stop&#160; </string>
+    <string name="count"> count </string>
+    <string name="duration"> duration </string>
+    <string name="event"> event </string>
+    <string name="value"> value </string>
+    <string name="gauge"> gauge </string>
 
 </resources>
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java
index d2ff892..bab0c1e 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java
@@ -18,6 +18,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.util.Log;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
 import java.text.ParseException;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -26,9 +27,11 @@
     private static final String TAG = "loadtest.BatteryDataRecorder";
     private static final String DUMP_FILENAME = TAG + "_dump.tmp";
 
-    public BatteryDataRecorder(boolean placebo, int replication, long bucketMins, long periodSecs,
-        int burst) {
-        super(placebo, replication, bucketMins, periodSecs, burst);
+    public BatteryDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs,
+        int burst, boolean includeCountMetric, boolean includeDurationMetric,
+        boolean includeEventMetric,  boolean includeValueMetric, boolean includeGaugeMetric) {
+      super(placebo, replication, bucket, periodSecs, burst, includeCountMetric,
+          includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric);
     }
 
     @Override
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
index 0d890fb..843b1e5 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
@@ -27,11 +27,11 @@
 import com.android.internal.os.StatsdConfigProto.EventMetric;
 import com.android.internal.os.StatsdConfigProto.GaugeMetric;
 import com.android.internal.os.StatsdConfigProto.ValueMetric;
-import com.android.internal.os.StatsdConfigProto.KeyMatcher;
-import com.android.internal.os.StatsdConfigProto.KeyValueMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.SimplePredicate;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
 
 import java.io.InputStream;
 import java.io.IOException;
@@ -42,7 +42,17 @@
  * Creates StatsdConfig protos for loadtesting.
  */
 public class ConfigFactory {
-    public static final String CONFIG_NAME = "LOADTEST";
+    public static class ConfigMetadata {
+        public final byte[] bytes;
+        public final int numMetrics;
+
+        public ConfigMetadata(byte[] bytes, int numMetrics) {
+            this.bytes = bytes;
+            this.numMetrics = numMetrics;
+        }
+    }
+
+    public static final long CONFIG_ID = 123456789;
 
     private static final String TAG = "loadtest.ConfigFactory";
 
@@ -81,36 +91,48 @@
      *        ones
      * @param bucketMillis The bucket size, in milliseconds, for aggregate metrics
      * @param placebo If true, only return an empty config
-     * @return The serialized config
+     * @return The serialized config and the number of metrics.
      */
-  public byte[] getConfig(int replication, long bucketMillis, boolean placebo) {
+    public ConfigMetadata getConfig(int replication, TimeUnit bucket, boolean placebo,
+            boolean includeCount, boolean includeDuration, boolean includeEvent,
+            boolean includeValue, boolean includeGauge) {
         StatsdConfig.Builder config = StatsdConfig.newBuilder()
-            .setName(CONFIG_NAME);
+            .setId(CONFIG_ID);
         if (placebo) {
           replication = 0;  // Config will be empty, aside from a name.
         }
         int numMetrics = 0;
         for (int i = 0; i < replication; i++) {
             // metrics
-            for (EventMetric metric : mTemplate.getEventMetricList()) {
-                addEventMetric(metric, i, config);
-                numMetrics++;
+            if (includeEvent) {
+                for (EventMetric metric : mTemplate.getEventMetricList()) {
+                    addEventMetric(metric, i, config);
+                    numMetrics++;
+                }
             }
-            for (CountMetric metric : mTemplate.getCountMetricList()) {
-                addCountMetric(metric, i, bucketMillis, config);
-                numMetrics++;
+            if (includeCount) {
+                for (CountMetric metric : mTemplate.getCountMetricList()) {
+                    addCountMetric(metric, i, bucket, config);
+                    numMetrics++;
+                }
             }
-            for (DurationMetric metric : mTemplate.getDurationMetricList()) {
-                addDurationMetric(metric, i, bucketMillis, config);
-                numMetrics++;
+            if (includeDuration) {
+                for (DurationMetric metric : mTemplate.getDurationMetricList()) {
+                    addDurationMetric(metric, i, bucket, config);
+                    numMetrics++;
+                }
             }
-            for (GaugeMetric metric : mTemplate.getGaugeMetricList()) {
-                addGaugeMetric(metric, i, bucketMillis, config);
-                numMetrics++;
+            if (includeGauge) {
+                for (GaugeMetric metric : mTemplate.getGaugeMetricList()) {
+                    addGaugeMetric(metric, i, bucket, config);
+                    numMetrics++;
+                }
             }
-            for (ValueMetric metric : mTemplate.getValueMetricList()) {
-                addValueMetric(metric, i, bucketMillis, config);
-                numMetrics++;
+            if (includeValue) {
+                for (ValueMetric metric : mTemplate.getValueMetricList()) {
+                    addValueMetric(metric, i, bucket, config);
+                    numMetrics++;
+                }
             }
             // predicates
             for (Predicate predicate : mTemplate.getPredicateList()) {
@@ -125,7 +147,7 @@
         Log.d(TAG, "Loadtest config is : " + config.build());
         Log.d(TAG, "Generated config has " + numMetrics + " metrics");
 
-        return config.build().toByteArray();
+        return new ConfigMetadata(config.build().toByteArray(), numMetrics);
     }
 
     /**
@@ -149,7 +171,7 @@
      */
     private void addEventMetric(EventMetric template, int suffix, StatsdConfig.Builder config) {
         EventMetric.Builder metric = template.toBuilder()
-            .setName(template.getName() + suffix)
+            .setId(template.getId() + suffix)
             .setWhat(template.getWhat() + suffix);
         if (template.hasCondition()) {
             metric.setCondition(template.getCondition() + suffix);
@@ -162,20 +184,14 @@
         config.addEventMetric(metric);
     }
 
-    private Bucket getBucket(long bucketMillis) {
-        return Bucket.newBuilder()
-            .setBucketSizeMillis(bucketMillis)
-            .build();
-    }
-
     /**
      * Creates a {@link CountMetric} based on the template. Makes sure that all names are appended
      * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
      */
-    private void addCountMetric(CountMetric template, int suffix, long bucketMillis,
+    private void addCountMetric(CountMetric template, int suffix, TimeUnit bucket,
         StatsdConfig.Builder config) {
         CountMetric.Builder metric = template.toBuilder()
-            .setName(template.getName() + suffix)
+            .setId(template.getId() + suffix)
             .setWhat(template.getWhat() + suffix);
         if (template.hasCondition()) {
             metric.setCondition(template.getCondition() + suffix);
@@ -185,7 +201,7 @@
             metric.clearLinks();
             metric.addAllLinks(links);
         }
-        metric.setBucket(getBucket(bucketMillis));
+        metric.setBucket(bucket);
         config.addCountMetric(metric);
     }
 
@@ -193,10 +209,10 @@
      * Creates a {@link DurationMetric} based on the template. Makes sure that all names are appended
      * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
      */
-    private void addDurationMetric(DurationMetric template, int suffix, long bucketMillis,
+    private void addDurationMetric(DurationMetric template, int suffix, TimeUnit bucket,
         StatsdConfig.Builder config) {
         DurationMetric.Builder metric = template.toBuilder()
-            .setName(template.getName() + suffix)
+            .setId(template.getId() + suffix)
             .setWhat(template.getWhat() + suffix);
         if (template.hasCondition()) {
             metric.setCondition(template.getCondition() + suffix);
@@ -206,7 +222,7 @@
             metric.clearLinks();
             metric.addAllLinks(links);
         }
-        metric.setBucket(getBucket(bucketMillis));
+        metric.setBucket(bucket);
         config.addDurationMetric(metric);
     }
 
@@ -214,10 +230,10 @@
      * Creates a {@link GaugeMetric} based on the template. Makes sure that all names are appended
      * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
      */
-    private void addGaugeMetric(GaugeMetric template, int suffix, long bucketMillis,
+    private void addGaugeMetric(GaugeMetric template, int suffix, TimeUnit bucket,
         StatsdConfig.Builder config) {
         GaugeMetric.Builder metric = template.toBuilder()
-            .setName(template.getName() + suffix)
+            .setId(template.getId() + suffix)
             .setWhat(template.getWhat() + suffix);
         if (template.hasCondition()) {
             metric.setCondition(template.getCondition() + suffix);
@@ -227,7 +243,7 @@
             metric.clearLinks();
             metric.addAllLinks(links);
         }
-        metric.setBucket(getBucket(bucketMillis));
+        metric.setBucket(bucket);
         config.addGaugeMetric(metric);
     }
 
@@ -235,10 +251,10 @@
      * Creates a {@link ValueMetric} based on the template. Makes sure that all names are appended
      * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
      */
-    private void addValueMetric(ValueMetric template, int suffix, long bucketMillis,
+    private void addValueMetric(ValueMetric template, int suffix, TimeUnit bucket,
         StatsdConfig.Builder config) {
         ValueMetric.Builder metric = template.toBuilder()
-            .setName(template.getName() + suffix)
+            .setId(template.getId() + suffix)
             .setWhat(template.getWhat() + suffix);
         if (template.hasCondition()) {
             metric.setCondition(template.getCondition() + suffix);
@@ -248,7 +264,7 @@
             metric.clearLinks();
             metric.addAllLinks(links);
         }
-        metric.setBucket(getBucket(bucketMillis));
+        metric.setBucket(bucket);
         config.addValueMetric(metric);
     }
 
@@ -258,11 +274,11 @@
      */
     private void addPredicate(Predicate template, int suffix, StatsdConfig.Builder config) {
         Predicate.Builder predicate = template.toBuilder()
-            .setName(template.getName() + suffix);
+            .setId(template.getId() + suffix);
         if (template.hasCombination()) {
             Predicate.Combination.Builder cb = template.getCombination().toBuilder()
                 .clearPredicate();
-            for (String child : template.getCombination().getPredicateList()) {
+            for (long child : template.getCombination().getPredicateList()) {
                 cb.addPredicate(child + suffix);
             }
             predicate.setCombination(cb.build());
@@ -285,11 +301,11 @@
      */
     private void addMatcher(AtomMatcher template, int suffix, StatsdConfig.Builder config) {
         AtomMatcher.Builder matcher = template.toBuilder()
-            .setName(template.getName() + suffix);
+            .setId(template.getId() + suffix);
         if (template.hasCombination()) {
             AtomMatcher.Combination.Builder cb = template.getCombination().toBuilder()
                 .clearMatcher();
-            for (String child : template.getCombination().getMatcherList()) {
+            for (long child : template.getCombination().getMatcherList()) {
                 cb.addMatcher(child + suffix);
             }
             matcher.setCombination(cb);
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
index 735a327..19087d8 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
@@ -26,7 +26,7 @@
         sb.append("ConfigKey: ");
         if (reports.hasConfigKey()) {
             com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey();
-            sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName())
+            sb.append("\tuid: ").append(key.getUid()).append(" id: ").append(key.getId())
                     .append("\n");
         }
 
@@ -34,7 +34,7 @@
             sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
             for (StatsLog.StatsLogReport log : report.getMetricsList()) {
                 sb.append("\n\n");
-                sb.append("metric id: ").append(log.getMetricName()).append("\n");
+                sb.append("metric id: ").append(log.getMetricId()).append("\n");
                 sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n");
                 sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n");
 
@@ -71,20 +71,25 @@
         return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString();
     }
 
-    private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) {
-        for (com.android.os.StatsLog.KeyValuePair kv : pairs) {
-            sb.append(kv.getKey()).append(":");
-            if (kv.hasValueBool()) {
-                sb.append(kv.getValueBool());
-            } else if (kv.hasValueFloat()) {
-                sb.append(kv.getValueFloat());
-            } else if (kv.hasValueInt()) {
-                sb.append(kv.getValueInt());
-            } else if (kv.hasValueStr()) {
-                sb.append(kv.getValueStr());
+    private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) {
+        sb.append(dimensionValue.getField()).append(":");
+        if (dimensionValue.hasValueBool()) {
+            sb.append(dimensionValue.getValueBool());
+        } else if (dimensionValue.hasValueFloat()) {
+            sb.append(dimensionValue.getValueFloat());
+        } else if (dimensionValue.hasValueInt()) {
+            sb.append(dimensionValue.getValueInt());
+        } else if (dimensionValue.hasValueStr()) {
+            sb.append(dimensionValue.getValueStr());
+        } else if (dimensionValue.hasValueTuple()) {
+            sb.append("{");
+            for (StatsLog.DimensionsValue child :
+                    dimensionValue.getValueTuple().getDimensionsValueList()) {
+                displayDimension(sb, child);
             }
-            sb.append(" ");
+            sb.append("}");
         }
+        sb.append(" ");
     }
 
     public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
@@ -93,7 +98,7 @@
         sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n");
         for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) {
             sb.append("dimension: ");
-            displayDimension(sb, duration.getDimensionList());
+            displayDimension(sb, duration.getDimension());
             sb.append("\n");
 
             for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList())  {
@@ -120,7 +125,7 @@
         sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n");
         for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) {
             sb.append("dimension: ");
-            displayDimension(sb, count.getDimensionList());
+            displayDimension(sb, count.getDimension());
             sb.append("\n");
 
             for (StatsLog.CountBucketInfo info : count.getBucketInfoList())  {
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
index 0a30ff8..c81ac07 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
@@ -39,15 +39,22 @@
 import android.view.inputmethod.InputMethodManager;
 import android.view.MotionEvent;
 import android.view.View.OnFocusChangeListener;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.EditText;
+import android.widget.Spinner;
 import android.widget.TextView;
 import android.widget.Toast;
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.StatsdStatsReport;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Runs a load test for statsd.
@@ -67,7 +74,7 @@
  *   <li> The 'burst' parameter controls how many atoms are pushed at the same time (per period).
  * </ul>
  */
-public class LoadtestActivity extends Activity {
+public class LoadtestActivity extends Activity implements AdapterView.OnItemSelectedListener {
 
     private static final String TAG = "StatsdLoadtest";
     public static final String TYPE = "type";
@@ -75,6 +82,8 @@
     public static final String PERF_ALARM = "perf_alarm";
     private static final String START = "start";
     private static final String STOP = "stop";
+    private static final Map<String, TimeUnit> TIME_UNIT_MAP = initializeTimeUnitMap();
+    private static final List<String> TIME_UNIT_LABELS = initializeTimeUnitLabels();
 
     public final static class PusherAlarmReceiver extends BroadcastReceiver {
         @Override
@@ -94,6 +103,35 @@
          }
     }
 
+    private static Map<String, TimeUnit> initializeTimeUnitMap() {
+        Map<String, TimeUnit> labels = new HashMap();
+        labels.put("1m", TimeUnit.ONE_MINUTE);
+        labels.put("5m", TimeUnit.FIVE_MINUTES);
+        labels.put("10m", TimeUnit.TEN_MINUTES);
+        labels.put("30m", TimeUnit.THIRTY_MINUTES);
+        labels.put("1h", TimeUnit.ONE_HOUR);
+        labels.put("3h", TimeUnit.THREE_HOURS);
+        labels.put("6h", TimeUnit.SIX_HOURS);
+        labels.put("12h", TimeUnit.TWELVE_HOURS);
+        labels.put("1d", TimeUnit.ONE_DAY);
+        labels.put("1s", TimeUnit.CTS);
+        return labels;
+    }
+    private static List<String> initializeTimeUnitLabels() {
+        List<String> labels = new ArrayList();
+        labels.add("1s");
+        labels.add("1m");
+        labels.add("5m");
+        labels.add("10m");
+        labels.add("30m");
+        labels.add("1h");
+        labels.add("3h");
+        labels.add("6h");
+        labels.add("12h");
+        labels.add("1d");
+        return labels;
+    }
+
     private AlarmManager mAlarmMgr;
 
     /** Used to periodically log atoms to logd. */
@@ -104,12 +142,17 @@
 
     private Button mStartStop;
     private EditText mReplicationText;
-    private EditText mBucketText;
+    private Spinner mBucketSpinner;
     private EditText mPeriodText;
     private EditText mBurstText;
     private EditText mDurationText;
     private TextView mReportText;
     private CheckBox mPlaceboCheckBox;
+    private CheckBox mCountMetricCheckBox;
+    private CheckBox mDurationMetricCheckBox;
+    private CheckBox mEventMetricCheckBox;
+    private CheckBox mValueMetricCheckBox;
+    private CheckBox mGaugeMetricCheckBox;
 
     /** When the load test started. */
     private long mStartedTimeMillis;
@@ -129,6 +172,31 @@
      */
     private boolean mPlacebo;
 
+    /**
+     * Whether to include CountMetric in the config.
+     */
+    private boolean mIncludeCountMetric;
+
+    /**
+     * Whether to include DurationMetric in the config.
+     */
+    private boolean mIncludeDurationMetric;
+
+    /**
+     * Whether to include EventMetric in the config.
+     */
+    private boolean mIncludeEventMetric;
+
+    /**
+     * Whether to include ValueMetric in the config.
+     */
+    private boolean mIncludeValueMetric;
+
+    /**
+     * Whether to include GaugeMetric in the config.
+     */
+    private boolean mIncludeGaugeMetric;
+
     /** The burst size. */
     private int mBurst;
 
@@ -139,7 +207,7 @@
     private long mPeriodSecs;
 
     /** The bucket size, in minutes, for aggregate metrics. */
-    private long mBucketMins;
+    private TimeUnit mBucket;
 
     /** The duration, in minutes, of the loadtest. */
     private long mDurationMins;
@@ -156,6 +224,9 @@
     /** For intra-minute periods. */
     private final Handler mHandler = new Handler();
 
+    /** Number of metrics in the current config. */
+    private int mNumMetrics;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -170,6 +241,7 @@
         initPeriod();
         initDuration();
         initPlacebo();
+        initMetricWhitelist();
 
         // Hide the keyboard outside edit texts.
         findViewById(R.id.outside).setOnTouchListener(new View.OnTouchListener() {
@@ -263,7 +335,7 @@
             return null;
         }
         if (mStatsManager != null) {
-            byte[] data = mStatsManager.getData(ConfigFactory.CONFIG_NAME);
+            byte[] data = mStatsManager.getData(ConfigFactory.CONFIG_ID);
             if (data != null) {
                 ConfigMetricsReportList reports = null;
                 try {
@@ -279,6 +351,18 @@
         return null;
     }
 
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+        String item = parent.getItemAtPosition(position).toString();
+
+        mBucket = TIME_UNIT_MAP.get(item);
+    }
+
+    @Override
+    public void onNothingSelected(AdapterView<?> parent) {
+        // Another interface callback
+    }
+
     private void onPerfAlarm() {
         if (mPerfData != null) {
             mPerfData.onAlarm(this);
@@ -286,7 +370,9 @@
         // Piggy-back on that alarm to show the elapsed time.
         long elapsedTimeMins = (long) Math.floor(
             (SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000);
-        mReportText.setText("Loadtest in progress. Elapsed time = " + elapsedTimeMins + " min(s)");
+        mReportText.setText("Loadtest in progress.\n"
+            + "num metrics =" + mNumMetrics
+            + "\nElapsed time = " + elapsedTimeMins + " min(s)");
     }
 
     private void onAlarm() {
@@ -329,7 +415,9 @@
         getData();
 
         // Create a config and push it to statsd.
-        if (!setConfig(mFactory.getConfig(mReplication, mBucketMins * 60 * 1000, mPlacebo))) {
+        if (!setConfig(mFactory.getConfig(mReplication, mBucket, mPlacebo,
+                mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric,
+                mIncludeValueMetric, mIncludeGaugeMetric))) {
             return;
         }
 
@@ -344,10 +432,12 @@
         scheduleNext();
 
         // Start tracking performance.
-        mPerfData = new PerfData(this, mPlacebo, mReplication, mBucketMins, mPeriodSecs, mBurst);
+        mPerfData = new PerfData(this, mPlacebo, mReplication, mBucket, mPeriodSecs, mBurst,
+            mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric, mIncludeValueMetric,
+            mIncludeGaugeMetric);
         mPerfData.startRecording(this);
 
-        mReportText.setText("Loadtest in progress.");
+        mReportText.setText("Loadtest in progress.\nnum metrics =" + mNumMetrics);
         mStartedTimeMillis = SystemClock.elapsedRealtime();
 
         updateStarted(true);
@@ -393,9 +483,16 @@
         mBurstText.setEnabled(!mPlacebo && !mStarted);
         mReplicationText.setEnabled(!mPlacebo && !mStarted);
         mPeriodText.setEnabled(!mStarted);
-        mBucketText.setEnabled(!mPlacebo && !mStarted);
+        mBucketSpinner.setEnabled(!mPlacebo && !mStarted);
         mDurationText.setEnabled(!mStarted);
         mPlaceboCheckBox.setEnabled(!mStarted);
+
+        boolean enabled = !mStarted && !mPlaceboCheckBox.isChecked();
+        mCountMetricCheckBox.setEnabled(enabled);
+        mDurationMetricCheckBox.setEnabled(enabled);
+        mEventMetricCheckBox.setEnabled(enabled);
+        mValueMetricCheckBox.setEnabled(enabled);
+        mGaugeMetricCheckBox.setEnabled(enabled);
     }
 
     private boolean statsdRunning() {
@@ -420,7 +517,7 @@
         // TODO: Clear all configs instead of specific ones.
         if (mStatsManager != null) {
             if (mStarted) {
-                if (!mStatsManager.removeConfiguration(ConfigFactory.CONFIG_NAME)) {
+                if (!mStatsManager.removeConfiguration(ConfigFactory.CONFIG_ID)) {
                     Log.d(TAG, "Removed loadtest statsd configs.");
                 } else {
                     Log.d(TAG, "Failed to remove loadtest configs.");
@@ -429,10 +526,11 @@
         }
     }
 
-    private boolean setConfig(byte[] config) {
+    private boolean setConfig(ConfigFactory.ConfigMetadata configData) {
       if (mStatsManager != null) {
-            if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_NAME,
-                config, getPackageName(), LoadtestActivity.this.getClass().getName())) {
+            if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_ID,
+                configData.bytes, getPackageName(), LoadtestActivity.this.getClass().getName())) {
+                mNumMetrics = configData.numMetrics;
                 Log.d(TAG, "Config pushed to statsd");
                 return true;
             } else {
@@ -450,10 +548,6 @@
         mPeriodSecs = periodSecs;
     }
 
-    private synchronized void setBucketMins(long bucketMins) {
-        mBucketMins = bucketMins;
-    }
-
     private synchronized void setBurst(int burst) {
         mBurst = burst;
     }
@@ -462,12 +556,9 @@
         mDurationMins = durationMins;
     }
 
-    private synchronized void setPlacebo(boolean placebo) {
-        mPlacebo = placebo;
-        updateControlsEnabled();
-    }
 
     private void handleFocus(EditText editText) {
+      /*
         editText.setOnFocusChangeListener(new OnFocusChangeListener() {
             @Override
             public void onFocusChange(View v, boolean hasFocus) {
@@ -476,6 +567,7 @@
                 }
             }
         });
+      */
     }
 
     private void initBurst() {
@@ -503,15 +595,23 @@
     }
 
     private void initBucket() {
-        mBucketMins = getResources().getInteger(R.integer.bucket_default);
-        mBucketText = (EditText) findViewById(R.id.bucket);
-        mBucketText.addTextChangedListener(new NumericalWatcher(mBucketText, 1, 24 * 60) {
-            @Override
-            public void onNewValue(int newValue) {
-                setBucketMins(newValue);
+        String defaultValue = getResources().getString(R.string.bucket_default);
+        mBucket = TimeUnit.valueOf(defaultValue);
+        mBucketSpinner = (Spinner) findViewById(R.id.bucket_spinner);
+
+        ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(
+            this, R.layout.spinner_item, TIME_UNIT_LABELS);
+
+        mBucketSpinner.setAdapter(dataAdapter);
+        mBucketSpinner.setOnItemSelectedListener(this);
+
+        for (String label : TIME_UNIT_MAP.keySet()) {
+          Log.d(TAG, "EVALUATE " + label + " VS " + defaultValue);
+          if (defaultValue.equals(TIME_UNIT_MAP.get(label).toString())) {
+                Log.d(TAG, " FOUND IT");
+                mBucketSpinner.setSelection(dataAdapter.getPosition(label));
             }
-        });
-        handleFocus(mBucketText);
+        }
     }
 
     private void initPeriod() {
@@ -544,8 +644,53 @@
         mPlaceboCheckBox.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
-                setPlacebo(((CheckBox) view).isChecked());
+                mPlacebo = mPlaceboCheckBox.isChecked();
+                updateControlsEnabled();
             }
         });
     }
+
+    private void initMetricWhitelist() {
+        mCountMetricCheckBox = findViewById(R.id.include_count);
+        mCountMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIncludeCountMetric = mCountMetricCheckBox.isChecked();
+            }
+        });
+        mDurationMetricCheckBox = findViewById(R.id.include_duration);
+        mDurationMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIncludeDurationMetric = mDurationMetricCheckBox.isChecked();
+            }
+        });
+        mEventMetricCheckBox = findViewById(R.id.include_event);
+        mEventMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIncludeEventMetric = mEventMetricCheckBox.isChecked();
+            }
+        });
+        mValueMetricCheckBox = findViewById(R.id.include_value);
+        mValueMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIncludeValueMetric = mValueMetricCheckBox.isChecked();
+            }
+        });
+        mGaugeMetricCheckBox = findViewById(R.id.include_gauge);
+        mGaugeMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked();
+            }
+        });
+
+        mIncludeCountMetric = mCountMetricCheckBox.isChecked();
+        mIncludeDurationMetric = mDurationMetricCheckBox.isChecked();
+        mIncludeEventMetric = mEventMetricCheckBox.isChecked();
+        mIncludeValueMetric = mValueMetricCheckBox.isChecked();
+        mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked();
+    }
 }
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java
index d9513a1..af7bd4d 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.os.SystemClock;
 import android.util.Log;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
 
 public class MemoryDataRecorder extends PerfDataRecorder {
     private static final String TAG = "loadtest.MemoryDataDataRecorder";
@@ -26,9 +27,11 @@
     private long mStartTimeMillis;
     private StringBuilder mSb;
 
-    public MemoryDataRecorder(boolean placebo, int replication, long bucketMins, long periodSecs,
-        int burst) {
-        super(placebo, replication, bucketMins, periodSecs, burst);
+    public MemoryDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs,
+        int burst,  boolean includeCountMetric, boolean includeDurationMetric,
+        boolean includeEventMetric,  boolean includeValueMetric, boolean includeGaugeMetric) {
+      super(placebo, replication, bucket, periodSecs, burst, includeCountMetric,
+          includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric);
     }
 
     @Override
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java
index 22ba9c5..7a01ade 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java
@@ -15,6 +15,8 @@
  */
 package com.android.statsd.loadtest;
 
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+
 import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -52,15 +54,24 @@
     private final Set<PerfDataRecorder> mRecorders;
 
     public PerfData(LoadtestActivity loadtestActivity, boolean placebo, int replication,
-        long bucketMins, long periodSecs,  int burst) {
-        super(placebo, replication, bucketMins, periodSecs, burst);
+        TimeUnit bucket, long periodSecs,  int burst, boolean includeCountMetric,
+        boolean includeDurationMetric, boolean includeEventMetric,  boolean includeValueMetric,
+        boolean includeGaugeMetric) {
+      super(placebo, replication, bucket, periodSecs, burst, includeCountMetric,
+          includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric);
         mRecorders = new HashSet();
-        mRecorders.add(new BatteryDataRecorder(placebo, replication, bucketMins, periodSecs, burst));
-        mRecorders.add(new MemoryDataRecorder(placebo, replication, bucketMins, periodSecs, burst));
-        mRecorders.add(new StatsdStatsRecorder(loadtestActivity, placebo, replication, bucketMins,
-                periodSecs, burst));
-        mRecorders.add(new ValidationRecorder(loadtestActivity, placebo, replication, bucketMins,
-                periodSecs, burst));
+        mRecorders.add(new BatteryDataRecorder(placebo, replication, bucket, periodSecs, burst,
+                includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric,
+                includeGaugeMetric));
+        mRecorders.add(new MemoryDataRecorder(placebo, replication, bucket, periodSecs, burst,
+                includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric,
+                includeGaugeMetric));
+        mRecorders.add(new StatsdStatsRecorder(loadtestActivity, placebo, replication, bucket,
+                periodSecs, burst, includeCountMetric, includeDurationMetric, includeEventMetric,
+                includeValueMetric, includeGaugeMetric));
+        mRecorders.add(new ValidationRecorder(loadtestActivity, placebo, replication, bucket,
+                periodSecs, burst, includeCountMetric, includeDurationMetric, includeEventMetric,
+                includeValueMetric, includeGaugeMetric));
         mAlarmMgr = (AlarmManager) loadtestActivity.getSystemService(Context.ALARM_SERVICE);
     }
 
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java
index 5b5ba37..8613ac1 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java
@@ -21,6 +21,7 @@
 import android.util.Log;
 import android.os.Debug;
 
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
 import java.io.BufferedReader;
 import java.io.Closeable;
 import java.io.File;
@@ -38,10 +39,13 @@
     protected final String mTimeAsString;
     protected final String mColumnSuffix;
 
-    protected PerfDataRecorder(boolean placebo, int replication, long bucketMins, long periodSecs,
-        int burst) {
+    protected PerfDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs,
+        int burst, boolean includeCountMetric, boolean includeDurationMetric,
+        boolean includeEventMetric,  boolean includeValueMetric, boolean includeGaugeMetric) {
         mTimeAsString = new SimpleDateFormat("YYYY_MM_dd_HH_mm_ss").format(new Date());
-        mColumnSuffix = getColumnSuffix(placebo, replication, bucketMins, periodSecs, burst);
+        mColumnSuffix = getColumnSuffix(placebo, replication, bucket, periodSecs, burst,
+            includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric,
+            includeGaugeMetric);
     }
 
     /** Starts recording performance data. */
@@ -120,15 +124,36 @@
     }
 
     /** Gets the suffix to use in the column name for perf data. */
-    private String getColumnSuffix(boolean placebo, int replication, long bucketMins,
-        long periodSecs, int burst) {
+    private String getColumnSuffix(boolean placebo, int replication, TimeUnit bucket,
+        long periodSecs, int burst, boolean includeCountMetric, boolean includeDurationMetric,
+        boolean includeEventMetric,  boolean includeValueMetric, boolean includeGaugeMetric) {
         if (placebo) {
             return "_placebo_p=" + periodSecs;
         }
-        return "_r=" + replication + "_bkt=" + bucketMins + "_p=" + periodSecs + "_bst=" + burst;
+        StringBuilder sb = new StringBuilder()
+            .append("_r=" + replication)
+            .append("_bkt=" + bucket)
+            .append("_p=" + periodSecs)
+            .append("_bst=" + burst)
+            .append("_m=");
+        if (includeCountMetric) {
+            sb.append("c");
+        }
+        if (includeEventMetric) {
+            sb.append("e");
+        }
+        if (includeDurationMetric) {
+            sb.append("d");
+        }
+        if (includeGaugeMetric) {
+            sb.append("g");
+        }
+        if (includeValueMetric) {
+            sb.append("v");
+        }
+        return sb.toString();
     }
 
-
     private File getStorageDir() {
         File file = new File(Environment.getExternalStoragePublicDirectory(
             Environment.DIRECTORY_DOCUMENTS), "loadtest/" + mTimeAsString);
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java
index 4ef5dc2..e63150f 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.util.Log;
 import com.android.os.StatsLog.StatsdStatsReport;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -27,8 +28,11 @@
     private final LoadtestActivity mLoadtestActivity;
 
     public StatsdStatsRecorder(LoadtestActivity loadtestActivity, boolean placebo, int replication,
-        long bucketMins, long periodSecs, int burst) {
-        super(placebo, replication, bucketMins, periodSecs, burst);
+        TimeUnit bucket, long periodSecs, int burst, boolean includeCountMetric,
+        boolean includeDurationMetric, boolean includeEventMetric,  boolean includeValueMetric,
+        boolean includeGaugeMetric) {
+      super(placebo, replication, bucket, periodSecs, burst, includeCountMetric,
+          includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric);
         mLoadtestActivity = loadtestActivity;
     }
 
@@ -55,7 +59,7 @@
                 .append(configStats.getConditionCount() + "\n")
                 .append("matcher_count,")
                 .append(configStats.getMatcherCount() + "\n");
-            writeData(context, "statsdstats_", "", sb);
+            writeData(context, "statsdstats_", "stat,value", sb);
         }
     }
 }
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java
index 4b614aa..d9f0ca9 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java
@@ -20,6 +20,7 @@
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.EventMetricData;
 import com.android.os.StatsLog.StatsLogReport;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -33,8 +34,11 @@
     private final LoadtestActivity mLoadtestActivity;
 
     public ValidationRecorder(LoadtestActivity loadtestActivity, boolean placebo, int replication,
-        long bucketMins, long periodSecs, int burst) {
-        super(placebo, replication, bucketMins, periodSecs, burst);
+        TimeUnit bucket, long periodSecs, int burst,  boolean includeCountMetric,
+        boolean includeDurationMetric, boolean includeEventMetric,  boolean includeValueMetric,
+        boolean includeGaugeMetric) {
+      super(placebo, replication, bucket, periodSecs, burst, includeCountMetric,
+          includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric);
         mLoadtestActivity = loadtestActivity;
     }
 
@@ -54,31 +58,25 @@
     }
 
     private void validateData() {
+        // The code below is commented out because it calls getData, which has the side-effect
+        // of clearing statsd's data buffer.
+        /*
         List<ConfigMetricsReport> reports = mLoadtestActivity.getData();
         if (reports != null) {
             Log.d(TAG, "GOT DATA");
             for (ConfigMetricsReport report : reports) {
                 for (StatsLogReport logReport : report.getMetricsList()) {
-                    if (!logReport.hasMetricName()) {
+                    if (!logReport.hasMetricId()) {
                         Log.e(TAG, "Metric missing name.");
-                        continue;
-                    }
-                    String metricName = logReport.getMetricName();
-                    if (metricName.startsWith("EVENT_BATTERY_LEVEL_CHANGES_WHILE_SCREEN_IS_ON_")) {
-                        validateEventBatteryLevelChangesWhileScreenIsOn(logReport);
-                        continue;
-                    }
-                    if (metricName.startsWith("EVENT_BATTERY_LEVEL_CHANGES_")) {
-                        validateEventBatteryLevelChanges(logReport);
-                        continue;
                     }
                 }
             }
         }
+        */
     }
 
     private void validateEventBatteryLevelChanges(StatsLogReport logReport) {
-        Log.d(TAG, "Validating " + logReport.getMetricName());
+        Log.d(TAG, "Validating " + logReport.getMetricId());
         if (logReport.hasEventMetrics()) {
             Log.d(TAG, "Num events captured: " + logReport.getEventMetrics().getDataCount());
             for (EventMetricData data : logReport.getEventMetrics().getDataList()) {
@@ -90,6 +88,6 @@
     }
 
     private void validateEventBatteryLevelChangesWhileScreenIsOn(StatsLogReport logReport) {
-        Log.d(TAG, "Validating " + logReport.getMetricName());
+        Log.d(TAG, "Validating " + logReport.getMetricId());
     }
 }
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index adbe9d0..34f6d7d 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.hardware.usb.IUsbManager;
+import android.hardware.usb.UsbManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -38,6 +39,9 @@
                 + "\n"
                 + "usage: svc usb setFunction [function] [usbDataUnlocked=false]\n"
                 + "         Set the current usb function and optionally the data lock state.\n\n"
+                + "       svc usb setScreenUnlockedFunctions [function]\n"
+                + "         Sets the functions which, if the device was charging,"
+                    + " become current on screen unlock.\n"
                 + "       svc usb getFunction\n"
                 + "          Gets the list of currently enabled functions\n";
     }
@@ -62,6 +66,16 @@
             } else if ("getFunction".equals(args[1])) {
                 System.err.println(SystemProperties.get("sys.usb.config"));
                 return;
+            } else if ("setScreenUnlockedFunctions".equals(args[1])) {
+                IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
+                        Context.USB_SERVICE));
+                try {
+                    usbMgr.setScreenUnlockedFunctions((args.length >= 3 ? args[2] :
+                            UsbManager.USB_FUNCTION_NONE));
+                } catch (RemoteException e) {
+                    System.err.println("Error communicating with UsbManager: " + e);
+                }
+                return;
             }
         }
         System.err.println(longHelp());
diff --git a/core/java/android/annotation/SystemApi.java b/core/java/android/annotation/SystemApi.java
index 55028eb..e96ff01 100644
--- a/core/java/android/annotation/SystemApi.java
+++ b/core/java/android/annotation/SystemApi.java
@@ -39,6 +39,6 @@
  * @hide
  */
 @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
-@Retention(RetentionPolicy.SOURCE)
+@Retention(RetentionPolicy.RUNTIME)
 public @interface SystemApi {
 }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1adae7a..847f91b 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -60,6 +60,7 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.WorkSource;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
@@ -3911,10 +3912,10 @@
     /**
      * @hide
      */
-    public static void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
-            String tag) {
+    public static void noteWakeupAlarm(PendingIntent ps, WorkSource workSource, int sourceUid,
+            String sourcePkg, String tag) {
         try {
-            getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null,
+            getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, workSource,
                     sourceUid, sourcePkg, tag);
         } catch (RemoteException ex) {
         }
@@ -3923,19 +3924,24 @@
     /**
      * @hide
      */
-    public static void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
+    public static void noteAlarmStart(PendingIntent ps, WorkSource workSource, int sourceUid,
+            String tag) {
         try {
-            getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag);
+            getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, workSource,
+                    sourceUid, tag);
         } catch (RemoteException ex) {
         }
     }
 
+
     /**
      * @hide
      */
-    public static void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
+    public static void noteAlarmFinish(PendingIntent ps, WorkSource workSource, int sourceUid,
+            String tag) {
         try {
-            getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag);
+            getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, workSource,
+                    sourceUid, tag);
         } catch (RemoteException ex) {
         }
     }
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index c09403c..4c558f3 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -75,20 +75,20 @@
      */
     static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
             String tag) {
-        ActivityManager.noteWakeupAlarm(ps, sourceUid, sourcePkg, tag);
+        ActivityManager.noteWakeupAlarm(ps, null, sourceUid, sourcePkg, tag);
     }
 
     /**
      * @deprecated use ActivityManager.noteAlarmStart instead.
      */
     static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
-        ActivityManager.noteAlarmStart(ps, sourceUid, tag);
+        ActivityManager.noteAlarmStart(ps, null, sourceUid, tag);
     }
 
     /**
      * @deprecated use ActivityManager.noteAlarmFinish instead.
      */
     static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
-        ActivityManager.noteAlarmFinish(ps, sourceUid, tag);
+        ActivityManager.noteAlarmFinish(ps, null, sourceUid, tag);
     }
 }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 4a21f5c..e61c5b7 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -36,6 +36,7 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.UserHandle;
 import android.transition.Transition;
 import android.transition.TransitionListenerAdapter;
 import android.transition.TransitionManager;
@@ -265,6 +266,8 @@
     public static final int ANIM_CUSTOM_IN_PLACE = 10;
     /** @hide */
     public static final int ANIM_CLIP_REVEAL = 11;
+    /** @hide */
+    public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12;
 
     private String mPackageName;
     private Rect mLaunchBounds;
@@ -486,6 +489,19 @@
     }
 
     /**
+     * Creates an {@link ActivityOptions} object specifying an animation where the new activity
+     * is started in another user profile by calling {@link
+     * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle)
+     * }.
+     * @hide
+     */
+    public static ActivityOptions makeOpenCrossProfileAppsAnimation() {
+        ActivityOptions options = new ActivityOptions();
+        options.mAnimationType = ANIM_OPEN_CROSS_PROFILE_APPS;
+        return options;
+    }
+
+    /**
      * Create an ActivityOptions specifying an animation where a thumbnail
      * is scaled from a given position to the new activity window that is
      * being started.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ea22d33..57f9f67 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -20,6 +20,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
 import android.media.AudioAttributes.AttributeUsage;
@@ -37,6 +38,7 @@
 import com.android.internal.app.IAppOpsService;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 
@@ -106,6 +108,7 @@
 
     // when adding one of these:
     //  - increment _NUM_OP
+    //  - define an OPSTR_* constant (marked as @SystemApi)
     //  - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault
     //  - add descriptive strings to Settings/res/values/arrays.xml
     //  - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
@@ -278,7 +281,7 @@
     public static final String OPSTR_GET_USAGE_STATS
             = "android:get_usage_stats";
     /** Activate a VPN connection without user intervention. @hide */
-    @SystemApi
+    @SystemApi @TestApi
     public static final String OPSTR_ACTIVATE_VPN
             = "android:activate_vpn";
     /** Allows an application to read the user's contacts data. */
@@ -360,6 +363,7 @@
     public static final String OPSTR_WRITE_SETTINGS
             = "android:write_settings";
     /** @hide Get device accounts. */
+    @SystemApi @TestApi
     public static final String OPSTR_GET_ACCOUNTS
             = "android:get_accounts";
     public static final String OPSTR_READ_PHONE_NUMBERS
@@ -368,12 +372,128 @@
     public static final String OPSTR_PICTURE_IN_PICTURE
             = "android:picture_in_picture";
     /** @hide */
+    @SystemApi @TestApi
     public static final String OPSTR_INSTANT_APP_START_FOREGROUND
             = "android:instant_app_start_foreground";
     /** Answer incoming phone calls */
     public static final String OPSTR_ANSWER_PHONE_CALLS
             = "android:answer_phone_calls";
 
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_GPS = "android:gps";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_VIBRATE = "android:vibrate";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WIFI_SCAN = "android:wifi_scan";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_POST_NOTIFICATION = "android:post_notification";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WRITE_SMS = "android:write_sms";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST =
+            "android:receive_emergency_broadcast";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_PLAY_AUDIO = "android:play_audio";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WRITE_CLIPBOARD = "android:write_clipboard";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_TAKE_MEDIA_BUTTONS = "android:take_media_buttons";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_TAKE_AUDIO_FOCUS = "android:take_audio_focus";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_MASTER_VOLUME = "android:audio_master_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_VOICE_VOLUME = "android:audio_voice_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_RING_VOLUME = "android:audio_ring_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_MEDIA_VOLUME = "android:audio_media_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_ALARM_VOLUME = "android:audio_alarm_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_NOTIFICATION_VOLUME =
+            "android:audio_notification_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_BLUETOOTH_VOLUME = "android:audio_bluetooth_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WAKE_LOCK = "android:wake_lock";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_TOAST_WINDOW = "android:toast_window";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_PROJECT_MEDIA = "android:project_media";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_ASSIST_STRUCTURE = "android:assist_structure";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_TURN_SCREEN_ON = "android:turn_screen_on";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_RUN_IN_BACKGROUND = "android:run_in_background";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_ACCESSIBILITY_VOLUME =
+            "android:audio_accessibility_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_CHANGE_WIFI_STATE = "change_wifi_state";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_REQUEST_DELETE_PACKAGES = "request_delete_packages";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = "bind_accessibility_service";
+
     // Warning: If an permission is added here it also has to be added to
     // com.android.packageinstaller.permission.utils.EventLogger
     private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
@@ -510,60 +630,59 @@
 
     /**
      * This maps each operation to the public string constant for it.
-     * If it doesn't have a public string constant, it maps to null.
      */
-    private static String[] sOpToString = new String[] {
+    private static String[] sOpToString = new String[]{
             OPSTR_COARSE_LOCATION,
             OPSTR_FINE_LOCATION,
-            null,
-            null,
+            OPSTR_GPS,
+            OPSTR_VIBRATE,
             OPSTR_READ_CONTACTS,
             OPSTR_WRITE_CONTACTS,
             OPSTR_READ_CALL_LOG,
             OPSTR_WRITE_CALL_LOG,
             OPSTR_READ_CALENDAR,
             OPSTR_WRITE_CALENDAR,
-            null,
-            null,
-            null,
+            OPSTR_WIFI_SCAN,
+            OPSTR_POST_NOTIFICATION,
+            OPSTR_NEIGHBORING_CELLS,
             OPSTR_CALL_PHONE,
             OPSTR_READ_SMS,
-            null,
+            OPSTR_WRITE_SMS,
             OPSTR_RECEIVE_SMS,
-            null,
+            OPSTR_RECEIVE_EMERGENCY_BROADCAST,
             OPSTR_RECEIVE_MMS,
             OPSTR_RECEIVE_WAP_PUSH,
             OPSTR_SEND_SMS,
-            null,
-            null,
+            OPSTR_READ_ICC_SMS,
+            OPSTR_WRITE_ICC_SMS,
             OPSTR_WRITE_SETTINGS,
             OPSTR_SYSTEM_ALERT_WINDOW,
-            null,
+            OPSTR_ACCESS_NOTIFICATIONS,
             OPSTR_CAMERA,
             OPSTR_RECORD_AUDIO,
-            null,
-            null,
-            null,
-            null,
-            null,
-            null,
-            null,
-            null,
-            null,
-            null,
-            null,
-            null,
-            null,
+            OPSTR_PLAY_AUDIO,
+            OPSTR_READ_CLIPBOARD,
+            OPSTR_WRITE_CLIPBOARD,
+            OPSTR_TAKE_MEDIA_BUTTONS,
+            OPSTR_TAKE_AUDIO_FOCUS,
+            OPSTR_AUDIO_MASTER_VOLUME,
+            OPSTR_AUDIO_VOICE_VOLUME,
+            OPSTR_AUDIO_RING_VOLUME,
+            OPSTR_AUDIO_MEDIA_VOLUME,
+            OPSTR_AUDIO_ALARM_VOLUME,
+            OPSTR_AUDIO_NOTIFICATION_VOLUME,
+            OPSTR_AUDIO_BLUETOOTH_VOLUME,
+            OPSTR_WAKE_LOCK,
             OPSTR_MONITOR_LOCATION,
             OPSTR_MONITOR_HIGH_POWER_LOCATION,
             OPSTR_GET_USAGE_STATS,
-            null,
-            null,
-            null,
+            OPSTR_MUTE_MICROPHONE,
+            OPSTR_TOAST_WINDOW,
+            OPSTR_PROJECT_MEDIA,
             OPSTR_ACTIVATE_VPN,
-            null,
-            null,
-            null,
+            OPSTR_WRITE_WALLPAPER,
+            OPSTR_ASSIST_STRUCTURE,
+            OPSTR_ASSIST_SCREENSHOT,
             OPSTR_READ_PHONE_STATE,
             OPSTR_ADD_VOICEMAIL,
             OPSTR_USE_SIP,
@@ -574,19 +693,19 @@
             OPSTR_MOCK_LOCATION,
             OPSTR_READ_EXTERNAL_STORAGE,
             OPSTR_WRITE_EXTERNAL_STORAGE,
-            null,
+            OPSTR_TURN_SCREEN_ON,
             OPSTR_GET_ACCOUNTS,
-            null,
-            null, // OP_AUDIO_ACCESSIBILITY_VOLUME
+            OPSTR_RUN_IN_BACKGROUND,
+            OPSTR_AUDIO_ACCESSIBILITY_VOLUME,
             OPSTR_READ_PHONE_NUMBERS,
-            null, // OP_REQUEST_INSTALL_PACKAGES
+            OPSTR_REQUEST_INSTALL_PACKAGES,
             OPSTR_PICTURE_IN_PICTURE,
             OPSTR_INSTANT_APP_START_FOREGROUND,
             OPSTR_ANSWER_PHONE_CALLS,
-            null, // OP_RUN_ANY_IN_BACKGROUND
-            null, // OP_CHANGE_WIFI_STATE
-            null, // OP_REQUEST_DELETE_PACKAGES
-            null, // OP_BIND_ACCESSIBILITY_SERVICE
+            OPSTR_RUN_ANY_IN_BACKGROUND,
+            OPSTR_CHANGE_WIFI_STATE,
+            OPSTR_REQUEST_DELETE_PACKAGES,
+            OPSTR_BIND_ACCESSIBILITY_SERVICE,
     };
 
     /**
@@ -1482,6 +1601,7 @@
     }
 
     /** @hide */
+    @TestApi
     public void setMode(int code, int uid, String packageName, int mode) {
         try {
             mService.setMode(code, uid, packageName, mode);
@@ -1997,4 +2117,14 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Returns all supported operation names.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static String[] getOpStrs() {
+        return Arrays.copyOf(sOpToString, sOpToString.length);
+    }
 }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 8641a21..82f2bac 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -64,7 +64,6 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
-import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -1683,22 +1682,6 @@
     }
 
     @Override
-    public void installPackage(Uri packageURI,
-            PackageInstallObserver observer, int flags, String installerPackageName) {
-        if (!"file".equals(packageURI.getScheme())) {
-            throw new UnsupportedOperationException("Only file:// URIs are supported");
-        }
-
-        final String originPath = packageURI.getPath();
-        try {
-            mPM.installPackageAsUser(originPath, observer.getBinder(), flags, installerPackageName,
-                    mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @Override
     public int installExistingPackage(String packageName) throws NameNotFoundException {
         return installExistingPackage(packageName, PackageManager.INSTALL_REASON_UNKNOWN);
     }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index a4e221a..a9e633f 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -63,6 +63,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.StrictMode;
+import android.os.WorkSource;
 import android.service.voice.IVoiceInteractionSession;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.os.IResultReceiver;
@@ -86,6 +87,9 @@
     // 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);
+    void registerUidObserver(in IUidObserver observer, int which, int cutpoint,
+            String callingPackage);
+    void unregisterUidObserver(in IUidObserver observer);
     // =============== End of transactions used on native side as well ============================
 
     // Special low-level communication with activity manager.
@@ -195,7 +199,7 @@
     void enterSafeMode();
     boolean startNextMatchingActivity(in IBinder callingActivity,
             in Intent intent, in Bundle options);
-    void noteWakeupAlarm(in IIntentSender sender, int sourceUid,
+    void noteWakeupAlarm(in IIntentSender sender, in WorkSource workSource, int sourceUid,
             in String sourcePkg, in String tag);
     void removeContentProvider(in IBinder connection, boolean stable);
     void setRequestedOrientation(in IBinder token, int requestedOrientation);
@@ -465,8 +469,8 @@
     void dumpHeapFinished(in String path);
     void setVoiceKeepAwake(in IVoiceInteractionSession session, boolean keepAwake);
     void updateLockTaskPackages(int userId, in String[] packages);
-    void noteAlarmStart(in IIntentSender sender, int sourceUid, in String tag);
-    void noteAlarmFinish(in IIntentSender sender, int sourceUid, in String tag);
+    void noteAlarmStart(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag);
+    void noteAlarmFinish(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag);
     int getPackageProcessState(in String packageName, in String callingPackage);
     oneway void showLockTaskEscapeMessage(in IBinder token);
     void updateDeviceOwner(in String packageName);
@@ -478,9 +482,6 @@
      */
     void keyguardGoingAway(int flags);
     int getUidProcessState(int uid, in String callingPackage);
-    void registerUidObserver(in IUidObserver observer, int which, int cutpoint,
-            String callingPackage);
-    void unregisterUidObserver(in IUidObserver observer);
     boolean isAssistDataAllowedOnCurrentActivity();
     boolean showAssistFromActivity(in IBinder token, in Bundle args);
     boolean isRootVoiceInteraction(in IBinder token);
@@ -626,9 +627,6 @@
     /** Cancels the window transitions for the given task. */
     void cancelTaskWindowTransition(int taskId);
 
-    /** Cancels the thumbnail transitions for the given task. */
-    void cancelTaskThumbnailTransition(int taskId);
-
     /**
      * @param taskId the id of the task to retrieve the sAutoapshots for
      * @param reducedResolution if set, if the snapshot needs to be loaded from disk, this will load
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index 01a9cbf..ce88809 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -18,15 +18,14 @@
 
 /** {@hide} */
 oneway interface IUidObserver {
-    /**
-     * General report of a state change of an uid.
-     *
-     * @param uid The uid for which the state change is being reported.
-     * @param procState The updated process state for the uid.
-     * @param procStateSeq The sequence no. associated with process state change of the uid,
-     *                     see UidRecord.procStateSeq for details.
-     */
-    void onUidStateChanged(int uid, int procState, long procStateSeq);
+    // 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.
+    // If a transaction which will also be used on the native side is being inserted, add it to
+    // 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
+    // =============== Beginning of transactions used on native side as well ======================
 
     /**
      * Report that there are no longer any processes running for a uid.
@@ -44,6 +43,18 @@
      */
     void onUidIdle(int uid, boolean disabled);
 
+    // =============== End of transactions used on native side as well ============================
+
+    /**
+     * General report of a state change of an uid.
+     *
+     * @param uid The uid for which the state change is being reported.
+     * @param procState The updated process state for the uid.
+     * @param procStateSeq The sequence no. associated with process state change of the uid,
+     *                     see UidRecord.procStateSeq for details.
+     */
+    void onUidStateChanged(int uid, int procState, long procStateSeq);
+
     /**
      * Report when the cached state of a uid has changed.
      * If true, a uid has become cached -- that is, it has some active processes that are
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 80782e3..5ef4be1 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -159,5 +159,5 @@
     /**
      * Called from SystemUI when it shows the AoD UI.
      */
-    void setInAmbientMode(boolean inAmbienMode);
+    oneway void setInAmbientMode(boolean inAmbientMode, boolean animated);
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 41324d0..b469de5 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1209,6 +1209,11 @@
     }
 
     private AppComponentFactory getFactory(String pkg) {
+        if (mThread == null) {
+            Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation,"
+                    + " disabling AppComponentFactory", new Throwable());
+            return AppComponentFactory.DEFAULT;
+        }
         LoadedApk loadedApk = mThread.peekLoadedApk(pkg, true);
         // This is in the case of starting up "android".
         if (loadedApk == null) loadedApk = mThread.getSystemContext().mLoadedApk;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 85c3be8..046a128 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5994,8 +5994,15 @@
         /**
          * Sets the title to be displayed on this conversation. May be set to {@code null}.
          *
-         * @param conversationTitle A name for the conversation, or {@code null}
-         * @return this object for method chaining.
+         * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
+         * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
+         * conversation title to a non-null value will make {@link #isGroupConversation()} return
+         * {@code true} and passing {@code null} will make it return {@code false}. In
+         * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
+         * to set group conversation status.
+         *
+         * @param conversationTitle Title displayed for this conversation
+         * @return this object for method chaining
          */
         public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
             mConversationTitle = conversationTitle;
@@ -6083,6 +6090,7 @@
 
         /**
          * Sets whether this conversation notification represents a group.
+         *
          * @param isGroupConversation {@code true} if the conversation represents a group,
          * {@code false} otherwise.
          * @return this object for method chaining
@@ -6093,9 +6101,27 @@
         }
 
         /**
-         * Returns {@code true} if this notification represents a group conversation.
+         * Returns {@code true} if this notification represents a group conversation, otherwise
+         * {@code false}.
+         *
+         * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
+         * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
+         * not the conversation title is set; returning {@code true} if the conversation title is
+         * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
+         * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
+         * named, non-group conversations.
+         *
+         * @see #setConversationTitle(CharSequence)
          */
         public boolean isGroupConversation() {
+            // When target SDK version is < P, a non-null conversation title dictates if this is
+            // as group conversation.
+            if (mBuilder != null
+                    && mBuilder.mContext.getApplicationInfo().targetSdkVersion
+                            < Build.VERSION_CODES.P) {
+                return mConversationTitle != null;
+            }
+
             return mIsGroupConversation;
         }
 
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 6dca400..8c47598 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -50,11 +50,6 @@
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 final class SharedPreferencesImpl implements SharedPreferences {
     private static final String TAG = "SharedPreferencesImpl";
@@ -74,12 +69,16 @@
     private final Object mLock = new Object();
     private final Object mWritingToDiskLock = new Object();
 
-    private Future<Map<String, Object>> mMap;
+    @GuardedBy("mLock")
+    private Map<String, Object> mMap;
 
     @GuardedBy("mLock")
     private int mDiskWritesInFlight = 0;
 
     @GuardedBy("mLock")
+    private boolean mLoaded = false;
+
+    @GuardedBy("mLock")
     private StructTimespec mStatTimestamp;
 
     @GuardedBy("mLock")
@@ -106,18 +105,27 @@
         mFile = file;
         mBackupFile = makeBackupFile(file);
         mMode = mode;
+        mLoaded = false;
         mMap = null;
         startLoadFromDisk();
     }
 
     private void startLoadFromDisk() {
-        FutureTask<Map<String, Object>> futureTask = new FutureTask<>(() -> loadFromDisk());
-        mMap = futureTask;
-        new Thread(futureTask, "SharedPreferencesImpl-load").start();
+        synchronized (mLock) {
+            mLoaded = false;
+        }
+        new Thread("SharedPreferencesImpl-load") {
+            public void run() {
+                loadFromDisk();
+            }
+        }.start();
     }
 
-    private Map<String, Object> loadFromDisk() {
+    private void loadFromDisk() {
         synchronized (mLock) {
+            if (mLoaded) {
+                return;
+            }
             if (mBackupFile.exists()) {
                 mFile.delete();
                 mBackupFile.renameTo(mFile);
@@ -150,14 +158,16 @@
         }
 
         synchronized (mLock) {
+            mLoaded = true;
             if (map != null) {
+                mMap = map;
                 mStatTimestamp = stat.st_mtim;
                 mStatSize = stat.st_size;
             } else {
-                map = new HashMap<>();
+                mMap = new HashMap<>();
             }
+            mLock.notifyAll();
         }
-        return map;
     }
 
     static File makeBackupFile(File prefsFile) {
@@ -216,42 +226,36 @@
         }
     }
 
-    private @GuardedBy("mLock") Map<String, Object> getLoaded() {
-        // For backwards compatibility, we need to ignore any interrupts. b/70122540.
-        for (;;) {
-            try {
-                return mMap.get();
-            } catch (ExecutionException e) {
-                throw new IllegalStateException(e);
-            } catch (InterruptedException e) {
-                // Ignore and try again.
-            }
-        }
-    }
-    private @GuardedBy("mLock") Map<String, Object> getLoadedWithBlockGuard() {
-        if (!mMap.isDone()) {
+    private void awaitLoadedLocked() {
+        if (!mLoaded) {
             // Raise an explicit StrictMode onReadFromDisk for this
             // thread, since the real read will be in a different
             // thread and otherwise ignored by StrictMode.
             BlockGuard.getThreadPolicy().onReadFromDisk();
         }
-        return getLoaded();
+        while (!mLoaded) {
+            try {
+                mLock.wait();
+            } catch (InterruptedException unused) {
+            }
+        }
     }
 
     @Override
     public Map<String, ?> getAll() {
-        Map<String, Object> map = getLoadedWithBlockGuard();
         synchronized (mLock) {
-            return new HashMap<String, Object>(map);
+            awaitLoadedLocked();
+            //noinspection unchecked
+            return new HashMap<String, Object>(mMap);
         }
     }
 
     @Override
     @Nullable
     public String getString(String key, @Nullable String defValue) {
-        Map<String, Object> map = getLoadedWithBlockGuard();
         synchronized (mLock) {
-            String v = (String) map.get(key);
+            awaitLoadedLocked();
+            String v = (String)mMap.get(key);
             return v != null ? v : defValue;
         }
     }
@@ -259,65 +263,66 @@
     @Override
     @Nullable
     public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
-        Map<String, Object> map = getLoadedWithBlockGuard();
         synchronized (mLock) {
-            @SuppressWarnings("unchecked")
-            Set<String> v = (Set<String>) map.get(key);
+            awaitLoadedLocked();
+            Set<String> v = (Set<String>) mMap.get(key);
             return v != null ? v : defValues;
         }
     }
 
     @Override
     public int getInt(String key, int defValue) {
-        Map<String, Object> map = getLoadedWithBlockGuard();
         synchronized (mLock) {
-            Integer v = (Integer) map.get(key);
+            awaitLoadedLocked();
+            Integer v = (Integer)mMap.get(key);
             return v != null ? v : defValue;
         }
     }
     @Override
     public long getLong(String key, long defValue) {
-        Map<String, Object> map = getLoadedWithBlockGuard();
         synchronized (mLock) {
-            Long v = (Long) map.get(key);
+            awaitLoadedLocked();
+            Long v = (Long)mMap.get(key);
             return v != null ? v : defValue;
         }
     }
     @Override
     public float getFloat(String key, float defValue) {
-        Map<String, Object> map = getLoadedWithBlockGuard();
         synchronized (mLock) {
-            Float v = (Float) map.get(key);
+            awaitLoadedLocked();
+            Float v = (Float)mMap.get(key);
             return v != null ? v : defValue;
         }
     }
     @Override
     public boolean getBoolean(String key, boolean defValue) {
-        Map<String, Object> map = getLoadedWithBlockGuard();
         synchronized (mLock) {
-            Boolean v = (Boolean) map.get(key);
+            awaitLoadedLocked();
+            Boolean v = (Boolean)mMap.get(key);
             return v != null ? v : defValue;
         }
     }
 
     @Override
     public boolean contains(String key) {
-        Map<String, Object> map = getLoadedWithBlockGuard();
         synchronized (mLock) {
-            return map.containsKey(key);
+            awaitLoadedLocked();
+            return mMap.containsKey(key);
         }
     }
 
     @Override
     public Editor edit() {
-        // TODO: remove the need to call getLoaded() when
+        // TODO: remove the need to call awaitLoadedLocked() when
         // requesting an editor.  will require some work on the
         // Editor, but then we should be able to do:
         //
         //      context.getSharedPreferences(..).edit().putString(..).apply()
         //
         // ... all without blocking.
-        getLoadedWithBlockGuard();
+        synchronized (mLock) {
+            awaitLoadedLocked();
+        }
 
         return new EditorImpl();
     }
@@ -471,43 +476,13 @@
                 // a memory commit comes in when we're already
                 // writing to disk.
                 if (mDiskWritesInFlight > 0) {
-                    // We can't modify our map as a currently
+                    // We can't modify our mMap as a currently
                     // in-flight write owns it.  Clone it before
                     // modifying it.
                     // noinspection unchecked
-                    mMap = new Future<Map<String, Object>>() {
-                        private Map<String, Object> mCopiedMap =
-                                new HashMap<String, Object>(getLoaded());
-
-                        @Override
-                        public boolean cancel(boolean mayInterruptIfRunning) {
-                            return false;
-                        }
-
-                        @Override
-                        public boolean isCancelled() {
-                            return false;
-                        }
-
-                        @Override
-                        public boolean isDone() {
-                            return true;
-                        }
-
-                        @Override
-                        public Map<String, Object> get()
-                                throws InterruptedException, ExecutionException {
-                            return mCopiedMap;
-                        }
-
-                        @Override
-                        public Map<String, Object> get(long timeout, TimeUnit unit)
-                                throws InterruptedException, ExecutionException, TimeoutException {
-                            return mCopiedMap;
-                        }
-                    };
+                    mMap = new HashMap<String, Object>(mMap);
                 }
-                mapToWriteToDisk = getLoaded();
+                mapToWriteToDisk = mMap;
                 mDiskWritesInFlight++;
 
                 boolean hasListeners = mListeners.size() > 0;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 495fd3c..6eb4783 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -494,7 +494,7 @@
         registerService(Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class,
                 new CachedServiceFetcher<SubscriptionManager>() {
             @Override
-            public SubscriptionManager createService(ContextImpl ctx) {
+            public SubscriptionManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                 return new SubscriptionManager(ctx.getOuterContext());
             }});
 
@@ -552,8 +552,16 @@
         registerService(Context.WALLPAPER_SERVICE, WallpaperManager.class,
                 new CachedServiceFetcher<WallpaperManager>() {
             @Override
-            public WallpaperManager createService(ContextImpl ctx) {
-                return new WallpaperManager(ctx.getOuterContext(),
+            public WallpaperManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
+                final IBinder b;
+                if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+                    b = ServiceManager.getServiceOrThrow(Context.WALLPAPER_SERVICE);
+                } else {
+                    b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
+                }
+                IWallpaperManager service = IWallpaperManager.Stub.asInterface(b);
+                return new WallpaperManager(service, ctx.getOuterContext(),
                         ctx.mMainThread.getHandler());
             }});
 
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 3829afb..f21746c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -286,9 +286,8 @@
         private Bitmap mDefaultWallpaper;
         private Handler mMainLooperHandler;
 
-        Globals(Looper looper) {
-            IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
-            mService = IWallpaperManager.Stub.asInterface(b);
+        Globals(IWallpaperManager service, Looper looper) {
+            mService = service;
             mMainLooperHandler = new Handler(looper);
             forgetLoadedWallpaper();
         }
@@ -497,17 +496,17 @@
     private static final Object sSync = new Object[0];
     private static Globals sGlobals;
 
-    static void initGlobals(Looper looper) {
+    static void initGlobals(IWallpaperManager service, Looper looper) {
         synchronized (sSync) {
             if (sGlobals == null) {
-                sGlobals = new Globals(looper);
+                sGlobals = new Globals(service, looper);
             }
         }
     }
 
-    /*package*/ WallpaperManager(Context context, Handler handler) {
+    /*package*/ WallpaperManager(IWallpaperManager service, Context context, Handler handler) {
         mContext = context;
-        initGlobals(context.getMainLooper());
+        initGlobals(service, context.getMainLooper());
     }
 
     /**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 70e1a96..76c078e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1668,6 +1668,46 @@
     public static final String ACTION_DEVICE_ADMIN_SERVICE
             = "android.app.action.DEVICE_ADMIN_SERVICE";
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = {"ID_TYPE_"}, value = {
+        ID_TYPE_BASE_INFO,
+        ID_TYPE_SERIAL,
+        ID_TYPE_IMEI,
+        ID_TYPE_MEID
+    })
+    public @interface AttestationIdType {}
+
+    /**
+     * Specifies that the device should attest its manufacturer details. For use with
+     * {@link #generateKeyPair}.
+     *
+     * @see #generateKeyPair
+     */
+    public static final int ID_TYPE_BASE_INFO = 1;
+
+    /**
+     * Specifies that the device should attest its serial number. For use with
+     * {@link #generateKeyPair}.
+     *
+     * @see #generateKeyPair
+     */
+    public static final int ID_TYPE_SERIAL = 2;
+
+    /**
+     * Specifies that the device should attest its IMEI. For use with {@link #generateKeyPair}.
+     *
+     * @see #generateKeyPair
+     */
+    public static final int ID_TYPE_IMEI = 4;
+
+    /**
+     * Specifies that the device should attest its MEID. For use with {@link #generateKeyPair}.
+     *
+     * @see #generateKeyPair
+     */
+    public static final int ID_TYPE_MEID = 8;
+
     /**
      * Return true if the given administrator component is currently active (enabled) in the system.
      *
@@ -2690,9 +2730,6 @@
      * @see #getPasswordBlacklistName
      * @see #isActivePasswordSufficient
      * @see #resetPasswordWithToken
-     *
-     * TODO(63578054): unhide for P
-     * @hide
      */
     public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name,
             @Nullable List<String> blacklist) {
@@ -2712,9 +2749,6 @@
      * @return the name of the blacklist or {@code null} if no blacklist is set
      *
      * @see #setPasswordBlacklist
-     *
-     * TODO(63578054): unhide for P
-     * @hide
      */
     public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) {
         try {
@@ -4112,22 +4146,46 @@
      * @param algorithm The key generation algorithm, see {@link java.security.KeyPairGenerator}.
      * @param keySpec Specification of the key to generate, see
      * {@link java.security.KeyPairGenerator}.
+     * @param idAttestationFlags A bitmask of all the identifiers that should be included in the
+     *        attestation record ({@code ID_TYPE_BASE_INFO}, {@code ID_TYPE_SERIAL},
+     *        {@code ID_TYPE_IMEI} and {@code ID_TYPE_MEID}), or {@code 0} if no device
+     *        identification is required in the attestation record.
+     *        Device owner, profile owner and their delegated certificate installer can use
+     *        {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information
+     *        including manufacturer, model, brand, device and product in the attestation record.
+     *        Only device owner and their delegated certificate installer can use
+     *        {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} to request
+     *        unique device identifiers to be attested.
+     *        <p>
+     *        If any of {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID}
+     *        is set, it is implicitly assumed that {@link #ID_TYPE_BASE_INFO} is also set.
+     *        <p>
+     *        If any flag is specified, then an attestation challenge must be included in the
+     *        {@code keySpec}.
      * @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise.
      * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
-     *         owner.
-     * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, or if the
+     *         owner. If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL},
+     *         {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner
+     *         or the Certificate Installer delegate.
+     * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, if the
      *         algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec}
-     *         or {@code ECGenParameterSpec}.
+     *         or {@code ECGenParameterSpec}, or if Device ID attestation was requested but the
+     *         {@code keySpec} does not contain an attestation challenge.
+     * @see KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])
      */
     public AttestedKeyPair generateKeyPair(@Nullable ComponentName admin,
-            @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec) {
+            @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec,
+            @AttestationIdType int idAttestationFlags) {
         throwIfParentInstance("generateKeyPair");
         try {
             final ParcelableKeyGenParameterSpec parcelableSpec =
                     new ParcelableKeyGenParameterSpec(keySpec);
             KeymasterCertificateChain attestationChain = new KeymasterCertificateChain();
+
+            // Translate ID attestation flags to values used by AttestationUtils
             final boolean success = mService.generateKeyPair(
-                    admin, mContext.getPackageName(), algorithm, parcelableSpec, attestationChain);
+                    admin, mContext.getPackageName(), algorithm, parcelableSpec,
+                    idAttestationFlags, attestationChain);
             if (!success) {
                 Log.e(TAG, "Error generating key via DevicePolicyManagerService.");
                 return null;
@@ -7460,7 +7518,8 @@
     }
 
     /**
-     * Called by a device owner to disable the keyguard altogether.
+     * Called by a device owner or profile owner of secondary users that is affiliated with the
+     * device to disable the keyguard altogether.
      * <p>
      * Setting the keyguard to disabled has the same effect as choosing "None" as the screen lock
      * type. However, this call has no effect if a password, pin or pattern is currently set. If a
@@ -7475,7 +7534,10 @@
      * @param disabled {@code true} disables the keyguard, {@code false} reenables it.
      * @return {@code false} if attempting to disable the keyguard while a lock password was in
      *         place. {@code true} otherwise.
-     * @throws SecurityException if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not the device owner, or a profile owner of
+     * secondary user that is affiliated with the device.
+     * @see #isAffiliatedUser
+     * @see #getSecondaryUsers
      */
     public boolean setKeyguardDisabled(@NonNull ComponentName admin, boolean disabled) {
         throwIfParentInstance("setKeyguardDisabled");
@@ -7487,9 +7549,9 @@
     }
 
     /**
-     * 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.
+     * Called by device owner or profile owner of secondary users  that is affiliated with the
+     * device 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
@@ -7500,7 +7562,10 @@
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param disabled {@code true} disables the status bar, {@code false} reenables it.
      * @return {@code false} if attempting to disable the status bar failed. {@code true} otherwise.
-     * @throws SecurityException if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not the device owner, or a profile owner of
+     * secondary user that is affiliated with the device.
+     * @see #isAffiliatedUser
+     * @see #getSecondaryUsers
      */
     public boolean setStatusBarDisabled(@NonNull ComponentName admin, boolean disabled) {
         throwIfParentInstance("setStatusBarDisabled");
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7cf19ee..5916a62 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -173,7 +173,7 @@
     boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
     boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm,
             in ParcelableKeyGenParameterSpec keySpec,
-            out KeymasterCertificateChain attestationChain);
+            in int idAttestationFlags, out KeymasterCertificateChain attestationChain);
     boolean setKeyPairCertificate(in ComponentName who, in String callerPackage, in String alias,
             in byte[] certBuffer, in byte[] certChainBuffer, boolean isUserSelectable);
     void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 7b549cd5..87f2271 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -32,6 +32,8 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -624,6 +626,7 @@
         int mMinEms = -1;
         int mMaxEms = -1;
         int mMaxLength = -1;
+        @Nullable String mTextIdEntry;
 
         // POJO used to override some autofill-related values when the node is parcelized.
         // Not written to parcel.
@@ -701,7 +704,7 @@
             final int flags = mFlags;
             if ((flags&FLAGS_HAS_ID) != 0) {
                 mId = in.readInt();
-                if (mId != 0) {
+                if (mId != View.NO_ID) {
                     mIdEntry = preader.readString();
                     if (mIdEntry != null) {
                         mIdType = preader.readString();
@@ -724,6 +727,7 @@
                 mMinEms = in.readInt();
                 mMaxEms = in.readInt();
                 mMaxLength = in.readInt();
+                mTextIdEntry = preader.readString();
             }
             if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
                 mX = in.readInt();
@@ -857,7 +861,7 @@
             out.writeInt(writtenFlags);
             if ((flags&FLAGS_HAS_ID) != 0) {
                 out.writeInt(mId);
-                if (mId != 0) {
+                if (mId != View.NO_ID) {
                     pwriter.writeString(mIdEntry);
                     if (mIdEntry != null) {
                         pwriter.writeString(mIdType);
@@ -890,6 +894,7 @@
                 out.writeInt(mMinEms);
                 out.writeInt(mMaxEms);
                 out.writeInt(mMaxLength);
+                pwriter.writeString(mTextIdEntry);
             }
             if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
                 out.writeInt(mX);
@@ -1430,6 +1435,17 @@
         }
 
         /**
+         * Gets the identifier used to set the text associated with this view.
+         *
+         * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+         * not for assist purposes.
+         */
+        @Nullable
+        public String getTextIdEntry() {
+            return mTextIdEntry;
+        }
+
+        /**
          * Return additional hint text associated with the node; this is typically used with
          * a node that takes user input, describing to the user what the input means.
          */
@@ -1684,6 +1700,11 @@
         }
 
         @Override
+        public void setTextIdEntry(@NonNull String entryName) {
+            mNode.mTextIdEntry = Preconditions.checkNotNull(entryName);
+        }
+
+        @Override
         public void setHint(CharSequence hint) {
             getNodeText().mHint = hint != null ? hint.toString() : null;
         }
@@ -2082,6 +2103,7 @@
             Log.i(TAG, prefix + "  Text color fg: #" + Integer.toHexString(node.getTextColor())
                     + ", bg: #" + Integer.toHexString(node.getTextBackgroundColor()));
             Log.i(TAG, prefix + "  Input type: " + node.getInputType());
+            Log.i(TAG, prefix + "  Resource id: " + node.getTextIdEntry());
         }
         String webDomain = node.getWebDomain();
         if (webDomain != null) {
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 6512b98..3a6a5b2 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -707,7 +707,6 @@
      * redirects them into main-thread actions.  This serializes the backup
      * progress callbacks nicely within the usual main-thread lifecycle pattern.
      */
-    @SystemApi
     private class BackupObserverWrapper extends IBackupObserver.Stub {
         final Handler mHandler;
         final BackupObserver mObserver;
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index c42a898..792cb5f 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -402,6 +402,9 @@
     /**
      * Ask the framework whether this app is eligible for backup.
      *
+     * <p>If you are calling this method multiple times, you should instead use
+     * {@link #filterAppsEligibleForBackup(String[])} to save resources.
+     *
      * <p>Callers must hold the android.permission.BACKUP permission to use this method.
      *
      * @param packageName The name of the package.
@@ -410,6 +413,16 @@
     boolean isAppEligibleForBackup(String packageName);
 
     /**
+     * Filter the packages that are eligible for backup and return the result.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @param packages The list of packages to filter.
+     * @return The packages eligible for backup.
+     */
+    String[] filterAppsEligibleForBackup(in String[] packages);
+
+    /**
      * Request an immediate backup, providing an observer to which results of the backup operation
      * will be published. The Android backup system will decide for each package whether it will
      * be full app data backup or key/value-pair-based backup.
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index 5c7f674..b8fb2e3 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -184,6 +184,10 @@
      * Subtype to tag an item representing priority.
      */
     public static final String SUBTYPE_PRIORITY = "priority";
+    /**
+     * Subtype to tag an item to use as a content description.
+     */
+    public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description";
 
     private final SliceItem[] mItems;
     private final @SliceHint String[] mHints;
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 4b4fe72..93e14de 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.app.usage;
 
+import android.annotation.UserIdInt;
 import android.app.usage.UsageStatsManager.StandbyBuckets;
 import android.content.ComponentName;
 import android.content.res.Configuration;
@@ -37,7 +38,7 @@
      * @param eventType The event that occurred. Valid values can be found at
      * {@link UsageEvents}
      */
-    public abstract void reportEvent(ComponentName component, int userId, int eventType);
+    public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType);
 
     /**
      * Reports an event to the UsageStatsManager.
@@ -47,14 +48,14 @@
      * @param eventType The event that occurred. Valid values can be found at
      * {@link UsageEvents}
      */
-    public abstract void reportEvent(String packageName, int userId, int eventType);
+    public abstract void reportEvent(String packageName, @UserIdInt int userId, int eventType);
 
     /**
      * Reports a configuration change to the UsageStatsManager.
      *
      * @param config The new device configuration.
      */
-    public abstract void reportConfigurationChange(Configuration config, int userId);
+    public abstract void reportConfigurationChange(Configuration config, @UserIdInt int userId);
 
     /**
      * Reports that an action equivalent to a ShortcutInfo is taken by the user.
@@ -65,7 +66,8 @@
      *
      * @see android.content.pm.ShortcutManager#reportShortcutUsed(String)
      */
-    public abstract void reportShortcutUsage(String packageName, String shortcutId, int userId);
+    public abstract void reportShortcutUsage(String packageName, String shortcutId,
+            @UserIdInt int userId);
 
     /**
      * Reports that a content provider has been accessed by a foreground app.
@@ -73,7 +75,8 @@
      * @param pkgName The package name of the content provider
      * @param userId The user in which the content provider was accessed.
      */
-    public abstract void reportContentProviderUsage(String name, String pkgName, int userId);
+    public abstract void reportContentProviderUsage(String name, String pkgName,
+            @UserIdInt int userId);
 
     /**
      * Prepares the UsageStatsService for shutdown.
@@ -89,7 +92,7 @@
      * @param userId
      * @return
      */
-    public abstract boolean isAppIdle(String packageName, int uidForAppId, int userId);
+    public abstract boolean isAppIdle(String packageName, int uidForAppId, @UserIdInt int userId);
 
     /**
      * Returns the app standby bucket that the app is currently in.  This accessor does
@@ -101,15 +104,15 @@
      * @return the AppStandby bucket code the app currently resides in.  If the app is
      *     unknown in the given user, STANDBY_BUCKET_NEVER is returned.
      */
-    @StandbyBuckets public abstract int getAppStandbyBucket(String packageName, int userId,
-            long nowElapsed);
+    @StandbyBuckets public abstract int getAppStandbyBucket(String packageName,
+            @UserIdInt int userId, long nowElapsed);
 
     /**
      * Returns all of the uids for a given user where all packages associating with that uid
      * are in the app idle state -- there are no associated apps that are not idle.  This means
      * all of the returned uids can be safely considered app idle.
      */
-    public abstract int[] getIdleUidsForUser(int userId);
+    public abstract int[] getIdleUidsForUser(@UserIdInt int userId);
 
     /**
      * @return True if currently app idle parole mode is on.  This means all idle apps are allow to
@@ -134,8 +137,8 @@
     public static abstract class AppIdleStateChangeListener {
 
         /** Callback to inform listeners that the idle state has changed to a new bucket. */
-        public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle,
-                                                   int bucket);
+        public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
+                boolean idle, int bucket);
 
         /**
          * Callback to inform listeners that the parole state has changed. This means apps are
@@ -144,10 +147,16 @@
         public abstract void onParoleStateChanged(boolean isParoleOn);
     }
 
-    /*  Backup/Restore API */
-    public abstract byte[] getBackupPayload(int user, String key);
+    /**  Backup/Restore API */
+    public abstract byte[] getBackupPayload(@UserIdInt int userId, String key);
 
-    public abstract void applyRestoredPayload(int user, String key, byte[] payload);
+    /**
+     * ?
+     * @param userId
+     * @param key
+     * @param payload
+     */
+    public abstract void applyRestoredPayload(@UserIdInt int userId, String key, byte[] payload);
 
     /**
      * Return usage stats.
@@ -155,6 +164,29 @@
      * @param obfuscateInstantApps whether instant app package names need to be obfuscated in the
      *     result.
      */
-    public abstract List<UsageStats> queryUsageStatsForUser(
-            int userId, int interval, long beginTime, long endTime, boolean obfuscateInstantApps);
+    public abstract List<UsageStats> queryUsageStatsForUser(@UserIdInt int userId, int interval,
+            long beginTime, long endTime, boolean obfuscateInstantApps);
+
+    /**
+     * Used to persist the last time a job was run for this app, in order to make decisions later
+     * whether a job should be deferred until later. The time passed in should be in elapsed
+     * realtime since boot.
+     * @param packageName the app that executed a job.
+     * @param userId the user associated with the job.
+     * @param elapsedRealtime the time when the job was executed, in elapsed realtime millis since
+     *                        boot.
+     */
+    public abstract void setLastJobRunTime(String packageName, @UserIdInt int userId,
+            long elapsedRealtime);
+
+    /**
+     * Returns the time in millis since a job was executed for this app, in elapsed realtime
+     * timebase. This value can be larger than the current elapsed realtime if the job was executed
+     * before the device was rebooted. The default value is {@link Long#MAX_VALUE}.
+     * @param packageName the app you're asking about.
+     * @param userId the user associated with the job.
+     * @return the time in millis since a job was last executed for the app, provided it was
+     * indicated here before by a call to {@link #setLastJobRunTime(String, int, long)}.
+     */
+    public abstract long getTimeSinceLastJobRun(String packageName, @UserIdInt int userId);
 }
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 7841b83..35a21a4 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -17,6 +17,7 @@
 package android.bluetooth;
 
 import android.Manifest;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -103,6 +104,24 @@
             "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED";
 
     /**
+     * Intent used to broadcast the selection of a connected device as active.
+     *
+     * <p>This intent will have one extra:
+     * <ul>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
+     * be null if no device is active. </li>
+     * </ul>
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ACTIVE_DEVICE_CHANGED =
+            "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED";
+
+    /**
      * Intent used to broadcast the change in the Audio Codec state of the
      * A2DP Source profile.
      *
@@ -425,6 +444,75 @@
     }
 
     /**
+     * Select a connected device as active.
+     *
+     * The active device selection is per profile. An active device's
+     * purpose is profile-specific. For example, A2DP audio streaming
+     * is to the active A2DP Sink device. If a remote device is not
+     * connected, it cannot be selected as active.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is not connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that the
+     * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
+     * with the active device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device the remote Bluetooth device. Could be null to clear
+     * the active device and stop streaming audio to a Bluetooth device.
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean setActiveDevice(@Nullable BluetoothDevice device) {
+        if (DBG) log("setActiveDevice(" + device + ")");
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null && isEnabled()
+                    && ((device == null) || isValidDevice(device))) {
+                return mService.setActiveDevice(device);
+            }
+            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Get the connected device that is active.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     * permission.
+     *
+     * @return the connected device that is active or null if no device
+     * is active
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @Nullable
+    public BluetoothDevice getActiveDevice() {
+        if (VDBG) log("getActiveDevice()");
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null && isEnabled()) {
+                return mService.getActiveDevice();
+            }
+            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            return null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return null;
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+    }
+
+    /**
      * Set priority of the profile
      *
      * <p> The device should already be paired.
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 838d315..c94540a 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -16,6 +16,7 @@
 
 package android.bluetooth;
 
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -93,6 +94,23 @@
     public static final String ACTION_AUDIO_STATE_CHANGED =
             "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
 
+    /**
+     * Intent used to broadcast the selection of a connected device as active.
+     *
+     * <p>This intent will have one extra:
+     * <ul>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
+     * be null if no device is active. </li>
+     * </ul>
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ACTIVE_DEVICE_CHANGED =
+            "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
 
     /**
      * Intent used to broadcast that the headset has posted a
@@ -983,9 +1001,105 @@
     }
 
     /**
-     * check if in-band ringing is supported for this platform.
+     * Select a connected device as active.
      *
-     * @return true if in-band ringing is supported false if in-band ringing is not supported
+     * The active device selection is per profile. An active device's
+     * purpose is profile-specific. For example, in HFP and HSP profiles,
+     * it is the device used for phone call audio. If a remote device is not
+     * connected, it cannot be selected as active.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is not connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that the
+     * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
+     * with the active device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device Remote Bluetooth Device, could be null if phone call audio should not be
+     * streamed to a headset
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setActiveDevice(@Nullable BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "setActiveDevice: " + device);
+        }
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
+            try {
+                return service.setActiveDevice(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return false;
+    }
+
+    /**
+     * Get the connected device that is active.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     * permission.
+     *
+     * @return the connected device that is active or null if no device
+     * is active.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
+    public BluetoothDevice getActiveDevice() {
+        if (VDBG) {
+            Log.d(TAG, "getActiveDevice");
+        }
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.getActiveDevice();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return null;
+    }
+
+    /**
+     * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
+     * active connection.
+     *
+     * @return true if in-band ringing is enabled, false if in-band ringing is disabled
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
+    public boolean isInbandRingingEnabled() {
+        if (DBG) {
+            log("isInbandRingingEnabled()");
+        }
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.isInbandRingingEnabled();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return false;
+    }
+
+    /**
+     * Check if in-band ringing is supported for this platform.
+     *
+     * @return true if in-band ringing is supported, false if in-band ringing is not supported
      * @hide
      */
     public static boolean isInbandRingingSupported(Context context) {
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index df2028a..41cf809 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -157,12 +157,19 @@
     public static final int HID_DEVICE = 19;
 
     /**
+     * Object Push Profile (OPP)
+     *
+     * @hide
+     */
+    public static final int OPP = 20;
+
+    /**
      * Max profile ID. This value should be updated whenever a new profile is added to match
      * the largest value assigned to a profile.
      *
      * @hide
      */
-    public static final int MAX_PROFILE_ID = 19;
+    public static final int MAX_PROFILE_ID = 20;
 
     /**
      * Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index a189e27..347fc4d 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -387,7 +387,7 @@
                 if (mScannerId > 0) {
                     mLeScanClients.put(mScanCallback, this);
                 } else {
-                    // Registration timed out or got exception, reset scannerId to -1 so no
+                    // Registration timed out or got exception, reset RscannerId to -1 so no
                     // subsequent operations can proceed.
                     if (mScannerId == 0) mScannerId = -1;
 
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 9323261..94e1e2d 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -34,16 +34,18 @@
 import android.text.TextUtils;
 import android.text.style.URLSpan;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.ArrayUtils;
 
+import libcore.io.IoUtils;
+
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.List;
-import libcore.io.IoUtils;
 
 /**
  * Representation of a clipped data on the clipboard.
@@ -665,6 +667,25 @@
                 b.append("NULL");
             }
         }
+
+        /** @hide */
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            if (mHtmlText != null) {
+                proto.write(ClipDataProto.Item.HTML_TEXT, mHtmlText);
+            } else if (mText != null) {
+                proto.write(ClipDataProto.Item.TEXT, mText.toString());
+            } else if (mUri != null) {
+                proto.write(ClipDataProto.Item.URI, mUri.toString());
+            } else if (mIntent != null) {
+                mIntent.writeToProto(proto, ClipDataProto.Item.INTENT, true, true, true, true);
+            } else {
+                proto.write(ClipDataProto.Item.NOTHING, true);
+            }
+
+            proto.end(token);
+        }
     }
 
     /**
@@ -1048,6 +1069,26 @@
     }
 
     /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        if (mClipDescription != null) {
+            mClipDescription.writeToProto(proto, ClipDataProto.DESCRIPTION);
+        }
+        if (mIcon != null) {
+            final long iToken = proto.start(ClipDataProto.ICON);
+            proto.write(ClipDataProto.Icon.WIDTH, mIcon.getWidth());
+            proto.write(ClipDataProto.Icon.HEIGHT, mIcon.getHeight());
+            proto.end(iToken);
+        }
+        for (int i = 0; i < mItems.size(); i++) {
+            mItems.get(i).writeToProto(proto, ClipDataProto.ITEMS);
+        }
+
+        proto.end(token);
+    }
+
+    /** @hide */
     public void collectUris(List<Uri> out) {
         for (int i = 0; i < mItems.size(); ++i) {
             ClipData.Item item = getItemAt(i);
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index 8e30fd6..19295fc 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -21,6 +21,7 @@
 import android.os.PersistableBundle;
 import android.text.TextUtils;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -337,6 +338,28 @@
         return !first;
     }
 
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        final int size = mMimeTypes.size();
+        for (int i = 0; i < size; i++) {
+            proto.write(ClipDescriptionProto.MIME_TYPES, mMimeTypes.get(i));
+        }
+
+        if (mLabel != null) {
+            proto.write(ClipDescriptionProto.LABEL, mLabel.toString());
+        }
+        if (mExtras != null) {
+            mExtras.writeToProto(proto, ClipDescriptionProto.EXTRAS);
+        }
+        if (mTimeStamp > 0) {
+            proto.write(ClipDescriptionProto.TIMESTAMP_MS, mTimeStamp);
+        }
+
+        proto.end(token);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 0d36bdd..ead6c25 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -284,9 +284,11 @@
     }
 
     /** Put this here so that individual services don't have to reimplement this. @hide */
-    public void toProto(ProtoOutputStream proto) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
         proto.write(ComponentNameProto.PACKAGE_NAME, mPackage);
         proto.write(ComponentNameProto.CLASS_NAME, mClass);
+        proto.end(token);
     }
 
     @Override
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 137c169..06f9171 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -324,6 +324,15 @@
     public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080;
 
     /**
+     * @hide Flag for {@link #bindService}: allows binding to a service provided
+     * by an instant app. Note that the caller may not have access to the instant
+     * app providing the service which is a violation of the instant app sandbox.
+     * This flag is intended ONLY for development/testing and should be used with
+     * great care. Only the system is allowed to use this flag.
+     */
+    public static final int BIND_ALLOW_INSTANT = 0x00400000;
+
+    /**
      * @hide Flag for {@link #bindService}: like {@link #BIND_NOT_FOREGROUND}, but puts it
      * up in to the important background state (instead of transient).
      */
@@ -2842,10 +2851,12 @@
      *          {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
      *          {@link #BIND_ALLOW_OOM_MANAGEMENT}, or
      *          {@link #BIND_WAIVE_PRIORITY}.
-     * @return If you have successfully bound to the service, {@code true} is returned;
-     *         {@code false} is returned if the connection is not made so you will not
-     *         receive the service object. You should still call {@link #unbindService}
-     *         to release the connection even if this method returned {@code false}.
+     * @return {@code true} if the system is in the process of bringing up a
+     *         service that your client has permission to bind to; {@code false}
+     *         if the system couldn't find the service or if your client doesn't
+     *         have permission to bind to it. If this value is {@code true}, you
+     *         should later call {@link #unbindService} to release the
+     *         connection.
      *
      * @throws SecurityException If the caller does not have permission to access the service
      * or the service can not be found.
@@ -3012,7 +3023,8 @@
             SYSTEM_HEALTH_SERVICE,
             //@hide: INCIDENT_SERVICE,
             //@hide: STATS_COMPANION_SERVICE,
-            COMPANION_DEVICE_SERVICE
+            COMPANION_DEVICE_SERVICE,
+            CROSS_PROFILE_APPS_SERVICE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -3091,6 +3103,14 @@
      * service objects between various different contexts (Activities, Applications,
      * Services, Providers, etc.)
      *
+     * <p>Note: Instant apps, for which {@link PackageManager#isInstantApp()} returns true,
+     * don't have access to the following system services: {@link #DEVICE_POLICY_SERVICE},
+     * {@link #FINGERPRINT_SERVICE}, {@link #SHORTCUT_SERVICE}, {@link #USB_SERVICE},
+     * {@link #WALLPAPER_SERVICE}, {@link #WIFI_P2P_SERVICE}, {@link #WIFI_SERVICE},
+     * {@link #WIFI_AWARE_SERVICE}. For these services this method will return <code>null</code>.
+     * Generally, if you are running as an instant app you should always check whether the result
+     * of this method is null.
+     *
      * @param name The name of the desired service.
      *
      * @return The service or null if the name does not exist.
@@ -3174,6 +3194,14 @@
      * Services, Providers, etc.)
      * </p>
      *
+     * <p>Note: Instant apps, for which {@link PackageManager#isInstantApp()} returns true,
+     * don't have access to the following system services: {@link #DEVICE_POLICY_SERVICE},
+     * {@link #FINGERPRINT_SERVICE}, {@link #SHORTCUT_SERVICE}, {@link #USB_SERVICE},
+     * {@link #WALLPAPER_SERVICE}, {@link #WIFI_P2P_SERVICE}, {@link #WIFI_SERVICE},
+     * {@link #WIFI_AWARE_SERVICE}. For these services this method will return <code>null</code>.
+     * Generally, if you are running as an instant app you should always check whether the result
+     * of this method is null.
+     *
      * @param serviceClass The class of the desired service.
      * @return The service or null if the class is not a supported system service.
      */
@@ -3504,9 +3532,8 @@
      *
      * @see #getSystemService
      * @see android.net.wifi.rtt.WifiRttManager
-     * @hide
      */
-    public static final String WIFI_RTT_RANGING_SERVICE = "rttmanager2";
+    public static final String WIFI_RTT_RANGING_SERVICE = "wifirtt";
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e940769..6e99709 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -9410,6 +9410,12 @@
     }
 
     /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        // Same input parameters that toString() gives to toShortString().
+        writeToProto(proto, fieldId, true, true, true, false);
+    }
+
+    /** @hide */
     public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp,
             boolean extras, boolean clip) {
         long token = proto.start(fieldId);
diff --git a/core/java/android/content/ServiceConnection.java b/core/java/android/content/ServiceConnection.java
index c16dbbe..21398f6 100644
--- a/core/java/android/content/ServiceConnection.java
+++ b/core/java/android/content/ServiceConnection.java
@@ -31,6 +31,11 @@
      * the {@link android.os.IBinder} of the communication channel to the
      * Service.
      *
+     * <p class="note"><b>Note:</b> If the system has started to bind your
+     * client app to a service, it's possible that your app will never receive
+     * this callback. Your app won't receive a callback if there's an issue with
+     * the service, such as the service crashing while being created.
+     *
      * @param name The concrete component name of the service that has
      * been connected.
      *
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 56a0def..e319750 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -26,7 +26,6 @@
 import android.content.pm.InstantAppInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IDexModuleRegisterCallback;
-import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageDeleteObserver2;
@@ -222,13 +221,6 @@
     ParceledListSlice queryInstrumentation(
             String targetPackage, int flags);
 
-    /** @deprecated Use PackageInstaller instead */
-    void installPackageAsUser(in String originPath,
-            in IPackageInstallObserver2 observer,
-            int flags,
-            in String installerPackageName,
-            int userId);
-
     void finishPackageInstall(int token, boolean didLaunch);
 
     void setInstallerPackageName(in String targetPackage, in String installerPackageName);
diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java
index cee2599..8014c94 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -16,6 +16,8 @@
 
 package android.content.pm;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.PackageParser.Package;
 import android.os.Build;
 
@@ -56,7 +58,7 @@
             boolean apacheHttpLegacyPresent = isLibraryPresent(
                     usesLibraries, usesOptionalLibraries, APACHE_HTTP_LEGACY);
             if (!apacheHttpLegacyPresent) {
-                usesLibraries = ArrayUtils.add(usesLibraries, APACHE_HTTP_LEGACY);
+                usesLibraries = prefix(usesLibraries, APACHE_HTTP_LEGACY);
             }
         }
 
@@ -86,4 +88,12 @@
         return ArrayUtils.contains(usesLibraries, apacheHttpLegacy)
                 || ArrayUtils.contains(usesOptionalLibraries, apacheHttpLegacy);
     }
+
+    private static @NonNull <T> ArrayList<T> prefix(@Nullable ArrayList<T> cur, T val) {
+        if (cur == null) {
+            cur = new ArrayList<>();
+        }
+        cur.add(0, val);
+        return cur;
+    }
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 77c5743..df677d2 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -324,7 +324,14 @@
      */
     public int createSession(@NonNull SessionParams params) throws IOException {
         try {
-            return mInstaller.createSession(params, mInstallerPackageName, mUserId);
+            final String installerPackage;
+            if (params.installerPackageName == null) {
+                installerPackage = mInstallerPackageName;
+            } else {
+                installerPackage = params.installerPackageName;
+            }
+
+            return mInstaller.createSession(params, installerPackage, mUserId);
         } catch (RuntimeException e) {
             ExceptionUtils.maybeUnwrapIOException(e);
             throw e;
@@ -1081,6 +1088,8 @@
         public String volumeUuid;
         /** {@hide} */
         public String[] grantedRuntimePermissions;
+        /** {@hide} */
+        public String installerPackageName;
 
         /**
          * Construct parameters for a new package install session.
@@ -1109,6 +1118,7 @@
             abiOverride = source.readString();
             volumeUuid = source.readString();
             grantedRuntimePermissions = source.readStringArray();
+            installerPackageName = source.readString();
         }
 
         /**
@@ -1304,6 +1314,18 @@
             }
         }
 
+        /**
+         * Set the installer package for the app.
+         *
+         * By default this is the app that created the {@link PackageInstaller} object.
+         *
+         * @param installerPackageName name of the installer package
+         * {@hide}
+         */
+        public void setInstallerPackageName(String installerPackageName) {
+            this.installerPackageName = installerPackageName;
+        }
+
         /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
@@ -1319,6 +1341,7 @@
             pw.printPair("abiOverride", abiOverride);
             pw.printPair("volumeUuid", volumeUuid);
             pw.printPair("grantedRuntimePermissions", grantedRuntimePermissions);
+            pw.printPair("installerPackageName", installerPackageName);
             pw.println();
         }
 
@@ -1343,6 +1366,7 @@
             dest.writeString(abiOverride);
             dest.writeString(volumeUuid);
             dest.writeStringArray(grantedRuntimePermissions);
+            dest.writeString(installerPackageName);
         }
 
         public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/PackageList.java b/core/java/android/content/pm/PackageList.java
new file mode 100644
index 0000000..cfd99ab
--- /dev/null
+++ b/core/java/android/content/pm/PackageList.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageManagerInternal.PackageListObserver;
+
+import com.android.server.LocalServices;
+
+import java.util.List;
+
+/**
+ * All of the package name installed on the system.
+ * <p>A self observable list that automatically removes the listener when it goes out of scope.
+ *
+ * @hide Only for use within the system server.
+ */
+public class PackageList implements PackageListObserver, AutoCloseable {
+    private final PackageListObserver mWrappedObserver;
+    private final List<String> mPackageNames;
+
+    /**
+     * Create a new object.
+     * <p>Ownership of the given {@link List} transfers to this object and should not
+     * be modified by the caller.
+     */
+    public PackageList(@NonNull List<String> packageNames, @Nullable PackageListObserver observer) {
+        mPackageNames = packageNames;
+        mWrappedObserver = observer;
+    }
+
+    @Override
+    public void onPackageAdded(String packageName) {
+        if (mWrappedObserver != null) {
+            mWrappedObserver.onPackageAdded(packageName);
+        }
+    }
+
+    @Override
+    public void onPackageRemoved(String packageName) {
+        if (mWrappedObserver != null) {
+            mWrappedObserver.onPackageRemoved(packageName);
+        }
+    }
+
+    @Override
+    public void close() throws Exception {
+        LocalServices.getService(PackageManagerInternal.class).removePackageListObserver(this);
+    }
+
+    /**
+     * Returns the names of packages installed on the system.
+     * <p>The list is a copy-in-time and the actual set of installed packages may differ. Real
+     * time updates to the package list are sent via the {@link PackageListObserver} callback.
+     */
+    public @NonNull List<String> getPackageNames() {
+        return mPackageNames;
+    }
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ff02c40..6cd4285 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -47,7 +47,6 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.net.Uri;
 import android.net.wifi.WifiManager;
 import android.os.Build;
 import android.os.Bundle;
@@ -2075,6 +2074,13 @@
     public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * supports cell-broadcast reception using the MBMS APIs.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports connecting to USB devices
      * as the USB host.
@@ -2321,8 +2327,6 @@
     /**
      * 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";
@@ -2473,10 +2477,17 @@
             = "android.software.securely_removes_users";
 
     /** {@hide} */
+    @TestApi
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_FILE_BASED_ENCRYPTION
             = "android.software.file_based_encryption";
 
+    /** {@hide} */
+    @TestApi
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_ADOPTABLE_STORAGE
+            = "android.software.adoptable_storage";
+
     /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device has a full implementation of the android.webkit.* APIs. Devices
@@ -4711,17 +4722,6 @@
     }
 
     /**
-     * @deprecated replaced by {@link PackageInstaller}
-     * @hide
-     */
-    @Deprecated
-    public abstract void installPackage(
-            Uri packageURI,
-            PackageInstallObserver observer,
-            @InstallFlags int flags,
-            String installerPackageName);
-
-    /**
      * If there is already an application with the given package name installed
      * on the system for other users, also install it for the calling user.
      * @hide
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 713cd10..2c45b8d 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -53,6 +53,14 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface KnownPackage {}
 
+    /** Observer called whenever the list of packages changes */
+    public interface PackageListObserver {
+        /** A package was added to the system. */
+        void onPackageAdded(@NonNull String packageName);
+        /** A package was removed from the system. */
+        void onPackageRemoved(@NonNull String packageName);
+    }
+
     /**
      * Provider for package names.
      */
@@ -111,6 +119,12 @@
     public abstract void setSimCallManagerPackagesProvider(PackagesProvider provider);
 
     /**
+     * Sets the Use Open Wifi packages provider.
+     * @param provider The packages provider.
+     */
+    public abstract void setUseOpenWifiAppPackagesProvider(PackagesProvider provider);
+
+    /**
      * Sets the sync adapter packages provider.
      * @param provider The provider.
      */
@@ -139,6 +153,14 @@
             int userId);
 
     /**
+     * Requests granting of the default permissions to the current default Use Open Wifi app.
+     * @param packageName The default use open wifi package name.
+     * @param userId The user for which to grant the permissions.
+     */
+    public abstract void grantDefaultPermissionsToDefaultUseOpenWifiApp(String packageName,
+            int userId);
+
+    /**
      * Sets a list of apps to keep in PM's internal data structures and as APKs even if no user has
      * currently installed it. The apps are not preloaded.
      * @param packageList List of package names to keep cached.
@@ -435,6 +457,35 @@
     public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName);
 
     /**
+     * Returns a list without a change observer.
+     *
+     * {@see #getPackageList(PackageListObserver)}
+     */
+    public @NonNull PackageList getPackageList() {
+        return getPackageList(null);
+    }
+
+    /**
+     * Returns the list of packages installed at the time of the method call.
+     * <p>The given observer is notified when the list of installed packages
+     * changes [eg. a package was installed or uninstalled]. It will not be
+     * notified if a package is updated.
+     * <p>The package list will not be updated automatically as packages are
+     * installed / uninstalled. Any changes must be handled within the observer.
+     */
+    public abstract @NonNull PackageList getPackageList(@Nullable PackageListObserver observer);
+
+    /**
+     * Removes the observer.
+     * <p>Generally not needed. {@link #getPackageList(PackageListObserver)} will automatically
+     * remove the observer.
+     * <p>Does nothing if the observer isn't currently registered.
+     * <p>Observers are notified asynchronously and it's possible for an observer to be
+     * invoked after its been removed.
+     */
+    public abstract void removePackageListObserver(@NonNull PackageListObserver observer);
+
+    /**
      * Returns a package object for the disabled system package name.
      */
     public abstract @Nullable PackageParser.Package getDisabledPackage(@NonNull String packageName);
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 8839cf9..ea476b0 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -181,6 +181,11 @@
     public static final int DISABLED_REASON_APP_CHANGED = 2;
 
     /**
+     * Shortcut is disabled for an unknown reason.
+     */
+    public static final int DISABLED_REASON_UNKNOWN = 3;
+
+    /**
      * 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.)
@@ -214,6 +219,7 @@
             DISABLED_REASON_NOT_DISABLED,
             DISABLED_REASON_BY_APP,
             DISABLED_REASON_APP_CHANGED,
+            DISABLED_REASON_UNKNOWN,
             DISABLED_REASON_VERSION_LOWER,
             DISABLED_REASON_BACKUP_NOT_SUPPORTED,
             DISABLED_REASON_SIGNATURE_MISMATCH,
@@ -272,6 +278,9 @@
             case DISABLED_REASON_OTHER_RESTORE_ISSUE:
                 return res.getString(
                         com.android.internal.R.string.shortcut_restore_unknown_issue);
+            case DISABLED_REASON_UNKNOWN:
+                return res.getString(
+                        com.android.internal.R.string.shortcut_disabled_reason_unknown);
         }
         return null;
     }
diff --git a/core/java/android/content/pm/crossprofile/CrossProfileApps.java b/core/java/android/content/pm/crossprofile/CrossProfileApps.java
index c9f184a..414c138 100644
--- a/core/java/android/content/pm/crossprofile/CrossProfileApps.java
+++ b/core/java/android/content/pm/crossprofile/CrossProfileApps.java
@@ -16,7 +16,6 @@
 package android.content.pm.crossprofile;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
@@ -61,15 +60,10 @@
      * @param user The UserHandle of the profile, must be one of the users returned by
      *        {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
      *        be thrown.
-     * @param sourceBounds The Rect containing the source bounds of the clicked icon, see
-     *                     {@link android.content.Intent#setSourceBounds(Rect)}.
-     * @param startActivityOptions Options to pass to startActivity
      */
-    public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user,
-            @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
+    public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user) {
         try {
-            mService.startActivityAsUser(mContext.getPackageName(),
-                    component, sourceBounds, startActivityOptions, user);
+            mService.startActivityAsUser(mContext.getPackageName(), component, user);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl b/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl
index dd8d04f..227f91f5 100644
--- a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl
+++ b/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl
@@ -26,6 +26,7 @@
  * @hide
  */
 interface ICrossProfileApps {
-    void startActivityAsUser(in String callingPackage, in ComponentName component, in Rect sourceBounds, in Bundle startActivityOptions, in UserHandle user);
+    void startActivityAsUser(in String callingPackage, in ComponentName component,
+        in UserHandle user);
     List<UserHandle> getTargetUserProfiles(in String callingPackage);
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3a3048e..1201ef4 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -21,6 +21,7 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.PublicKey;
 import android.hardware.camera2.impl.SyntheticKey;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.utils.TypeReference;
 import android.util.Rational;
 
@@ -169,6 +170,7 @@
     private final CameraMetadataNative mProperties;
     private List<CameraCharacteristics.Key<?>> mKeys;
     private List<CaptureRequest.Key<?>> mAvailableRequestKeys;
+    private List<CaptureRequest.Key<?>> mAvailableSessionKeys;
     private List<CaptureResult.Key<?>> mAvailableResultKeys;
 
     /**
@@ -251,6 +253,67 @@
     }
 
     /**
+     * <p>Returns a subset of {@link #getAvailableCaptureRequestKeys} keys that the
+     * camera device can pass as part of the capture session initialization.</p>
+     *
+     * <p>This list includes keys that are difficult to apply per-frame and
+     * can result in unexpected delays when modified during the capture session
+     * lifetime. Typical examples include parameters that require a
+     * time-consuming hardware re-configuration or internal camera pipeline
+     * change. For performance reasons we suggest clients to pass their initial
+     * values as part of {@link SessionConfiguration#setSessionParameters}. Once
+     * the camera capture session is enabled it is also recommended to avoid
+     * changing them from their initial values set in
+     * {@link SessionConfiguration#setSessionParameters }.
+     * Control over session parameters can still be exerted in capture requests
+     * but clients should be aware and expect delays during their application.
+     * An example usage scenario could look like this:</p>
+     * <ul>
+     * <li>The camera client starts by quering the session parameter key list via
+     *   {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li>
+     * <li>Before triggering the capture session create sequence, a capture request
+     *   must be built via {@link CameraDevice#createCaptureRequest } using an
+     *   appropriate template matching the particular use case.</li>
+     * <li>The client should go over the list of session parameters and check
+     *   whether some of the keys listed matches with the parameters that
+     *   they intend to modify as part of the first capture request.</li>
+     * <li>If there is no such match, the capture request can be  passed
+     *   unmodified to {@link SessionConfiguration#setSessionParameters }.</li>
+     * <li>If matches do exist, the client should update the respective values
+     *   and pass the request to {@link SessionConfiguration#setSessionParameters }.</li>
+     * <li>After the capture session initialization completes the session parameter
+     *   key list can continue to serve as reference when posting or updating
+     *   further requests. As mentioned above further changes to session
+     *   parameters should ideally be avoided, if updates are necessary
+     *   however clients could expect a delay/glitch during the
+     *   parameter switch.</li>
+     * </ul>
+     *
+     * <p>The list returned is not modifiable, so any attempts to modify it will throw
+     * a {@code UnsupportedOperationException}.</p>
+     *
+     * <p>Each key is only listed once in the list. The order of the keys is undefined.</p>
+     *
+     * @return List of keys that can be passed during capture session initialization. In case the
+     * camera device doesn't support such keys the list can be null.
+     */
+    @SuppressWarnings({"unchecked"})
+    public List<CaptureRequest.Key<?>> getAvailableSessionKeys() {
+        if (mAvailableSessionKeys == null) {
+            Object crKey = CaptureRequest.Key.class;
+            Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>)crKey;
+
+            int[] filterTags = get(REQUEST_AVAILABLE_SESSION_KEYS);
+            if (filterTags == null) {
+                return null;
+            }
+            mAvailableSessionKeys =
+                    getAvailableKeyList(CaptureRequest.class, crKeyTyped, filterTags);
+        }
+        return mAvailableSessionKeys;
+    }
+
+    /**
      * Returns the list of keys supported by this {@link CameraDevice} for querying
      * with a {@link CaptureRequest}.
      *
@@ -1086,36 +1149,33 @@
     /**
      * <p>Position of the camera optical center.</p>
      * <p>The position of the camera device's lens optical center,
-     * as a three-dimensional vector <code>(x,y,z)</code>, relative to the
-     * optical center of the largest camera device facing in the
-     * same direction as this camera, in the {@link android.hardware.SensorEvent Android sensor coordinate
-     * axes}. Note that only the axis definitions are shared with
-     * the sensor coordinate system, but not the origin.</p>
-     * <p>If this device is the largest or only camera device with a
-     * given facing, then this position will be <code>(0, 0, 0)</code>; a
-     * camera device with a lens optical center located 3 cm from
-     * the main sensor along the +X axis (to the right from the
-     * user's perspective) will report <code>(0.03, 0, 0)</code>.</p>
-     * <p>To transform a pixel coordinates between two cameras
-     * facing the same direction, first the source camera
-     * {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion} must be corrected for.  Then
-     * the source camera {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} needs
-     * to be applied, followed by the {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}
-     * of the source camera, the translation of the source camera
-     * relative to the destination camera, the
-     * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the destination camera, and
-     * finally the inverse of {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}
-     * of the destination camera. This obtains a
-     * radial-distortion-free coordinate in the destination
-     * camera pixel coordinates.</p>
-     * <p>To compare this against a real image from the destination
-     * camera, the destination camera image then needs to be
-     * corrected for radial distortion before comparison or
-     * sampling.</p>
+     * as a three-dimensional vector <code>(x,y,z)</code>.</p>
+     * <p>Prior to Android P, or when {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is PRIMARY_CAMERA, this position
+     * is relative to the optical center of the largest camera device facing in the same
+     * direction as this camera, in the {@link android.hardware.SensorEvent Android sensor
+     * coordinate axes}. Note that only the axis definitions are shared with the sensor
+     * coordinate system, but not the origin.</p>
+     * <p>If this device is the largest or only camera device with a given facing, then this
+     * position will be <code>(0, 0, 0)</code>; a camera device with a lens optical center located 3 cm
+     * from the main sensor along the +X axis (to the right from the user's perspective) will
+     * report <code>(0.03, 0, 0)</code>.</p>
+     * <p>To transform a pixel coordinates between two cameras facing the same direction, first
+     * the source camera {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion} must be corrected for.  Then the source
+     * camera {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} needs to be applied, followed by the
+     * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the source camera, the translation of the source camera
+     * relative to the destination camera, the {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the destination
+     * camera, and finally the inverse of {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} of the destination
+     * camera. This obtains a radial-distortion-free coordinate in the destination camera pixel
+     * coordinates.</p>
+     * <p>To compare this against a real image from the destination camera, the destination camera
+     * image then needs to be corrected for radial distortion before comparison or sampling.</p>
+     * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is GYROSCOPE, then this position is relative to
+     * the center of the primary gyroscope on the device.</p>
      * <p><b>Units</b>: Meters</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
      * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
+     * @see CameraCharacteristics#LENS_POSE_REFERENCE
      * @see CameraCharacteristics#LENS_POSE_ROTATION
      * @see CameraCharacteristics#LENS_RADIAL_DISTORTION
      */
@@ -1226,6 +1286,28 @@
             new Key<float[]>("android.lens.radialDistortion", float[].class);
 
     /**
+     * <p>The origin for {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}.</p>
+     * <p>Different calibration methods and use cases can produce better or worse results
+     * depending on the selected coordinate origin.</p>
+     * <p>For devices designed to support the MOTION_TRACKING capability, the GYROSCOPE origin
+     * makes device calibration and later usage by applications combining camera and gyroscope
+     * information together simpler.</p>
+     * <p><b>Possible values:</b>
+     * <ul>
+     *   <li>{@link #LENS_POSE_REFERENCE_PRIMARY_CAMERA PRIMARY_CAMERA}</li>
+     *   <li>{@link #LENS_POSE_REFERENCE_GYROSCOPE GYROSCOPE}</li>
+     * </ul></p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+     * @see #LENS_POSE_REFERENCE_PRIMARY_CAMERA
+     * @see #LENS_POSE_REFERENCE_GYROSCOPE
+     */
+    @PublicKey
+    public static final Key<Integer> LENS_POSE_REFERENCE =
+            new Key<Integer>("android.lens.poseReference", int.class);
+
+    /**
      * <p>List of noise reduction modes for {@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} that are supported
      * by this camera device.</p>
      * <p>Full-capability camera devices will always support OFF and FAST.</p>
@@ -1496,6 +1578,7 @@
      *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING YUV_REPROCESSING}</li>
      *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT}</li>
      *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO CONSTRAINED_HIGH_SPEED_VIDEO}</li>
+     *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING}</li>
      * </ul></p>
      * <p>This key is available on all devices.</p>
      *
@@ -1510,6 +1593,7 @@
      * @see #REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING
      * @see #REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT
      * @see #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+     * @see #REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING
      */
     @PublicKey
     public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES =
@@ -1571,6 +1655,52 @@
             new Key<int[]>("android.request.availableCharacteristicsKeys", int[].class);
 
     /**
+     * <p>A subset of the available request keys that the camera device
+     * can pass as part of the capture session initialization.</p>
+     * <p>This is a subset of android.request.availableRequestKeys which
+     * contains a list of keys that are difficult to apply per-frame and
+     * can result in unexpected delays when modified during the capture session
+     * lifetime. Typical examples include parameters that require a
+     * time-consuming hardware re-configuration or internal camera pipeline
+     * change. For performance reasons we advise clients to pass their initial
+     * values as part of
+     * {@link SessionConfiguration#setSessionParameters }.
+     * Once the camera capture session is enabled it is also recommended to avoid
+     * changing them from their initial values set in
+     * {@link SessionConfiguration#setSessionParameters }.
+     * Control over session parameters can still be exerted in capture requests
+     * but clients should be aware and expect delays during their application.
+     * An example usage scenario could look like this:</p>
+     * <ul>
+     * <li>The camera client starts by quering the session parameter key list via
+     *   {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li>
+     * <li>Before triggering the capture session create sequence, a capture request
+     *   must be built via
+     *   {@link CameraDevice#createCaptureRequest }
+     *   using an appropriate template matching the particular use case.</li>
+     * <li>The client should go over the list of session parameters and check
+     *   whether some of the keys listed matches with the parameters that
+     *   they intend to modify as part of the first capture request.</li>
+     * <li>If there is no such match, the capture request can be  passed
+     *   unmodified to
+     *   {@link SessionConfiguration#setSessionParameters }.</li>
+     * <li>If matches do exist, the client should update the respective values
+     *   and pass the request to
+     *   {@link SessionConfiguration#setSessionParameters }.</li>
+     * <li>After the capture session initialization completes the session parameter
+     *   key list can continue to serve as reference when posting or updating
+     *   further requests. As mentioned above further changes to session
+     *   parameters should ideally be avoided, if updates are necessary
+     *   however clients could expect a delay/glitch during the
+     *   parameter switch.</li>
+     * </ul>
+     * <p>This key is available on all devices.</p>
+     * @hide
+     */
+    public static final Key<int[]> REQUEST_AVAILABLE_SESSION_KEYS =
+            new Key<int[]>("android.request.availableSessionKeys", int[].class);
+
+    /**
      * <p>The list of image formats that are supported by this
      * camera device for output streams.</p>
      * <p>All camera devices will support JPEG and YUV_420_888 formats.</p>
@@ -2867,6 +2997,18 @@
             new Key<Integer>("android.info.supportedHardwareLevel", int.class);
 
     /**
+     * <p>A short string for manufacturer version information about the camera device, such as
+     * ISP hardware, sensors, etc.</p>
+     * <p>This can be used in {@link android.media.ExifInterface#TAG_IMAGE_DESCRIPTION TAG_IMAGE_DESCRIPTION}
+     * in jpeg EXIF. This key may be absent if no version information is available on the
+     * device.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     */
+    @PublicKey
+    public static final Key<String> INFO_VERSION =
+            new Key<String>("android.info.version", String.class);
+
+    /**
      * <p>The maximum number of frames that can occur after a request
      * (different than the previous) has been submitted, and before the
      * result's state becomes synchronized.</p>
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 55343a2..ce1fba7 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -26,6 +26,7 @@
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.os.Handler;
 import android.view.Surface;
 
@@ -143,6 +144,37 @@
      */
     public static final int TEMPLATE_MANUAL = 6;
 
+    /**
+     * A template for selecting camera parameters that match TEMPLATE_PREVIEW as closely as
+     * possible while improving the camera output for motion tracking use cases.
+     *
+     * <p>This template is best used by applications that are frequently switching between motion
+     * tracking use cases and regular still capture use cases, to minimize the IQ changes
+     * when swapping use cases.</p>
+     *
+     * <p>This template is guaranteed to be supported on camera devices that support the
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING}
+     * capability.</p>
+     *
+     * @see #createCaptureRequest
+     */
+    public static final int TEMPLATE_MOTION_TRACKING_PREVIEW = 7;
+
+    /**
+     * A template for selecting camera parameters that maximize the quality of camera output for
+     * motion tracking use cases.
+     *
+     * <p>This template is best used by applications dedicated to motion tracking applications,
+     * which aren't concerned about fast switches between motion tracking and other use cases.</p>
+     *
+     * <p>This template is guaranteed to be supported on camera devices that support the
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING}
+     * capability.</p>
+     *
+     * @see #createCaptureRequest
+     */
+    public static final int TEMPLATE_MOTION_TRACKING_BEST = 8;
+
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(prefix = {"TEMPLATE_"}, value =
@@ -385,6 +417,24 @@
      * </table><br>
      * </p>
      *
+     * <p>MOTION_TRACKING-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}
+     * includes
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING})
+     * devices support at least the below stream combinations in addition to those for
+     * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices. The
+     * {@code FULL FOV 640} entry means that the device will support a resolution that's 640 pixels
+     * wide, with the height set so that the resolution aspect ratio matches the MAXIMUM output
+     * aspect ratio.  So for a device with a 4:3 image sensor, this will be 640x480, and for a
+     * device with a 16:9 sensor, this will be 640x360, and so on.
+     *
+     * <table>
+     * <tr><th colspan="7">MOTION_TRACKING-capability additional guaranteed configurations</th></tr>
+     * <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th><th rowspan="2">Sample use case(s)</th> </tr>
+     * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr>
+     * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code FULL FOV 640}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Live preview with a tracking YUV output and a maximum-resolution YUV for still captures.</td> </tr>
+     * </table><br>
+     * </p>
+     *
      * <p>BURST-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes
      * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}) devices
      * support at least the below stream combinations in addition to those for
@@ -811,6 +861,26 @@
             throws CameraAccessException;
 
     /**
+     * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
+     * object that aggregates all supported parameters.</p>
+     *
+     * @param config A session configuration (see {@link SessionConfiguration}).
+     *
+     * @throws IllegalArgumentException In case the session configuration is invalid; or the output
+     *                                  configurations are empty.
+     * @throws CameraAccessException In case the camera device is no longer connected or has
+     *                               encountered a fatal error.
+     * @see #createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)
+     * @see #createCaptureSessionByOutputConfigurations
+     * @see #createReprocessableCaptureSession
+     * @see #createConstrainedHighSpeedCaptureSession
+     */
+    public void createCaptureSession(
+            SessionConfiguration config) throws CameraAccessException {
+        throw new UnsupportedOperationException("No default implementation");
+    }
+
+    /**
      * <p>Create a {@link CaptureRequest.Builder} for new capture requests,
      * initialized with template for a target use case. The settings are chosen
      * to be the best options for the specific camera device, so it is not
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index cb11d0f..1c7f289 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -336,6 +336,30 @@
     public static final int LENS_FACING_EXTERNAL = 2;
 
     //
+    // Enumeration values for CameraCharacteristics#LENS_POSE_REFERENCE
+    //
+
+    /**
+     * <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} is relative to the optical center of
+     * the largest camera device facing the same direction as this camera.</p>
+     * <p>This default value for API levels before Android P.</p>
+     *
+     * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+     * @see CameraCharacteristics#LENS_POSE_REFERENCE
+     */
+    public static final int LENS_POSE_REFERENCE_PRIMARY_CAMERA = 0;
+
+    /**
+     * <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} is relative to the position of the
+     * primary gyroscope of this Android device.</p>
+     * <p>This is the value reported by all devices that support the MOTION_TRACKING capability.</p>
+     *
+     * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+     * @see CameraCharacteristics#LENS_POSE_REFERENCE
+     */
+    public static final int LENS_POSE_REFERENCE_GYROSCOPE = 1;
+
+    //
     // Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
     //
 
@@ -665,6 +689,7 @@
      * </ul>
      * </li>
      * <li>The {@link CameraCharacteristics#DEPTH_DEPTH_IS_EXCLUSIVE android.depth.depthIsExclusive} entry is listed by this device.</li>
+     * <li>As of Android P, the {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} entry is listed by this device.</li>
      * <li>A LIMITED camera with only the DEPTH_OUTPUT capability does not have to support
      *   normal YUV_420_888, JPEG, and PRIV-format outputs. It only has to support the DEPTH16
      *   format.</li>
@@ -680,6 +705,7 @@
      * @see CameraCharacteristics#DEPTH_DEPTH_IS_EXCLUSIVE
      * @see CameraCharacteristics#LENS_FACING
      * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
+     * @see CameraCharacteristics#LENS_POSE_REFERENCE
      * @see CameraCharacteristics#LENS_POSE_ROTATION
      * @see CameraCharacteristics#LENS_POSE_TRANSLATION
      * @see CameraCharacteristics#LENS_RADIAL_DISTORTION
@@ -774,6 +800,51 @@
      */
     public static final int REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO = 9;
 
+    /**
+     * <p>The device supports controls and metadata required for accurate motion tracking for
+     * use cases such as augmented reality, electronic image stabilization, and so on.</p>
+     * <p>This means this camera device has accurate optical calibration and timestamps relative
+     * to the inertial sensors.</p>
+     * <p>This capability requires the camera device to support the following:</p>
+     * <ul>
+     * <li>Capture request templates {@link android.hardware.camera2.CameraDevice#TEMPLATE_MOTION_TRACKING_PREVIEW } and {@link android.hardware.camera2.CameraDevice#TEMPLATE_MOTION_TRACKING_BEST } are defined.</li>
+     * <li>The stream configurations listed in {@link android.hardware.camera2.CameraDevice#createCaptureSession } for MOTION_TRACKING are
+     *   supported, either at 30 or 60fps maximum frame rate.</li>
+     * <li>The following camera characteristics and capture result metadata are provided:<ul>
+     * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li>
+     * <li>{@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion}</li>
+     * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li>
+     * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li>
+     * <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} with value GYROSCOPE</li>
+     * </ul>
+     * </li>
+     * <li>The {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE android.sensor.info.timestampSource} field has value <code>REALTIME</code>. When compared to
+     *   timestamps from the device's gyroscopes, the clock difference for events occuring at
+     *   the same actual time instant will be less than 1 ms.</li>
+     * <li>The value of the {@link CaptureResult#SENSOR_ROLLING_SHUTTER_SKEW android.sensor.rollingShutterSkew} field is accurate to within 1 ms.</li>
+     * <li>The value of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} is guaranteed to be available in the
+     *   capture result.</li>
+     * <li>The {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} control supports MOTION_TRACKING to limit maximum
+     *   exposure to 20 milliseconds.</li>
+     * <li>The stream configurations required for MOTION_TRACKING (listed at {@link android.hardware.camera2.CameraDevice#createCaptureSession }) can operate at least at
+     *   30fps; optionally, they can operate at 60fps, and '[60, 60]' is listed in
+     *   {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}.</li>
+     * </ul>
+     *
+     * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
+     * @see CaptureRequest#CONTROL_CAPTURE_INTENT
+     * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
+     * @see CameraCharacteristics#LENS_POSE_REFERENCE
+     * @see CameraCharacteristics#LENS_POSE_ROTATION
+     * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+     * @see CameraCharacteristics#LENS_RADIAL_DISTORTION
+     * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+     * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE
+     * @see CaptureResult#SENSOR_ROLLING_SHUTTER_SKEW
+     * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+     */
+    public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10;
+
     //
     // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
     //
@@ -1661,6 +1732,16 @@
      */
     public static final int CONTROL_CAPTURE_INTENT_MANUAL = 6;
 
+    /**
+     * <p>This request is for a motion tracking use case, where
+     * the application will use camera and inertial sensor data to
+     * locate and track objects in the world.</p>
+     * <p>The camera device auto-exposure routine will limit the exposure time
+     * of the camera to no more than 20 milliseconds, to minimize motion blur.</p>
+     * @see CaptureRequest#CONTROL_CAPTURE_INTENT
+     */
+    public static final int CONTROL_CAPTURE_INTENT_MOTION_TRACKING = 7;
+
     //
     // Enumeration values for CaptureRequest#CONTROL_EFFECT_MODE
     //
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 0262ecb..cf27c70 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -21,15 +21,18 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.PublicKey;
 import android.hardware.camera2.impl.SyntheticKey;
+import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.utils.HashCodeHelpers;
 import android.hardware.camera2.utils.TypeReference;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
 import android.view.Surface;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 
@@ -198,7 +201,24 @@
         }
     }
 
-    private final HashSet<Surface> mSurfaceSet;
+    private final String            TAG = "CaptureRequest-JV";
+
+    private final ArraySet<Surface> mSurfaceSet = new ArraySet<Surface>();
+
+    // Speed up sending CaptureRequest across IPC:
+    // mSurfaceConverted should only be set to true during capture request
+    // submission by {@link #convertSurfaceToStreamId}. The method will convert
+    // surfaces to stream/surface indexes based on passed in stream configuration at that time.
+    // This will save significant unparcel time for remote camera device.
+    // Once the request is submitted, camera device will call {@link #recoverStreamIdToSurface}
+    // to reset the capture request back to its original state.
+    private final Object           mSurfacesLock = new Object();
+    private boolean                mSurfaceConverted = false;
+    private int[]                  mStreamIdxArray;
+    private int[]                  mSurfaceIdxArray;
+
+    private static final ArraySet<Surface> mEmptySurfaceSet = new ArraySet<Surface>();
+
     private final CameraMetadataNative mSettings;
     private boolean mIsReprocess;
     // If this request is part of constrained high speed request list that was created by
@@ -218,7 +238,6 @@
     private CaptureRequest() {
         mSettings = new CameraMetadataNative();
         setNativeInstance(mSettings);
-        mSurfaceSet = new HashSet<Surface>();
         mIsReprocess = false;
         mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE;
     }
@@ -232,7 +251,7 @@
     private CaptureRequest(CaptureRequest source) {
         mSettings = new CameraMetadataNative(source.mSettings);
         setNativeInstance(mSettings);
-        mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone();
+        mSurfaceSet.addAll(source.mSurfaceSet);
         mIsReprocess = source.mIsReprocess;
         mIsPartOfCHSRequestList = source.mIsPartOfCHSRequestList;
         mReprocessableSessionId = source.mReprocessableSessionId;
@@ -263,7 +282,6 @@
             int reprocessableSessionId) {
         mSettings = CameraMetadataNative.move(settings);
         setNativeInstance(mSettings);
-        mSurfaceSet = new HashSet<Surface>();
         mIsReprocess = isReprocess;
         if (isReprocess) {
             if (reprocessableSessionId == CameraCaptureSession.SESSION_ID_NONE) {
@@ -463,22 +481,25 @@
     private void readFromParcel(Parcel in) {
         mSettings.readFromParcel(in);
         setNativeInstance(mSettings);
-
-        mSurfaceSet.clear();
-
-        Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader());
-
-        if (parcelableArray == null) {
-            return;
-        }
-
-        for (Parcelable p : parcelableArray) {
-            Surface s = (Surface) p;
-            mSurfaceSet.add(s);
-        }
-
         mIsReprocess = (in.readInt() == 0) ? false : true;
         mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE;
+
+        synchronized (mSurfacesLock) {
+            mSurfaceSet.clear();
+            Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader());
+            if (parcelableArray != null) {
+                for (Parcelable p : parcelableArray) {
+                    Surface s = (Surface) p;
+                    mSurfaceSet.add(s);
+                }
+            }
+            // Intentionally disallow java side readFromParcel to receive streamIdx/surfaceIdx
+            // Since there is no good way to convert indexes back to Surface
+            int streamSurfaceSize = in.readInt();
+            if (streamSurfaceSize != 0) {
+                throw new RuntimeException("Reading cached CaptureRequest is not supported");
+            }
+        }
     }
 
     @Override
@@ -489,8 +510,21 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         mSettings.writeToParcel(dest, flags);
-        dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags);
         dest.writeInt(mIsReprocess ? 1 : 0);
+
+        synchronized (mSurfacesLock) {
+            final ArraySet<Surface> surfaces = mSurfaceConverted ? mEmptySurfaceSet : mSurfaceSet;
+            dest.writeParcelableArray(surfaces.toArray(new Surface[surfaces.size()]), flags);
+            if (mSurfaceConverted) {
+                dest.writeInt(mStreamIdxArray.length);
+                for (int i = 0; i < mStreamIdxArray.length; i++) {
+                    dest.writeInt(mStreamIdxArray[i]);
+                    dest.writeInt(mSurfaceIdxArray[i]);
+                }
+            } else {
+                dest.writeInt(0);
+            }
+        }
     }
 
     /**
@@ -508,6 +542,67 @@
     }
 
     /**
+     * @hide
+     */
+    public void convertSurfaceToStreamId(
+            final SparseArray<OutputConfiguration> configuredOutputs) {
+        synchronized (mSurfacesLock) {
+            if (mSurfaceConverted) {
+                Log.v(TAG, "Cannot convert already converted surfaces!");
+                return;
+            }
+
+            mStreamIdxArray = new int[mSurfaceSet.size()];
+            mSurfaceIdxArray = new int[mSurfaceSet.size()];
+            int i = 0;
+            for (Surface s : mSurfaceSet) {
+                boolean streamFound = false;
+                for (int j = 0; j < configuredOutputs.size(); ++j) {
+                    int streamId = configuredOutputs.keyAt(j);
+                    OutputConfiguration outConfig = configuredOutputs.valueAt(j);
+                    int surfaceId = 0;
+                    for (Surface outSurface : outConfig.getSurfaces()) {
+                        if (s == outSurface) {
+                            streamFound = true;
+                            mStreamIdxArray[i] = streamId;
+                            mSurfaceIdxArray[i] = surfaceId;
+                            i++;
+                            break;
+                        }
+                        surfaceId++;
+                    }
+                    if (streamFound) {
+                        break;
+                    }
+                }
+                if (!streamFound) {
+                    mStreamIdxArray = null;
+                    mSurfaceIdxArray = null;
+                    throw new IllegalArgumentException(
+                            "CaptureRequest contains unconfigured Input/Output Surface!");
+                }
+            }
+            mSurfaceConverted = true;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void recoverStreamIdToSurface() {
+        synchronized (mSurfacesLock) {
+            if (!mSurfaceConverted) {
+                Log.v(TAG, "Cannot convert already converted surfaces!");
+                return;
+            }
+
+            mStreamIdxArray = null;
+            mSurfaceIdxArray = null;
+            mSurfaceConverted = false;
+        }
+    }
+
+    /**
      * A builder for capture requests.
      *
      * <p>To obtain a builder instance, use the
@@ -1392,10 +1487,13 @@
      * strategy.</p>
      * <p>This control (except for MANUAL) is only effective if
      * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
-     * <p>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
-     * contains PRIVATE_REPROCESSING or YUV_REPROCESSING. MANUAL will be supported if
-     * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR. Other intent values are
-     * always supported.</p>
+     * <p>All intents are supported by all devices, except that:
+     *   * ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * PRIVATE_REPROCESSING or YUV_REPROCESSING.
+     *   * MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * MANUAL_SENSOR.
+     *   * MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * MOTION_TRACKING.</p>
      * <p><b>Possible values:</b>
      * <ul>
      *   <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li>
@@ -1405,6 +1503,7 @@
      *   <li>{@link #CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT VIDEO_SNAPSHOT}</li>
      *   <li>{@link #CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG ZERO_SHUTTER_LAG}</li>
      *   <li>{@link #CONTROL_CAPTURE_INTENT_MANUAL MANUAL}</li>
+     *   <li>{@link #CONTROL_CAPTURE_INTENT_MOTION_TRACKING MOTION_TRACKING}</li>
      * </ul></p>
      * <p>This key is available on all devices.</p>
      *
@@ -1417,6 +1516,7 @@
      * @see #CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT
      * @see #CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG
      * @see #CONTROL_CAPTURE_INTENT_MANUAL
+     * @see #CONTROL_CAPTURE_INTENT_MOTION_TRACKING
      */
     @PublicKey
     public static final Key<Integer> CONTROL_CAPTURE_INTENT =
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 6d7b06f..b6b0c90 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -1754,10 +1754,13 @@
      * strategy.</p>
      * <p>This control (except for MANUAL) is only effective if
      * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
-     * <p>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
-     * contains PRIVATE_REPROCESSING or YUV_REPROCESSING. MANUAL will be supported if
-     * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR. Other intent values are
-     * always supported.</p>
+     * <p>All intents are supported by all devices, except that:
+     *   * ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * PRIVATE_REPROCESSING or YUV_REPROCESSING.
+     *   * MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * MANUAL_SENSOR.
+     *   * MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * MOTION_TRACKING.</p>
      * <p><b>Possible values:</b>
      * <ul>
      *   <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li>
@@ -1767,6 +1770,7 @@
      *   <li>{@link #CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT VIDEO_SNAPSHOT}</li>
      *   <li>{@link #CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG ZERO_SHUTTER_LAG}</li>
      *   <li>{@link #CONTROL_CAPTURE_INTENT_MANUAL MANUAL}</li>
+     *   <li>{@link #CONTROL_CAPTURE_INTENT_MOTION_TRACKING MOTION_TRACKING}</li>
      * </ul></p>
      * <p>This key is available on all devices.</p>
      *
@@ -1779,6 +1783,7 @@
      * @see #CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT
      * @see #CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG
      * @see #CONTROL_CAPTURE_INTENT_MANUAL
+     * @see #CONTROL_CAPTURE_INTENT_MOTION_TRACKING
      */
     @PublicKey
     public static final Key<Integer> CONTROL_CAPTURE_INTENT =
@@ -2761,36 +2766,33 @@
     /**
      * <p>Position of the camera optical center.</p>
      * <p>The position of the camera device's lens optical center,
-     * as a three-dimensional vector <code>(x,y,z)</code>, relative to the
-     * optical center of the largest camera device facing in the
-     * same direction as this camera, in the {@link android.hardware.SensorEvent Android sensor coordinate
-     * axes}. Note that only the axis definitions are shared with
-     * the sensor coordinate system, but not the origin.</p>
-     * <p>If this device is the largest or only camera device with a
-     * given facing, then this position will be <code>(0, 0, 0)</code>; a
-     * camera device with a lens optical center located 3 cm from
-     * the main sensor along the +X axis (to the right from the
-     * user's perspective) will report <code>(0.03, 0, 0)</code>.</p>
-     * <p>To transform a pixel coordinates between two cameras
-     * facing the same direction, first the source camera
-     * {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion} must be corrected for.  Then
-     * the source camera {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} needs
-     * to be applied, followed by the {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}
-     * of the source camera, the translation of the source camera
-     * relative to the destination camera, the
-     * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the destination camera, and
-     * finally the inverse of {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}
-     * of the destination camera. This obtains a
-     * radial-distortion-free coordinate in the destination
-     * camera pixel coordinates.</p>
-     * <p>To compare this against a real image from the destination
-     * camera, the destination camera image then needs to be
-     * corrected for radial distortion before comparison or
-     * sampling.</p>
+     * as a three-dimensional vector <code>(x,y,z)</code>.</p>
+     * <p>Prior to Android P, or when {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is PRIMARY_CAMERA, this position
+     * is relative to the optical center of the largest camera device facing in the same
+     * direction as this camera, in the {@link android.hardware.SensorEvent Android sensor
+     * coordinate axes}. Note that only the axis definitions are shared with the sensor
+     * coordinate system, but not the origin.</p>
+     * <p>If this device is the largest or only camera device with a given facing, then this
+     * position will be <code>(0, 0, 0)</code>; a camera device with a lens optical center located 3 cm
+     * from the main sensor along the +X axis (to the right from the user's perspective) will
+     * report <code>(0.03, 0, 0)</code>.</p>
+     * <p>To transform a pixel coordinates between two cameras facing the same direction, first
+     * the source camera {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion} must be corrected for.  Then the source
+     * camera {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} needs to be applied, followed by the
+     * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the source camera, the translation of the source camera
+     * relative to the destination camera, the {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the destination
+     * camera, and finally the inverse of {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} of the destination
+     * camera. This obtains a radial-distortion-free coordinate in the destination camera pixel
+     * coordinates.</p>
+     * <p>To compare this against a real image from the destination camera, the destination camera
+     * image then needs to be corrected for radial distortion before comparison or sampling.</p>
+     * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is GYROSCOPE, then this position is relative to
+     * the center of the primary gyroscope on the device.</p>
      * <p><b>Units</b>: Meters</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
      * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
+     * @see CameraCharacteristics#LENS_POSE_REFERENCE
      * @see CameraCharacteristics#LENS_POSE_ROTATION
      * @see CameraCharacteristics#LENS_RADIAL_DISTORTION
      */
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index 374789c..8b8bbc3 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -800,7 +800,8 @@
                 try {
                     // begin transition to unconfigured
                     mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null,
-                            /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE);
+                            /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE,
+                            /*sessionParams*/ null);
                 } catch (CameraAccessException e) {
                     // OK: do not throw checked exceptions.
                     Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 6787d84..f1ffb89 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -32,6 +32,7 @@
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.ReprocessFormatsMap;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.utils.SubmitInfo;
 import android.hardware.camera2.utils.SurfaceUtils;
@@ -362,7 +363,7 @@
             outputConfigs.add(new OutputConfiguration(s));
         }
         configureStreamsChecked(/*inputConfig*/null, outputConfigs,
-                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
 
     }
 
@@ -382,12 +383,13 @@
      * @param outputs a list of one or more surfaces, or {@code null} to unconfigure
      * @param operatingMode If the stream configuration is for a normal session,
      *     a constrained high speed session, or something else.
+     * @param sessionParams Session parameters.
      * @return whether or not the configuration was successful
      *
      * @throws CameraAccessException if there were any unexpected problems during configuration
      */
     public boolean configureStreamsChecked(InputConfiguration inputConfig,
-            List<OutputConfiguration> outputs, int operatingMode)
+            List<OutputConfiguration> outputs, int operatingMode, CaptureRequest sessionParams)
                     throws CameraAccessException {
         // Treat a null input the same an empty list
         if (outputs == null) {
@@ -463,7 +465,11 @@
                     }
                 }
 
-                mRemoteDevice.endConfigure(operatingMode);
+                if (sessionParams != null) {
+                    mRemoteDevice.endConfigure(operatingMode, sessionParams.getNativeCopy());
+                } else {
+                    mRemoteDevice.endConfigure(operatingMode, null);
+                }
 
                 success = true;
             } catch (IllegalArgumentException e) {
@@ -499,7 +505,7 @@
             outConfigurations.add(new OutputConfiguration(surface));
         }
         createCaptureSessionInternal(null, outConfigurations, callback, handler,
-                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
     }
 
     @Override
@@ -515,7 +521,7 @@
         List<OutputConfiguration> currentOutputs = new ArrayList<>(outputConfigurations);
 
         createCaptureSessionInternal(null, currentOutputs, callback, handler,
-                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/null);
     }
 
     @Override
@@ -535,7 +541,7 @@
             outConfigurations.add(new OutputConfiguration(surface));
         }
         createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler,
-                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
     }
 
     @Override
@@ -563,7 +569,8 @@
             currentOutputs.add(new OutputConfiguration(output));
         }
         createCaptureSessionInternal(inputConfig, currentOutputs,
-                callback, handler, /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+                callback, handler, /*operatingMode*/ICameraDeviceUser.NORMAL_MODE,
+                /*sessionParams*/ null);
     }
 
     @Override
@@ -574,16 +581,13 @@
             throw new IllegalArgumentException(
                     "Output surface list must not be null and the size must be no more than 2");
         }
-        StreamConfigurationMap config =
-                getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-        SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null, config);
-
         List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
         for (Surface surface : outputs) {
             outConfigurations.add(new OutputConfiguration(surface));
         }
         createCaptureSessionInternal(null, outConfigurations, callback, handler,
-                /*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE);
+                /*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE,
+                /*sessionParams*/ null);
     }
 
     @Override
@@ -596,13 +600,30 @@
         for (OutputConfiguration output : outputs) {
             currentOutputs.add(new OutputConfiguration(output));
         }
-        createCaptureSessionInternal(inputConfig, currentOutputs, callback, handler, operatingMode);
+        createCaptureSessionInternal(inputConfig, currentOutputs, callback, handler, operatingMode,
+                /*sessionParams*/ null);
+    }
+
+    @Override
+    public void createCaptureSession(SessionConfiguration config)
+            throws CameraAccessException {
+        if (config == null) {
+            throw new IllegalArgumentException("Invalid session configuration");
+        }
+
+        List<OutputConfiguration> outputConfigs = config.getOutputConfigurations();
+        if (outputConfigs == null) {
+            throw new IllegalArgumentException("Invalid output configurations");
+        }
+        createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs,
+                config.getStateCallback(), config.getHandler(), config.getSessionType(),
+                config.getSessionParameters());
     }
 
     private void createCaptureSessionInternal(InputConfiguration inputConfig,
             List<OutputConfiguration> outputConfigurations,
             CameraCaptureSession.StateCallback callback, Handler handler,
-            int operatingMode) throws CameraAccessException {
+            int operatingMode, CaptureRequest sessionParams) throws CameraAccessException {
         synchronized(mInterfaceLock) {
             if (DEBUG) {
                 Log.d(TAG, "createCaptureSessionInternal");
@@ -630,7 +651,7 @@
             try {
                 // configure streams and then block until IDLE
                 configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
-                        operatingMode);
+                        operatingMode, sessionParams);
                 if (configureSuccess == true && inputConfig != null) {
                     input = mRemoteDevice.getInputSurface();
                 }
@@ -646,6 +667,14 @@
             // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
             CameraCaptureSessionCore newSession = null;
             if (isConstrainedHighSpeed) {
+                ArrayList<Surface> surfaces = new ArrayList<>(outputConfigurations.size());
+                for (OutputConfiguration outConfig : outputConfigurations) {
+                    surfaces.add(outConfig.getSurface());
+                }
+                StreamConfigurationMap config =
+                    getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+                SurfaceUtils.checkConstrainedHighSpeedSurfaces(surfaces, /*fpsRange*/null, config);
+
                 newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++,
                         callback, handler, this, mDeviceHandler, configureSuccess,
                         mCharacteristics);
@@ -779,6 +808,7 @@
             }
 
             mRemoteDevice.updateOutputConfiguration(streamId, config);
+            mConfiguredOutputs.put(streamId, config);
         }
     }
 
@@ -828,6 +858,7 @@
                             + " must have at least 1 surface");
                 }
                 mRemoteDevice.finalizeOutputConfigurations(streamId, config);
+                mConfiguredOutputs.put(streamId, config);
             }
         }
     }
@@ -950,11 +981,20 @@
             SubmitInfo requestInfo;
 
             CaptureRequest[] requestArray = requestList.toArray(new CaptureRequest[requestList.size()]);
+            // Convert Surface to streamIdx and surfaceIdx
+            for (CaptureRequest request : requestArray) {
+                request.convertSurfaceToStreamId(mConfiguredOutputs);
+            }
+
             requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating);
             if (DEBUG) {
                 Log.v(TAG, "last frame number " + requestInfo.getLastFrameNumber());
             }
 
+            for (CaptureRequest request : requestArray) {
+                request.recoverStreamIdToSurface();
+            }
+
             if (callback != null) {
                 mCaptureCallbackMap.put(requestInfo.getRequestId(),
                         new CaptureCallbackHolder(
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 0978ff8..1f4ed13 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -106,9 +106,11 @@
         }
     }
 
-    public void endConfigure(int operatingMode) throws CameraAccessException {
+    public void endConfigure(int operatingMode, CameraMetadataNative sessionParams)
+           throws CameraAccessException {
         try {
-            mRemoteDevice.endConfigure(operatingMode);
+            mRemoteDevice.endConfigure(operatingMode, (sessionParams == null) ?
+                    new CameraMetadataNative() : sessionParams);
         } catch (Throwable t) {
             CameraManager.throwAsPublicException(t);
             throw new UnsupportedOperationException("Unexpected exception", t);
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 119cca8..eccab75 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -498,7 +498,7 @@
     }
 
     @Override
-    public void endConfigure(int operatingMode) {
+    public void endConfigure(int operatingMode, CameraMetadataNative sessionParams) {
         if (DEBUG) {
             Log.d(TAG, "endConfigure called.");
         }
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
new file mode 100644
index 0000000..a79a6c1
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.camera2.params;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.IntDef;
+import android.os.Handler;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.OutputConfiguration;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * A helper class that aggregates all supported arguments for capture session initialization.
+ */
+public final class SessionConfiguration {
+    /**
+     * A regular session type containing instances of {@link OutputConfiguration} running
+     * at regular non high speed FPS ranges and optionally {@link InputConfiguration} for
+     * reprocessable sessions.
+     *
+     * @see CameraDevice#createCaptureSession
+     * @see CameraDevice#createReprocessableCaptureSession
+     */
+    public static final int SESSION_REGULAR = CameraDevice.SESSION_OPERATION_MODE_NORMAL;
+
+    /**
+     * A high speed session type that can only contain instances of {@link OutputConfiguration}.
+     * The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration}
+     * are not supported.
+     *
+     * @see CameraDevice#createConstrainedHighSpeedCaptureSession
+     */
+    public static final int SESSION_HIGH_SPEED =
+        CameraDevice.SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED;
+
+    /**
+     * First vendor-specific session mode
+     * @hide
+     */
+    public static final int SESSION_VENDOR_START =
+        CameraDevice.SESSION_OPERATION_MODE_VENDOR_START;
+
+     /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SESSION_"}, value =
+            {SESSION_REGULAR,
+             SESSION_HIGH_SPEED })
+    public @interface SessionMode {};
+
+    // Camera capture session related parameters.
+    private List<OutputConfiguration> mOutputConfigurations;
+    private CameraCaptureSession.StateCallback mStateCallback;
+    private int mSessionType;
+    private Handler mHandler = null;
+    private InputConfiguration mInputConfig = null;
+    private CaptureRequest mSessionParameters = null;
+
+    /**
+     * Create a new {@link SessionConfiguration}.
+     *
+     * @param sessionType The session type.
+     * @param outputs A list of output configurations for the capture session.
+     * @param cb A state callback interface implementation.
+     * @param handler The handler on which the callback will be invoked. If it is
+     *                set to null, the callback will be invoked on the current thread's
+     *                {@link android.os.Looper looper}.
+     *
+     * @see #SESSION_REGULAR
+     * @see #SESSION_HIGH_SPEED
+     * @see CameraDevice#createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)
+     * @see CameraDevice#createCaptureSessionByOutputConfigurations
+     * @see CameraDevice#createReprocessableCaptureSession
+     * @see CameraDevice#createConstrainedHighSpeedCaptureSession
+     */
+    public SessionConfiguration(@SessionMode int sessionType,
+            @NonNull List<OutputConfiguration> outputs,
+            @NonNull CameraCaptureSession.StateCallback cb, @Nullable Handler handler) {
+        mSessionType = sessionType;
+        mOutputConfigurations = Collections.unmodifiableList(new ArrayList<>(outputs));
+        mStateCallback = cb;
+        mHandler = handler;
+    }
+
+    /**
+     * Retrieve the type of the capture session.
+     *
+     * @return The capture session type.
+     */
+    public @SessionMode int getSessionType() {
+        return mSessionType;
+    }
+
+    /**
+     * Retrieve the {@link OutputConfiguration} list for the capture session.
+     *
+     * @return A list of output configurations for the capture session.
+     */
+    public List<OutputConfiguration> getOutputConfigurations() {
+        return mOutputConfigurations;
+    }
+
+    /**
+     * Retrieve the {@link CameraCaptureSession.StateCallback} for the capture session.
+     *
+     * @return A state callback interface implementation.
+     */
+    public CameraCaptureSession.StateCallback getStateCallback() {
+        return mStateCallback;
+    }
+
+    /**
+     * Retrieve the {@link CameraCaptureSession.StateCallback} for the capture session.
+     *
+     * @return The handler on which the callback will be invoked. If it is
+     *         set to null, the callback will be invoked on the current thread's
+     *         {@link android.os.Looper looper}.
+     */
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    /**
+     * Sets the {@link InputConfiguration} for a reprocessable session. Input configuration are not
+     * supported for {@link #SESSION_HIGH_SPEED}.
+     *
+     * @param input Input configuration.
+     * @throws UnsupportedOperationException In case it is called for {@link #SESSION_HIGH_SPEED}
+     *                                       type session configuration.
+     */
+    public void setInputConfiguration(@NonNull InputConfiguration input) {
+        if (mSessionType != SESSION_HIGH_SPEED) {
+            mInputConfig = input;
+        } else {
+            throw new UnsupportedOperationException("Method not supported for high speed session" +
+                    " types");
+        }
+    }
+
+    /**
+     * Retrieve the {@link InputConfiguration}.
+     *
+     * @return The capture session input configuration.
+     */
+    public InputConfiguration getInputConfiguration() {
+        return mInputConfig;
+    }
+
+    /**
+     * Sets the session wide camera parameters (see {@link CaptureRequest}). This argument can
+     * be set for every supported session type and will be passed to the camera device as part
+     * of the capture session initialization. Session parameters are a subset of the available
+     * capture request parameters (see {@link CameraCharacteristics#getAvailableSessionKeys})
+     * and their application can introduce internal camera delays. To improve camera performance
+     * it is suggested to change them sparingly within the lifetime of the capture session and
+     * to pass their initial values as part of this method.
+     *
+     * @param params A capture request that includes the initial values for any available
+     *               session wide capture keys.
+     */
+    public void setSessionParameters(CaptureRequest params) {
+        mSessionParameters = params;
+    }
+
+    /**
+     * Retrieve the session wide camera parameters (see {@link CaptureRequest}).
+     *
+     * @return A capture request that includes the initial values for any available
+     *         session wide capture keys.
+     */
+    public CaptureRequest getSessionParameters() {
+        return mSessionParameters;
+    }
+}
diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java
index 3003607..0a08353 100644
--- a/core/java/android/hardware/display/BrightnessChangeEvent.java
+++ b/core/java/android/hardware/display/BrightnessChangeEvent.java
@@ -28,7 +28,7 @@
  */
 public final class BrightnessChangeEvent implements Parcelable {
     /** Brightness in nits */
-    public int brightness;
+    public float brightness;
 
     /** Timestamp of the change {@see System.currentTimeMillis()} */
     public long timeStamp;
@@ -58,7 +58,7 @@
     public int colorTemperature;
 
     /** Brightness level before slider adjustment */
-    public int lastBrightness;
+    public float lastBrightness;
 
     public BrightnessChangeEvent() {
     }
@@ -78,7 +78,7 @@
     }
 
     private BrightnessChangeEvent(Parcel source) {
-        brightness = source.readInt();
+        brightness = source.readFloat();
         timeStamp = source.readLong();
         packageName = source.readString();
         userId = source.readInt();
@@ -87,7 +87,7 @@
         batteryLevel = source.readFloat();
         nightMode = source.readBoolean();
         colorTemperature = source.readInt();
-        lastBrightness = source.readInt();
+        lastBrightness = source.readFloat();
     }
 
     public static final Creator<BrightnessChangeEvent> CREATOR =
@@ -107,7 +107,7 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(brightness);
+        dest.writeFloat(brightness);
         dest.writeLong(timeStamp);
         dest.writeString(packageName);
         dest.writeInt(userId);
@@ -116,6 +116,6 @@
         dest.writeFloat(batteryLevel);
         dest.writeBoolean(nightMode);
         dest.writeInt(colorTemperature);
-        dest.writeInt(lastBrightness);
+        dest.writeFloat(lastBrightness);
     }
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 7de667d..97e9b9c 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -628,13 +628,6 @@
     }
 
     /**
-     * @hide STOPSHIP - remove when adaptive brightness accepts curves.
-     */
-    public void setBrightness(int brightness) {
-        mGlobal.setBrightness(brightness);
-    }
-
-    /**
      * Sets the global display brightness configuration.
      *
      * @hide
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index bf4cc1d..cbb5a7d 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -476,18 +476,6 @@
     }
 
     /**
-     * Set brightness but don't add a BrightnessChangeEvent
-     * STOPSHIP remove when adaptive brightness accepts curves.
-     */
-    public void setBrightness(int brightness) {
-        try {
-            mDm.setBrightness(brightness);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Sets the global brightness configuration for a given user.
      *
      * @hide
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index cd551bd..3f6dd2e 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -222,6 +222,11 @@
         // set by the user as opposed to being programmatically controlled by apps.
         public boolean brightnessSetByUser;
 
+        // Set to true if screenBrightness or screenAutoBrightnessAdjustment are being set
+        // temporarily. This is typically set while the user has their finger on the brightness
+        // control, before they've selected the final brightness value.
+        public boolean brightnessIsTemporary;
+
         // If true, enables automatic brightness control.
         public boolean useAutoBrightness;
 
@@ -280,6 +285,7 @@
             screenAutoBrightnessAdjustment = other.screenAutoBrightnessAdjustment;
             screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor;
             brightnessSetByUser = other.brightnessSetByUser;
+            brightnessIsTemporary = other.brightnessIsTemporary;
             useAutoBrightness = other.useAutoBrightness;
             blockScreenOn = other.blockScreenOn;
             lowPowerMode = other.lowPowerMode;
@@ -303,6 +309,7 @@
                     && screenLowPowerBrightnessFactor
                     == other.screenLowPowerBrightnessFactor
                     && brightnessSetByUser == other.brightnessSetByUser
+                    && brightnessIsTemporary == other.brightnessIsTemporary
                     && useAutoBrightness == other.useAutoBrightness
                     && blockScreenOn == other.blockScreenOn
                     && lowPowerMode == other.lowPowerMode
@@ -324,6 +331,7 @@
                     + ", screenAutoBrightnessAdjustment=" + screenAutoBrightnessAdjustment
                     + ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor
                     + ", brightnessSetByUser=" + brightnessSetByUser
+                    + ", brightnessIsTemporary=" + brightnessIsTemporary
                     + ", useAutoBrightness=" + useAutoBrightness
                     + ", blockScreenOn=" + blockScreenOn
                     + ", lowPowerMode=" + lowPowerMode
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 8afae6e..61c42e1 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -87,10 +87,6 @@
     // Requires BRIGHTNESS_SLIDER_USAGE permission.
     ParceledListSlice getBrightnessEvents(String callingPackage);
 
-    // STOPSHIP remove when adaptive brightness code is updated to accept curves.
-    // Requires BRIGHTNESS_SLIDER_USAGE permission.
-    void setBrightness(int brightness);
-
     // Sets the global brightness configuration for a given user. Requires
     // CONFIGURE_DISPLAY_BRIGHTNESS, and INTERACT_ACROSS_USER if the user being configured is not
     // the same as the calling user.
diff --git a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
index 6fe13ca..d7c2e1b 100644
--- a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
+++ b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
@@ -187,7 +187,6 @@
      * Base class for time-related information.
      * @hide
      */
-    @SystemApi
     /* package */ static class TimeUnit {
         /* package */ final int mHour;
         /* package */ final int mMinute;
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 52527ed..0a21083 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -15,9 +15,13 @@
  */
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.os.RemoteException;
 
+import com.android.internal.util.Preconditions;
+
 import dalvik.system.CloseGuard;
 
 import java.io.Closeable;
@@ -31,16 +35,12 @@
  *
  * @hide
  */
+@SystemApi
 public class ContextHubClient implements Closeable {
     /*
      * The proxy to the client interface at the service.
      */
-    private final IContextHubClient mClientProxy;
-
-    /*
-     * The callback interface associated with this client.
-     */
-    private final IContextHubClientCallback mCallbackInterface;
+    private IContextHubClient mClientProxy = null;
 
     /*
      * The Context Hub that this client is attached to.
@@ -51,20 +51,33 @@
 
     private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
 
-    /* package */ ContextHubClient(
-            IContextHubClient clientProxy, IContextHubClientCallback callback,
-            ContextHubInfo hubInfo) {
-        mClientProxy = clientProxy;
-        mCallbackInterface = callback;
+    /* package */ ContextHubClient(ContextHubInfo hubInfo) {
         mAttachedHub = hubInfo;
         mCloseGuard.open("close");
     }
 
     /**
+     * Sets the proxy interface of the client at the service. This method should always be called
+     * by the ContextHubManager after the client is registered at the service, and should only be
+     * called once.
+     *
+     * @param clientProxy the proxy of the client at the service
+     */
+    /* package */ void setClientProxy(IContextHubClient clientProxy) {
+        Preconditions.checkNotNull(clientProxy, "IContextHubClient cannot be null");
+        if (mClientProxy != null) {
+            throw new IllegalStateException("Cannot change client proxy multiple times");
+        }
+
+        mClientProxy = clientProxy;
+    }
+
+    /**
      * Returns the hub that this client is attached to.
      *
      * @return the ContextHubInfo of the attached hub
      */
+    @NonNull
     public ContextHubInfo getAttachedHub() {
         return mAttachedHub;
     }
@@ -96,12 +109,16 @@
      *
      * @return the result of sending the message defined as in ContextHubTransaction.Result
      *
+     * @throws NullPointerException if NanoAppMessage is null
+     *
      * @see NanoAppMessage
      * @see ContextHubTransaction.Result
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     @ContextHubTransaction.Result
-    public int sendMessageToNanoApp(NanoAppMessage message) {
+    public int sendMessageToNanoApp(@NonNull NanoAppMessage message) {
+        Preconditions.checkNotNull(message, "NanoAppMessage cannot be null");
+
         try {
             return mClientProxy.sendMessageToNanoApp(message);
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
index ab19d54..cc2fe65 100644
--- a/core/java/android/hardware/location/ContextHubClientCallback.java
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -15,15 +15,20 @@
  */
 package android.hardware.location;
 
+import android.annotation.SystemApi;
+
+import java.util.concurrent.Executor;
+
 /**
  * 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:
+ * This callback is registered through the {@link
+ * android.hardware.location.ContextHubManager#createClient(
+ * ContextHubInfo, ContextHubClientCallback, Executor) 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
@@ -31,6 +36,7 @@
  *
  * @hide
  */
+@SystemApi
 public class ContextHubClientCallback {
     /**
      * Callback invoked when receiving a message from a nanoapp.
@@ -38,48 +44,56 @@
      * The message contents of this callback may either be broadcasted or targeted to the
      * client receiving the invocation.
      *
+     * @param client the client that is associated with this callback
      * @param message the message sent by the nanoapp
      */
-    public void onMessageFromNanoApp(NanoAppMessage message) {}
+    public void onMessageFromNanoApp(ContextHubClient client, NanoAppMessage message) {}
 
     /**
      * Callback invoked when the attached Context Hub has reset.
+     *
+     * @param client the client that is associated with this callback
      */
-    public void onHubReset() {}
+    public void onHubReset(ContextHubClient client) {}
 
     /**
      * Callback invoked when a nanoapp aborts at the attached Context Hub.
      *
+     * @param client the client that is associated with this callback
      * @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) {}
+    public void onNanoAppAborted(ContextHubClient client, long nanoAppId, int abortCode) {}
 
     /**
      * Callback invoked when a nanoapp is loaded at the attached Context Hub.
      *
+     * @param client the client that is associated with this callback
      * @param nanoAppId the ID of the nanoapp that had been loaded
      */
-    public void onNanoAppLoaded(long nanoAppId) {}
+    public void onNanoAppLoaded(ContextHubClient client, long nanoAppId) {}
 
     /**
      * Callback invoked when a nanoapp is unloaded from the attached Context Hub.
      *
+     * @param client the client that is associated with this callback
      * @param nanoAppId the ID of the nanoapp that had been unloaded
      */
-    public void onNanoAppUnloaded(long nanoAppId) {}
+    public void onNanoAppUnloaded(ContextHubClient client, long nanoAppId) {}
 
     /**
      * Callback invoked when a nanoapp is enabled at the attached Context Hub.
      *
+     * @param client the client that is associated with this callback
      * @param nanoAppId the ID of the nanoapp that had been enabled
      */
-    public void onNanoAppEnabled(long nanoAppId) {}
+    public void onNanoAppEnabled(ContextHubClient client, long nanoAppId) {}
 
     /**
      * Callback invoked when a nanoapp is disabled at the attached Context Hub.
      *
+     * @param client the client that is associated with this callback
      * @param nanoAppId the ID of the nanoapp that had been disabled
      */
-    public void onNanoAppDisabled(long nanoAppId) {}
+    public void onNanoAppDisabled(ContextHubClient client, long nanoAppId) {}
 }
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index c2b2800..36123e3 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -221,9 +221,6 @@
 
     /**
      * @return the CHRE platform ID as defined in chre/version.h
-     *
-     * TODO(b/67734082): Expose as public API
-     * @hide
      */
     public long getChrePlatformId() {
         return mChrePlatformId;
@@ -231,9 +228,6 @@
 
     /**
      * @return the CHRE API's major version as defined in chre/version.h
-     *
-     * TODO(b/67734082): Expose as public API
-     * @hide
      */
     public byte getChreApiMajorVersion() {
         return mChreApiMajorVersion;
@@ -241,9 +235,6 @@
 
     /**
      * @return the CHRE API's minor version as defined in chre/version.h
-     *
-     * TODO(b/67734082): Expose as public API
-     * @hide
      */
     public byte getChreApiMinorVersion() {
         return mChreApiMinorVersion;
@@ -251,9 +242,6 @@
 
     /**
      * @return the CHRE patch version as defined in chre/version.h
-     *
-     * TODO(b/67734082): Expose as public API
-     * @hide
      */
     public short getChrePatchVersion() {
         return mChrePatchVersion;
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index b7ce875..de13c81 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -15,6 +15,8 @@
  */
 package android.hardware.location;
 
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
@@ -22,13 +24,17 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * A class that exposes the Context hubs on a device to applications.
@@ -56,7 +62,11 @@
 
     /**
      * An interface to receive asynchronous communication from the context hub.
+     *
+     * @deprecated Use the more refined {@link android.hardware.location.ContextHubClientCallback}
+     *             instead for notification callbacks.
      */
+    @Deprecated
     public abstract static class Callback {
         protected Callback() {}
 
@@ -72,7 +82,7 @@
         public abstract void onMessageReceipt(
                 int hubHandle,
                 int nanoAppHandle,
-                ContextHubMessage message);
+                @NonNull ContextHubMessage message);
     }
 
     /**
@@ -95,8 +105,13 @@
 
     /**
      * Get a handle to all the context hubs in the system
+     *
      * @return array of context hub handles
+     *
+     * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the
+     *             new APIs.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public int[] getContextHubHandles() {
         try {
@@ -113,7 +128,11 @@
      * @return ContextHubInfo Information about the requested context hub.
      *
      * @see ContextHubInfo
+     *
+     * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the
+     *             new APIs.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public ContextHubInfo getContextHubInfo(int hubHandle) {
         try {
@@ -141,9 +160,12 @@
      *         -1 otherwise
      *
      * @see NanoApp
+     *
+     * @deprecated Use {@link #loadNanoApp(ContextHubInfo, NanoAppBinary)} instead.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public int loadNanoApp(int hubHandle, NanoApp app) {
+    public int loadNanoApp(int hubHandle, @NonNull NanoApp app) {
         try {
             return mService.loadNanoApp(hubHandle, app);
         } catch (RemoteException e) {
@@ -165,7 +187,10 @@
      *
      * @return 0 if the command for unloading was sent to the context hub;
      *         -1 otherwise
+     *
+     * @deprecated Use {@link #unloadNanoApp(ContextHubInfo, long)} instead.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public int unloadNanoApp(int nanoAppHandle) {
         try {
@@ -196,13 +221,18 @@
      * TODO(b/30943489): Have the returned NanoAppInstanceInfo contain the
      *     correct information.
      *
-     * @param nanoAppHandle handle of the nanoAppInstance
-     * @return NanoAppInstanceInfo Information about the nano app instance.
+     * @param nanoAppHandle handle of the nanoapp instance
+     * @return NanoAppInstanceInfo the NanoAppInstanceInfo of the nanoapp, or null if the nanoapp
+     *                             does not exist
      *
      * @see NanoAppInstanceInfo
+     *
+     * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub
+     *             for loaded nanoapps.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) {
+    @Nullable public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) {
         try {
             return mService.getNanoAppInstanceInfo(nanoAppHandle);
         } catch (RemoteException e) {
@@ -219,9 +249,13 @@
      * @see NanoAppFilter
      *
      * @return int[] Array of handles to any found nano apps
+     *
+     * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub
+     *             for loaded nanoapps.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public int[] findNanoAppOnHub(int hubHandle, NanoAppFilter filter) {
+    @NonNull public int[] findNanoAppOnHub(int hubHandle, @NonNull NanoAppFilter filter) {
         try {
             return mService.findNanoAppOnHub(hubHandle, filter);
         } catch (RemoteException e) {
@@ -247,9 +281,16 @@
      * @see ContextHubMessage
      *
      * @return int 0 on success, -1 otherwise
+     *
+     * @deprecated Use {@link android.hardware.location.ContextHubClient#sendMessageToNanoApp(
+     *             NanoAppMessage)} instead, after creating a
+     *             {@link android.hardware.location.ContextHubClient} with
+     *             {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
+     *             or {@link #createClient(ContextHubInfo, ContextHubClientCallback)}.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage message) {
+    public int sendMessage(int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message) {
         try {
             return mService.sendMessage(hubHandle, nanoAppHandle, message);
         } catch (RemoteException e) {
@@ -263,11 +304,9 @@
      * @return the list of ContextHubInfo objects
      *
      * @see ContextHubInfo
-     *
-     * @hide
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public List<ContextHubInfo> getContextHubs() {
+    @NonNull public List<ContextHubInfo> getContextHubs() {
         try {
             return mService.getContextHubs();
         } catch (RemoteException e) {
@@ -339,13 +378,16 @@
      *
      * @return the ContextHubTransaction of the request
      *
-     * @see NanoAppBinary
+     * @throws NullPointerException if hubInfo or NanoAppBinary is null
      *
-     * @hide
+     * @see NanoAppBinary
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public ContextHubTransaction<Void> loadNanoApp(
-            ContextHubInfo hubInfo, NanoAppBinary appBinary) {
+    @NonNull public ContextHubTransaction<Void> loadNanoApp(
+            @NonNull ContextHubInfo hubInfo, @NonNull NanoAppBinary appBinary) {
+        Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+        Preconditions.checkNotNull(appBinary, "NanoAppBinary cannot be null");
+
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_LOAD_NANOAPP);
         IContextHubTransactionCallback callback = createTransactionCallback(transaction);
@@ -367,10 +409,13 @@
      *
      * @return the ContextHubTransaction of the request
      *
-     * @hide
+     * @throws NullPointerException if hubInfo is null
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public ContextHubTransaction<Void> unloadNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+    @NonNull public ContextHubTransaction<Void> unloadNanoApp(
+            @NonNull ContextHubInfo hubInfo, long nanoAppId) {
+        Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_UNLOAD_NANOAPP);
         IContextHubTransactionCallback callback = createTransactionCallback(transaction);
@@ -392,10 +437,13 @@
      *
      * @return the ContextHubTransaction of the request
      *
-     * @hide
+     * @throws NullPointerException if hubInfo is null
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public ContextHubTransaction<Void> enableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+    @NonNull public ContextHubTransaction<Void> enableNanoApp(
+            @NonNull ContextHubInfo hubInfo, long nanoAppId) {
+        Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_ENABLE_NANOAPP);
         IContextHubTransactionCallback callback = createTransactionCallback(transaction);
@@ -417,10 +465,13 @@
      *
      * @return the ContextHubTransaction of the request
      *
-     * @hide
+     * @throws NullPointerException if hubInfo is null
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public ContextHubTransaction<Void> disableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+    @NonNull public ContextHubTransaction<Void> disableNanoApp(
+            @NonNull ContextHubInfo hubInfo, long nanoAppId) {
+        Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_DISABLE_NANOAPP);
         IContextHubTransactionCallback callback = createTransactionCallback(transaction);
@@ -441,10 +492,13 @@
      *
      * @return the ContextHubTransaction of the request
      *
-     * @hide
+     * @throws NullPointerException if hubInfo is null
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public ContextHubTransaction<List<NanoAppState>> queryNanoApps(ContextHubInfo hubInfo) {
+    @NonNull public ContextHubTransaction<List<NanoAppState>> queryNanoApps(
+            @NonNull ContextHubInfo hubInfo) {
+        Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+
         ContextHubTransaction<List<NanoAppState>> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_QUERY_NANOAPPS);
         IContextHubTransactionCallback callback = createQueryCallback(transaction);
@@ -466,9 +520,14 @@
      * @see Callback
      *
      * @return int 0 on success, -1 otherwise
+     *
+     * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
+     *             or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to
+     *             register a {@link android.hardware.location.ContextHubClientCallback}.
      */
+    @Deprecated
     @SuppressLint("Doclava125")
-    public int registerCallback(Callback callback) {
+    public int registerCallback(@NonNull Callback callback) {
         return registerCallback(callback, null);
     }
 
@@ -495,7 +554,12 @@
      * @see Callback
      *
      * @return int 0 on success, -1 otherwise
+     *
+     * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
+     *             or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to
+     *             register a {@link android.hardware.location.ContextHubClientCallback}.
      */
+    @Deprecated
     @SuppressLint("Doclava125")
     public int registerCallback(Callback callback, Handler handler) {
         synchronized(this) {
@@ -512,47 +576,48 @@
     /**
      * Creates an interface to the ContextHubClient to send down to the service.
      *
+     * @param client the ContextHubClient object associated with this callback
      * @param callback the callback to invoke at the client process
-     * @param handler the handler to post callbacks for this client
+     * @param executor the executor to invoke callbacks for this client
      *
      * @return the callback interface
      */
     private IContextHubClientCallback createClientCallback(
-            ContextHubClientCallback callback, Handler handler) {
+            ContextHubClient client, ContextHubClientCallback callback, Executor executor) {
         return new IContextHubClientCallback.Stub() {
             @Override
             public void onMessageFromNanoApp(NanoAppMessage message) {
-                handler.post(() -> callback.onMessageFromNanoApp(message));
+                executor.execute(() -> callback.onMessageFromNanoApp(client, message));
             }
 
             @Override
             public void onHubReset() {
-                handler.post(() -> callback.onHubReset());
+                executor.execute(() -> callback.onHubReset(client));
             }
 
             @Override
             public void onNanoAppAborted(long nanoAppId, int abortCode) {
-                handler.post(() -> callback.onNanoAppAborted(nanoAppId, abortCode));
+                executor.execute(() -> callback.onNanoAppAborted(client, nanoAppId, abortCode));
             }
 
             @Override
             public void onNanoAppLoaded(long nanoAppId) {
-                handler.post(() -> callback.onNanoAppLoaded(nanoAppId));
+                executor.execute(() -> callback.onNanoAppLoaded(client, nanoAppId));
             }
 
             @Override
             public void onNanoAppUnloaded(long nanoAppId) {
-                handler.post(() -> callback.onNanoAppUnloaded(nanoAppId));
+                executor.execute(() -> callback.onNanoAppUnloaded(client, nanoAppId));
             }
 
             @Override
             public void onNanoAppEnabled(long nanoAppId) {
-                handler.post(() -> callback.onNanoAppEnabled(nanoAppId));
+                executor.execute(() -> callback.onNanoAppEnabled(client, nanoAppId));
             }
 
             @Override
             public void onNanoAppDisabled(long nanoAppId) {
-                handler.post(() -> callback.onNanoAppDisabled(nanoAppId));
+                executor.execute(() -> callback.onNanoAppDisabled(client, nanoAppId));
             }
         };
     }
@@ -564,38 +629,56 @@
      * 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 main thread's Looper
+     * @param callback the notification callback to register
+     * @param executor the executor to invoke the callback
+     * @return the registered client object
+     *
+     * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+     * @throws IllegalStateException    if there were too many registered clients at the service
+     * @throws NullPointerException     if callback, hubInfo, or executor is null
+     *
+     * @see ContextHubClientCallback
+     */
+    @NonNull public ContextHubClient createClient(
+            @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
+            @NonNull @CallbackExecutor Executor executor) {
+        Preconditions.checkNotNull(callback, "Callback cannot be null");
+        Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+        Preconditions.checkNotNull(executor, "Executor cannot be null");
+
+        ContextHubClient client = new ContextHubClient(hubInfo);
+        IContextHubClientCallback clientInterface = createClientCallback(
+                client, callback, executor);
+
+        IContextHubClient clientProxy;
+        try {
+            clientProxy = mService.createClient(clientInterface, hubInfo.getId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        client.setClientProxy(clientProxy);
+        return client;
+    }
+
+    /**
+     * Equivalent to {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
+     * with the executor using the main thread's Looper.
+     *
+     * @param hubInfo  the hub to attach this client to
+     * @param callback the notification callback to register
      * @return the registered client object
      *
      * @throws IllegalArgumentException if hubInfo does not represent a valid hub
      * @throws IllegalStateException    if there were too many registered clients at the service
      * @throws NullPointerException     if callback or hubInfo is null
      *
-     * @hide
      * @see ContextHubClientCallback
      */
-    public ContextHubClient createClient(
-            ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) {
-        if (callback == null) {
-            throw new NullPointerException("Callback cannot be null");
-        }
-        if (hubInfo == null) {
-            throw new NullPointerException("Hub info cannot be null");
-        }
-
-        Handler realHandler = (handler == null) ? new Handler(mMainLooper) : handler;
-        IContextHubClientCallback clientInterface = createClientCallback(callback, realHandler);
-
-        IContextHubClient client;
-        try {
-            client = mService.createClient(clientInterface, hubInfo.getId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
-        return new ContextHubClient(client, clientInterface, hubInfo);
+    @NonNull public ContextHubClient createClient(
+            @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback) {
+        return createClient(hubInfo, callback, new HandlerExecutor(Handler.getMain()));
     }
 
     /**
@@ -606,9 +689,13 @@
      * @param callback method to deregister
      *
      * @return int 0 on success, -1 otherwise
+     *
+     * @deprecated Use {@link android.hardware.location.ContextHubClient#close()} to unregister
+     *             a {@link android.hardware.location.ContextHubClientCallback}.
      */
     @SuppressLint("Doclava125")
-    public int unregisterCallback(Callback callback) {
+    @Deprecated
+    public int unregisterCallback(@NonNull Callback callback) {
       synchronized(this) {
           if (callback != mCallback) {
               Log.w(TAG, "Cannot recognize callback!");
@@ -657,8 +744,6 @@
                 synchronized (this) {
                     mLocalCallback.onMessageReceipt(hubId, nanoAppId, message);
                 }
-            } else {
-                Log.d(TAG, "Context hub manager client callback is NULL");
             }
         }
     };
@@ -672,7 +757,7 @@
         try {
             mService.registerCallback(mClientCallback);
         } catch (RemoteException e) {
-            Log.w(TAG, "Could not register callback:" + e);
+            throw e.rethrowFromSystemServer();
         }
     }
 }
diff --git a/core/java/android/hardware/location/ContextHubMessage.java b/core/java/android/hardware/location/ContextHubMessage.java
index bca2ae6..2a4ad00 100644
--- a/core/java/android/hardware/location/ContextHubMessage.java
+++ b/core/java/android/hardware/location/ContextHubMessage.java
@@ -19,14 +19,20 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Log;
 
 import java.util.Arrays;
 
 /**
+ * @deprecated Use {@link android.hardware.location.NanoAppMessage} instead to send messages with
+ *             {@link android.hardware.location.ContextHubClient#sendMessageToNanoApp(
+ *             NanoAppMessage)} and receive messages with
+ *             {@link android.hardware.location.ContextHubClientCallback#onMessageFromNanoApp(
+ *             ContextHubClient, NanoAppMessage)}.
+ *
  * @hide
  */
 @SystemApi
+@Deprecated
 public class ContextHubMessage {
     private int mType;
     private int mVersion;
@@ -34,7 +40,6 @@
 
     private static final String TAG = "ContextHubMessage";
 
-
     /**
      * Get the message type
      *
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index ec1e68f..bc7efef 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -15,15 +15,19 @@
  */
 package android.hardware.location;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
+import android.os.HandlerExecutor;
+
+import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -33,18 +37,20 @@
  * 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 #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}).
+ * asynchronously through a user-defined listener
+ * ({@link #setOnCompleteListener(OnCompleteListener, Executor)} )}).
  *
  * @param <T> the type of the contents in the transaction response
  *
  * @hide
  */
+@SystemApi
 public class ContextHubTransaction<T> {
     private static final String TAG = "ContextHubTransaction";
 
     /**
      * Constants describing the type of a transaction through the Context Hub Service.
+     * {@hide}
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "TYPE_" }, value = {
@@ -64,6 +70,7 @@
 
     /**
      * Constants describing the result of a transaction or request through the Context Hub Service.
+     * {@hide}
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "RESULT_" }, value = {
@@ -71,7 +78,7 @@
             RESULT_FAILED_UNKNOWN,
             RESULT_FAILED_BAD_PARAMS,
             RESULT_FAILED_UNINITIALIZED,
-            RESULT_FAILED_PENDING,
+            RESULT_FAILED_BUSY,
             RESULT_FAILED_AT_HUB,
             RESULT_FAILED_TIMEOUT,
             RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
@@ -94,7 +101,7 @@
     /**
      * Failure mode when there are too many transactions pending.
      */
-    public static final int RESULT_FAILED_PENDING = 4;
+    public static final int RESULT_FAILED_BUSY = 4;
     /**
      * Failure mode when the request went through, but failed asynchronously at the hub.
      */
@@ -145,20 +152,20 @@
     }
 
     /**
-     * An interface describing the callback to be invoked when a transaction completes.
+     * An interface describing the listener for a transaction completion.
      *
-     * @param <C> the type of the contents in the transaction response
+     * @param <L> the type of the contents in the transaction response
      */
     @FunctionalInterface
-    public interface Callback<C> {
+    public interface OnCompleteListener<L> {
         /**
-         * The callback to invoke when the transaction completes.
+         * The listener function 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);
+                ContextHubTransaction<L> transaction, ContextHubTransaction.Response<L> response);
     }
 
     /*
@@ -173,14 +180,14 @@
     private ContextHubTransaction.Response<T> mResponse;
 
     /*
-     * The handler to invoke the aynsc response supplied by onComplete.
+     * The executor to invoke the onComplete async callback.
      */
-    private Handler mHandler = null;
+    private Executor mExecutor = null;
 
     /*
-     * The callback to invoke when the transaction completes.
+     * The listener to be invoked when the transaction completes.
      */
-    private ContextHubTransaction.Callback<T> mCallback = null;
+    private ContextHubTransaction.OnCompleteListener<T> mListener = null;
 
     /*
      * Synchronization latch used to block on response.
@@ -258,73 +265,65 @@
     }
 
     /**
-     * Sets a callback to be invoked when the transaction completes.
+     * Sets the listener to be invoked 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.
+     * the given listener will be invoked.
      *
-     * 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.
+     * If the transaction has already completed at the time of invocation, the listener
+     * will be immediately invoked. If the transaction has been invalidated,
+     * the listener will never be invoked.
      *
      * A transaction can be invalidated if the process owning the transaction is no longer active
      * and the reference to this object is lost.
      *
-     * This method or {@link #setOnCompleteCallback(ContextHubTransaction.Callback)} can only be
-     * invoked once, or an IllegalStateException will be thrown.
+     * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener)} can
+     * only be invoked once, or an IllegalStateException will be thrown.
      *
-     * @param callback the callback to be invoked upon completion
-     * @param handler the handler to post the callback
+     * @param listener the listener to be invoked upon completion
+     * @param executor the executor to invoke the callback
      *
      * @throws IllegalStateException if this method is called multiple times
      * @throws NullPointerException if the callback or handler is null
      */
-    public void setOnCompleteCallback(
-            @NonNull ContextHubTransaction.Callback<T> callback, @NonNull Handler handler) {
+    public void setOnCompleteListener(
+            @NonNull ContextHubTransaction.OnCompleteListener<T> listener,
+            @NonNull @CallbackExecutor Executor executor) {
         synchronized (this) {
-            if (callback == null) {
-                throw new NullPointerException("Callback cannot be null");
-            }
-            if (handler == null) {
-                throw new NullPointerException("Handler cannot be null");
-            }
-            if (mCallback != null) {
+            Preconditions.checkNotNull(listener, "OnCompleteListener cannot be null");
+            Preconditions.checkNotNull(executor, "Executor cannot be null");
+            if (mListener != null) {
                 throw new IllegalStateException(
-                        "Cannot set ContextHubTransaction callback multiple times");
+                        "Cannot set ContextHubTransaction listener multiple times");
             }
 
-            mCallback = callback;
-            mHandler = handler;
+            mListener = listener;
+            mExecutor = executor;
 
             if (mDoneSignal.getCount() == 0) {
-                boolean callbackPosted = mHandler.post(() -> {
-                    mCallback.onComplete(this, mResponse);
-                });
-
-                if (!callbackPosted) {
-                    Log.e(TAG, "Failed to post callback to Handler");
-                }
+                mExecutor.execute(() -> mListener.onComplete(this, mResponse));
             }
         }
     }
 
     /**
-     * Sets a callback to be invoked when the transaction completes.
+     * Sets the listener to be invoked invoked when the transaction completes.
      *
-     * Equivalent to {@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}
-     * with the handler being that of the main thread's Looper.
+     * Equivalent to {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener,
+     * Executor)} with the executor using the main thread's Looper.
      *
-     * This method or {@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}
-     * can only be invoked once, or an IllegalStateException will be thrown.
+     * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener,
+     * Executor)} can only be invoked once, or an IllegalStateException will be thrown.
      *
-     * @param callback the callback to be invoked upon completion
+     * @param listener the listener to be invoked upon completion
      *
      * @throws IllegalStateException if this method is called multiple times
      * @throws NullPointerException if the callback is null
      */
-    public void setOnCompleteCallback(@NonNull ContextHubTransaction.Callback<T> callback) {
-        setOnCompleteCallback(callback, new Handler(Looper.getMainLooper()));
+    public void setOnCompleteListener(
+            @NonNull ContextHubTransaction.OnCompleteListener<T> listener) {
+        setOnCompleteListener(listener, new HandlerExecutor(Handler.getMain()));
     }
 
     /**
@@ -339,11 +338,9 @@
      * @throws IllegalStateException if this method is invoked multiple times
      * @throws NullPointerException if the response is null
      */
-    void setResponse(ContextHubTransaction.Response<T> response) {
+    /* package */ void setResponse(ContextHubTransaction.Response<T> response) {
         synchronized (this) {
-            if (response == null) {
-                throw new NullPointerException("Response cannot be null");
-            }
+            Preconditions.checkNotNull(response, "Response cannot be null");
             if (mIsResponseSet) {
                 throw new IllegalStateException(
                         "Cannot set response of ContextHubTransaction multiple times");
@@ -353,14 +350,8 @@
             mIsResponseSet = true;
 
             mDoneSignal.countDown();
-            if (mCallback != null) {
-                boolean callbackPosted = mHandler.post(() -> {
-                    mCallback.onComplete(this, mResponse);
-                });
-
-                if (!callbackPosted) {
-                    Log.e(TAG, "Failed to post callback to Handler");
-                }
+            if (mListener != null) {
+                mExecutor.execute(() -> mListener.onComplete(this, mResponse));
             }
         }
     }
diff --git a/core/java/android/hardware/location/NanoApp.java b/core/java/android/hardware/location/NanoApp.java
index 0465def..b5c01ec 100644
--- a/core/java/android/hardware/location/NanoApp.java
+++ b/core/java/android/hardware/location/NanoApp.java
@@ -28,9 +28,14 @@
  * Nano apps are expected to be used only by bundled apps only
  * at this time.
  *
+ * @deprecated Use {@link android.hardware.location.NanoAppBinary} instead to load a nanoapp with
+ *             {@link android.hardware.location.ContextHubManager#loadNanoApp(
+ *             ContextHubInfo, NanoAppBinary)}.
+ *
  * @hide
  */
 @SystemApi
+@Deprecated
 public class NanoApp {
     private final String TAG = "NanoApp";
 
diff --git a/core/java/android/hardware/location/NanoAppBinary.java b/core/java/android/hardware/location/NanoAppBinary.java
index 934e9e4..ba01ca2 100644
--- a/core/java/android/hardware/location/NanoAppBinary.java
+++ b/core/java/android/hardware/location/NanoAppBinary.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
@@ -27,6 +28,7 @@
 /**
  * @hide
  */
+@SystemApi
 public final class NanoAppBinary implements Parcelable {
     private static final String TAG = "NanoAppBinary";
 
diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java
index 5ccf546..75a96ee 100644
--- a/core/java/android/hardware/location/NanoAppFilter.java
+++ b/core/java/android/hardware/location/NanoAppFilter.java
@@ -16,15 +16,18 @@
 
 package android.hardware.location;
 
-
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 /**
+ * @deprecated Use {@link android.hardware.location.ContextHubManager#queryNanoApps(ContextHubInfo)}
+ *             to find loaded nanoapps, which doesn't require using this class as a parameter.
+ *
  * @hide
  */
 @SystemApi
+@Deprecated
 public class NanoAppFilter {
 
     private static final String TAG = "NanoAppFilter";
diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java
index f73fd87..f1926eaa 100644
--- a/core/java/android/hardware/location/NanoAppInstanceInfo.java
+++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java
@@ -28,9 +28,12 @@
  *
  * TODO(b/69270990) Remove this class once the old API is deprecated.
  *
+ * @deprecated Use {@link android.hardware.location.NanoAppState} instead.
+ *
  * @hide
  */
 @SystemApi
+@Deprecated
 public class NanoAppInstanceInfo {
     private String mPublisher = "Unknown";
     private String mName = "Unknown";
@@ -90,11 +93,6 @@
     /**
      * Get the application version
      *
-     * NOTE: There is a race condition where shortly after loading, this
-     * may return -1 instead of the correct version.
-     *
-     * TODO(b/30970527): Fix this race condition.
-     *
      * @return int - version of the app
      */
     public int getAppVersion() {
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 2028674..716a194 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -25,6 +26,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class NanoAppMessage implements Parcelable {
     private long mNanoAppId;
     private int mMessageType;
diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java
index 644031b..d05277d 100644
--- a/core/java/android/hardware/location/NanoAppState.java
+++ b/core/java/android/hardware/location/NanoAppState.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -23,6 +24,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class NanoAppState implements Parcelable {
     private long mNanoAppId;
     private int mNanoAppVersion;
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
index 18287fa..ca38076 100644
--- a/core/java/android/hardware/radio/ITuner.aidl
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -82,17 +82,9 @@
      */
     List<RadioManager.ProgramInfo> getProgramList(in Map vendorFilter);
 
-    /**
-     * @throws IllegalStateException if the switch is not supported at current
-     *         configuration.
-     */
-    boolean isAnalogForced();
-
-    /**
-     * @throws IllegalStateException if the switch is not supported at current
-     *         configuration.
-     */
-    void setAnalogForced(boolean isForced);
+    boolean isConfigFlagSupported(int flag);
+    boolean isConfigFlagSet(int flag);
+    void setConfigFlag(int flag, boolean value);
 
     /**
      * @param parameters Vendor-specific key-value pairs, must be Map<String, String>
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 4d54e31..b740f14 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -38,6 +38,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -119,6 +120,71 @@
      * @see BandDescriptor */
     public static final int REGION_KOREA  = 4;
 
+    /**
+     * Forces mono audio stream reception.
+     *
+     * Analog broadcasts can recover poor reception conditions by jointing
+     * stereo channels into one. Mainly for, but not limited to AM/FM.
+     */
+    public static final int CONFIG_FORCE_MONO = 1;
+    /**
+     * Forces the analog playback for the supporting radio technology.
+     *
+     * User may disable digital playback for FM HD Radio or hybrid FM/DAB with
+     * this option. This is purely user choice, ie. does not reflect digital-
+     * analog handover state managed from the HAL implementation side.
+     *
+     * Some radio technologies may not support this, ie. DAB.
+     */
+    public static final int CONFIG_FORCE_ANALOG = 2;
+    /**
+     * Forces the digital playback for the supporting radio technology.
+     *
+     * User may disable digital-analog handover that happens with poor
+     * reception conditions. With digital forced, the radio will remain silent
+     * instead of switching to analog channel if it's available. This is purely
+     * user choice, it does not reflect the actual state of handover.
+     */
+    public static final int CONFIG_FORCE_DIGITAL = 3;
+    /**
+     * RDS Alternative Frequencies.
+     *
+     * If set and the currently tuned RDS station broadcasts on multiple
+     * channels, radio tuner automatically switches to the best available
+     * alternative.
+     */
+    public static final int CONFIG_RDS_AF = 4;
+    /**
+     * RDS region-specific program lock-down.
+     *
+     * Allows user to lock to the current region as they move into the
+     * other region.
+     */
+    public static final int CONFIG_RDS_REG = 5;
+    /** Enables DAB-DAB hard- and implicit-linking (the same content). */
+    public static final int CONFIG_DAB_DAB_LINKING = 6;
+    /** Enables DAB-FM hard- and implicit-linking (the same content). */
+    public static final int CONFIG_DAB_FM_LINKING = 7;
+    /** Enables DAB-DAB soft-linking (related content). */
+    public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8;
+    /** Enables DAB-FM soft-linking (related content). */
+    public static final int CONFIG_DAB_FM_SOFT_LINKING = 9;
+
+    /** @hide */
+    @IntDef(prefix = { "CONFIG_" }, value = {
+        CONFIG_FORCE_MONO,
+        CONFIG_FORCE_ANALOG,
+        CONFIG_FORCE_DIGITAL,
+        CONFIG_RDS_AF,
+        CONFIG_RDS_REG,
+        CONFIG_DAB_DAB_LINKING,
+        CONFIG_DAB_FM_LINKING,
+        CONFIG_DAB_DAB_SOFT_LINKING,
+        CONFIG_DAB_FM_SOFT_LINKING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConfigFlag {}
+
     private static void writeStringMap(@NonNull Parcel dest, @NonNull Map<String, String> map) {
         dest.writeInt(map.size());
         for (Map.Entry<String, String> entry : map.entrySet()) {
@@ -645,7 +711,8 @@
         private final boolean mAf;
         private final boolean mEa;
 
-        FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+        /** @hide */
+        public FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
                 boolean stereo, boolean rds, boolean ta, boolean af, boolean ea) {
             super(region, type, lowerLimit, upperLimit, spacing);
             mStereo = stereo;
@@ -771,7 +838,8 @@
 
         private final boolean mStereo;
 
-        AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+        /** @hide */
+        public AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
                 boolean stereo) {
             super(region, type, lowerLimit, upperLimit, spacing);
             mStereo = stereo;
@@ -843,10 +911,10 @@
     /** Radio band configuration. */
     public static class BandConfig implements Parcelable {
 
-        final BandDescriptor mDescriptor;
+        @NonNull final BandDescriptor mDescriptor;
 
         BandConfig(BandDescriptor descriptor) {
-            mDescriptor = descriptor;
+            mDescriptor = Objects.requireNonNull(descriptor);
         }
 
         BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing) {
@@ -968,7 +1036,8 @@
         private final boolean mAf;
         private final boolean mEa;
 
-        FmBandConfig(FmBandDescriptor descriptor) {
+        /** @hide */
+        public FmBandConfig(FmBandDescriptor descriptor) {
             super((BandDescriptor)descriptor);
             mStereo = descriptor.isStereoSupported();
             mRds = descriptor.isRdsSupported();
@@ -1204,7 +1273,8 @@
     public static class AmBandConfig extends BandConfig {
         private final boolean mStereo;
 
-        AmBandConfig(AmBandDescriptor descriptor) {
+        /** @hide */
+        public AmBandConfig(AmBandDescriptor descriptor) {
             super((BandDescriptor)descriptor);
             mStereo = descriptor.isStereoSupported();
         }
@@ -1649,8 +1719,8 @@
         TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler);
         try {
             tuner = mService.openTuner(moduleId, config, withAudio, halCallback);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to open tuner", e);
+        } catch (RemoteException | IllegalArgumentException ex) {
+            Log.e(TAG, "Failed to open tuner", ex);
             return null;
         }
         if (tuner == null) {
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index e93fd5f..0d367e7 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -290,7 +290,9 @@
      * @throws IllegalStateException if the switch is not supported at current
      *         configuration.
      * @return {@code true} if analog is forced, {@code false} otherwise.
+     * @deprecated Use {@link isConfigFlagSet(int)} instead.
      */
+    @Deprecated
     public abstract boolean isAnalogForced();
 
     /**
@@ -305,10 +307,50 @@
      * @param isForced {@code true} to force analog, {@code false} for a default behaviour.
      * @throws IllegalStateException if the switch is not supported at current
      *         configuration.
+     * @deprecated Use {@link setConfigFlag(int, boolean)} instead.
      */
+    @Deprecated
     public abstract void setAnalogForced(boolean isForced);
 
     /**
+     * Checks, if a given config flag is supported
+     *
+     * @param flag Flag to check.
+     * @return True, if the flag is supported.
+     */
+    public boolean isConfigFlagSupported(@RadioManager.ConfigFlag int flag) {
+        return false;
+    }
+
+    /**
+     * Fetches the current setting of a given config flag.
+     *
+     * The success/failure result is consistent with isConfigFlagSupported.
+     *
+     * @param flag Flag to fetch.
+     * @return The current value of the flag.
+     * @throws IllegalStateException if the flag is not applicable right now.
+     * @throws UnsupportedOperationException if the flag is not supported at all.
+     */
+    public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Sets the config flag.
+     *
+     * The success/failure result is consistent with isConfigFlagSupported.
+     *
+     * @param flag Flag to set.
+     * @param value The new value of a given flag.
+     * @throws IllegalStateException if the flag is not applicable right now.
+     * @throws UnsupportedOperationException if the flag is not supported at all.
+     */
+    public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Generic method for setting vendor-specific parameter values.
      * The framework does not interpret the parameters, they are passed
      * in an opaque manner between a vendor application and HAL.
@@ -316,6 +358,7 @@
      * Framework does not make any assumptions on the keys or values, other than
      * ones stated in VendorKeyValue documentation (a requirement of key
      * prefixes).
+     * See VendorKeyValue at hardware/interfaces/broadcastradio/2.0/types.hal.
      *
      * For each pair in the result map, the key will be one of the keys
      * contained in the input (possibly with wildcards expanded), and the value
@@ -332,10 +375,11 @@
      *
      * @param parameters Vendor-specific key-value pairs.
      * @return Operation completion status for parameters being set.
-     * @hide FutureFeature
      */
-    public abstract @NonNull Map<String, String>
-            setParameters(@NonNull Map<String, String> parameters);
+    public @NonNull Map<String, String>
+            setParameters(@NonNull Map<String, String> parameters) {
+        throw new UnsupportedOperationException();
+    }
 
     /**
      * Generic method for retrieving vendor-specific parameter values.
@@ -355,10 +399,11 @@
      *
      * @param keys Parameter keys to fetch.
      * @return Vendor-specific key-value pairs.
-     * @hide FutureFeature
      */
-    public abstract @NonNull Map<String, String>
-            getParameters(@NonNull List<String> keys);
+    public @NonNull Map<String, String>
+            getParameters(@NonNull List<String> keys) {
+        throw new UnsupportedOperationException();
+    }
 
     /**
      * Get current antenna connection state for current configuration.
@@ -494,7 +539,6 @@
          * asynchronously.
          *
          * @param parameters Vendor-specific key-value pairs.
-         * @hide FutureFeature
          */
         public void onParametersUpdated(@NonNull Map<String, String> parameters) {}
     }
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index 864d17c..8ad609d 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -63,6 +63,7 @@
 
     @Override
     public int setConfiguration(RadioManager.BandConfig config) {
+        if (config == null) return RadioManager.STATUS_BAD_VALUE;
         try {
             mTuner.setConfiguration(config);
             mBand = config.getType();
@@ -235,17 +236,36 @@
 
     @Override
     public boolean isAnalogForced() {
+        return isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG);
+    }
+
+    @Override
+    public void setAnalogForced(boolean isForced) {
+        setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, isForced);
+    }
+
+    @Override
+    public boolean isConfigFlagSupported(@RadioManager.ConfigFlag int flag) {
         try {
-            return mTuner.isAnalogForced();
+            return mTuner.isConfigFlagSupported(flag);
         } catch (RemoteException e) {
             throw new RuntimeException("service died", e);
         }
     }
 
     @Override
-    public void setAnalogForced(boolean isForced) {
+    public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) {
         try {
-            mTuner.setAnalogForced(isForced);
+            return mTuner.isConfigFlagSet(flag);
+        } catch (RemoteException e) {
+            throw new RuntimeException("service died", e);
+        }
+    }
+
+    @Override
+    public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {
+        try {
+            mTuner.setConfigFlag(flag, value);
         } catch (RemoteException e) {
             throw new RuntimeException("service died", e);
         }
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 151e62d..398dda1 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -96,6 +96,11 @@
      */
     void setCurrentFunction(String function, boolean usbDataUnlocked);
 
+    /* Sets the screen unlocked USB function(s), which will be set automatically
+     * when the screen is unlocked.
+     */
+    void setScreenUnlockedFunctions(String function);
+
     /* Allow USB debugging from the attached host. If alwaysAllow is true, add the
      * the public key to list of host keys that the user has approved.
      */
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index bdb90bc..7617c2b 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -601,6 +601,32 @@
     }
 
     /**
+     * Sets the screen unlocked functions, which are persisted and set as the current functions
+     * whenever the screen is unlocked.
+     * <p>
+     * The allowed values are: {@link #USB_FUNCTION_NONE},
+     * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP},
+     * or {@link #USB_FUNCTION_RNDIS}.
+     * {@link #USB_FUNCTION_NONE} has the effect of switching off this feature, so functions
+     * no longer change on screen unlock.
+     * </p><p>
+     * Note: When the screen is on, this method will apply given functions as current functions,
+     * which is asynchronous and may fail silently without applying the requested changes.
+     * </p>
+     *
+     * @param function function to set as default
+     *
+     * {@hide}
+     */
+    public void setScreenUnlockedFunctions(String function) {
+        try {
+            mService.setScreenUnlockedFunctions(function);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns a list of physical USB ports on the device.
      * <p>
      * This list is guaranteed to contain all dual-role USB Type C ports but it might
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index f75789f..7e37432 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -70,6 +70,7 @@
 
     SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
     void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
+    String getSubscriptionPlansOwner(int subId);
 
     void factoryReset(String subscriber);
 
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index f82627b..7d752e8 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -231,6 +231,31 @@
         }
     }
 
+    /** @hide */
+    public boolean isAuthentication() {
+        switch (getName()) {
+            // Fallthrough
+            case AUTH_HMAC_MD5:
+            case AUTH_HMAC_SHA1:
+            case AUTH_HMAC_SHA256:
+            case AUTH_HMAC_SHA384:
+            case AUTH_HMAC_SHA512:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /** @hide */
+    public boolean isEncryption() {
+        return getName().equals(CRYPT_AES_CBC);
+    }
+
+    /** @hide */
+    public boolean isAead() {
+        return getName().equals(AUTH_CRYPT_AES_GCM);
+    }
+
     @Override
     public String toString() {
         return new StringBuilder()
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index e6cd3fc..f54ceb5 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -102,17 +102,11 @@
 
     /** 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;
     }
 
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 6a4b891..34cfa9b 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -69,7 +69,7 @@
     }
 
     /** @hide */
-    public static final int INVALID_RESOURCE_ID = 0;
+    public static final int INVALID_RESOURCE_ID = -1;
 
     /**
      * Thrown to indicate that a requested SPI is in use.
@@ -128,7 +128,7 @@
         private final InetAddress mRemoteAddress;
         private final CloseGuard mCloseGuard = CloseGuard.get();
         private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
-        private int mResourceId;
+        private int mResourceId = INVALID_RESOURCE_ID;
 
         /** Get the underlying SPI held by this object. */
         public int getSpi() {
@@ -146,6 +146,7 @@
         public void close() {
             try {
                 mService.releaseSecurityParameterIndex(mResourceId);
+                mResourceId = INVALID_RESOURCE_ID;
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -501,7 +502,7 @@
     public static final class UdpEncapsulationSocket implements AutoCloseable {
         private final ParcelFileDescriptor mPfd;
         private final IIpSecService mService;
-        private final int mResourceId;
+        private int mResourceId = INVALID_RESOURCE_ID;
         private final int mPort;
         private final CloseGuard mCloseGuard = CloseGuard.get();
 
@@ -554,6 +555,7 @@
         public void close() throws IOException {
             try {
                 mService.closeUdpEncapsulationSocket(mResourceId);
+                mResourceId = INVALID_RESOURCE_ID;
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 7cd742b..102ba6d 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -347,6 +347,9 @@
          */
         public IpSecTransform.Builder setSpi(
                 @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
+            if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+                throw new IllegalArgumentException("Invalid SecurityParameterIndex");
+            }
             mConfig.setSpiResourceId(direction, spi.getResourceId());
             return this;
         }
@@ -381,6 +384,9 @@
         public IpSecTransform.Builder setIpv4Encapsulation(
                 IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
             mConfig.setEncapType(ENCAP_ESPINUDP);
+            if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
+                throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
+            }
             mConfig.setEncapSocketResourceId(localSocket.getResourceId());
             mConfig.setEncapRemotePort(remotePort);
             return this;
@@ -426,6 +432,9 @@
         public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
                 throws IpSecManager.ResourceUnavailableException,
                         IpSecManager.SpiUnavailableException, IOException {
+            if (remoteAddress == null) {
+                throw new IllegalArgumentException("Remote address may not be null or empty!");
+            }
             mConfig.setMode(MODE_TRANSPORT);
             mConfig.setRemoteAddress(remoteAddress.getHostAddress());
             // FIXME: modifying a builder after calling build can change the built transform.
@@ -447,8 +456,12 @@
          */
         public IpSecTransform buildTunnelModeTransform(
                 InetAddress localAddress, InetAddress remoteAddress) {
-            // FIXME: argument validation here
-            // throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
+            if (localAddress == null) {
+                throw new IllegalArgumentException("Local address may not be null or empty!");
+            }
+            if (remoteAddress == null) {
+                throw new IllegalArgumentException("Remote address may not be null or empty!");
+            }
             mConfig.setLocalAddress(localAddress.getHostAddress());
             mConfig.setRemoteAddress(remoteAddress.getHostAddress());
             mConfig.setMode(MODE_TUNNEL);
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 903b602..1a3ce91 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -21,6 +21,7 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.okhttp.internalandroidapi.Dns;
 import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory;
@@ -402,4 +403,11 @@
     public String toString() {
         return Integer.toString(netId);
     }
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(NetworkProto.NET_ID, netId);
+        proto.end(token);
+    }
 }
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index f468e5d..8b03fa8 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -20,6 +20,7 @@
 import android.net.ConnectivityManager.NetworkCallback;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.BitUtils;
@@ -1016,6 +1017,31 @@
         return "[" + transports + capabilities + upBand + dnBand + specifier + signalStrength + "]";
     }
 
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        for (int transport : getTransportTypes()) {
+            proto.write(NetworkCapabilitiesProto.TRANSPORTS, transport);
+        }
+
+        for (int capability : getCapabilities()) {
+            proto.write(NetworkCapabilitiesProto.CAPABILITIES, capability);
+        }
+
+        proto.write(NetworkCapabilitiesProto.LINK_UP_BANDWIDTH_KBPS, mLinkUpBandwidthKbps);
+        proto.write(NetworkCapabilitiesProto.LINK_DOWN_BANDWIDTH_KBPS, mLinkDownBandwidthKbps);
+
+        if (mNetworkSpecifier != null) {
+            proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString());
+        }
+
+        proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength());
+        proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength);
+
+        proto.end(token);
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 97ded2d..a072409 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -20,6 +20,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
 
 import java.util.Objects;
 
@@ -389,6 +390,35 @@
                 ", " + networkCapabilities.toString() + " ]";
     }
 
+    private int typeToProtoEnum(Type t) {
+        switch (t) {
+            case NONE:
+                return NetworkRequestProto.TYPE_NONE;
+            case LISTEN:
+                return NetworkRequestProto.TYPE_LISTEN;
+            case TRACK_DEFAULT:
+                return NetworkRequestProto.TYPE_TRACK_DEFAULT;
+            case REQUEST:
+                return NetworkRequestProto.TYPE_REQUEST;
+            case BACKGROUND_REQUEST:
+                return NetworkRequestProto.TYPE_BACKGROUND_REQUEST;
+            default:
+                return NetworkRequestProto.TYPE_UNKNOWN;
+        }
+    }
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        proto.write(NetworkRequestProto.TYPE, typeToProtoEnum(type));
+        proto.write(NetworkRequestProto.REQUEST_ID, requestId);
+        proto.write(NetworkRequestProto.LEGACY_TYPE, legacyType);
+        networkCapabilities.writeToProto(proto, NetworkRequestProto.NETWORK_CAPABILITIES);
+
+        proto.end(token);
+    }
+
     public boolean equals(Object obj) {
         if (obj instanceof NetworkRequest == false) return false;
         NetworkRequest that = (NetworkRequest)obj;
diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java
index 42e43c8..5425bf5 100644
--- a/core/java/android/net/NetworkWatchlistManager.java
+++ b/core/java/android/net/NetworkWatchlistManager.java
@@ -59,8 +59,8 @@
     /**
      * Report network watchlist records if necessary.
      *
-     * Watchlist report process will run summarize records into a single report, then the
-     * report will be processed by differential privacy framework and store it on disk.
+     * Watchlist report process will summarize records into a single report, then the
+     * report will be processed by differential privacy framework and stored on disk.
      *
      * @hide
      */
@@ -72,4 +72,18 @@
             e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Reload network watchlist.
+     *
+     * @hide
+     */
+    public void reloadWatchlist() {
+        try {
+            mNetworkWatchlistManager.reloadWatchlist();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to reload watchlist");
+            e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 196a3bc..bda720bb 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -27,6 +27,7 @@
 import android.media.MediaPlayer;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.DataUnit;
 
 import com.android.server.NetworkManagementSocketTagger;
 
@@ -56,15 +57,20 @@
      */
     public final static int UNSUPPORTED = -1;
 
-    /** @hide */
+    /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+    @Deprecated
     public static final long KB_IN_BYTES = 1024;
-    /** @hide */
+    /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+    @Deprecated
     public static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
-    /** @hide */
+    /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+    @Deprecated
     public static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
-    /** @hide */
+    /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+    @Deprecated
     public static final long TB_IN_BYTES = GB_IN_BYTES * 1024;
-    /** @hide */
+    /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+    @Deprecated
     public static final long PB_IN_BYTES = TB_IN_BYTES * 1024;
 
     /**
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 430c28b..d4d74f4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -180,6 +180,11 @@
     public static final int FOREGROUND_SERVICE = 22;
 
     /**
+     * A constant indicating an aggregate wifi multicast timer
+     */
+     public static final int WIFI_AGGREGATE_MULTICAST_ENABLED = 23;
+
+    /**
      * Include all of the data in the stats, including previously saved data.
      */
     public static final int STATS_SINCE_CHARGED = 0;
@@ -227,8 +232,11 @@
      * New in version 28:
      *   - Light/Deep Doze power
      *   - WiFi Multicast Wakelock statistics (count & duration)
+     * New in version 29:
+     *   - Process states re-ordered. TOP_SLEEPING now below BACKGROUND. HEAVY_WEIGHT introduced.
+     *   - CPU times per UID process state
      */
-    static final int CHECKIN_VERSION = 28;
+    static final int CHECKIN_VERSION = 29;
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -2331,6 +2339,22 @@
     };
 
     /**
+     * Returns total time for WiFi Multicast Wakelock timer.
+     * Note that this may be different from the sum of per uid timer values.
+     *
+     *  {@hide}
+     */
+    public abstract long getWifiMulticastWakelockTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns total time for WiFi Multicast Wakelock timer
+     * Note that this may be different from the sum of per uid timer values.
+     *
+     * {@hide}
+     */
+    public abstract int getWifiMulticastWakelockCount(int which);
+
+    /**
      * Returns the time in microseconds that wifi has been on while the device was
      * running on battery.
      *
@@ -3439,16 +3463,13 @@
                 screenDozeTime / 1000);
 
 
-        // Calculate both wakelock and wifi multicast wakelock times across all uids.
+        // Calculate wakelock times across all uids.
         long fullWakeLockTimeTotal = 0;
         long partialWakeLockTimeTotal = 0;
-        long multicastWakeLockTimeTotalMicros = 0;
-        int multicastWakeLockCountTotal = 0;
 
         for (int iu = 0; iu < NU; iu++) {
             final Uid u = uidStats.valueAt(iu);
 
-            // First calculating the wakelock stats
             final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
                     = u.getWakelockStats();
             for (int iw=wakelocks.size()-1; iw>=0; iw--) {
@@ -3466,13 +3487,6 @@
                         rawRealtime, which);
                 }
             }
-
-            // Now calculating the wifi multicast wakelock stats
-            final Timer mcTimer = u.getMulticastWakelockStats();
-            if (mcTimer != null) {
-                multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which);
-                multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
-            }
         }
 
         // Dump network stats
@@ -3589,6 +3603,9 @@
         dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args);
 
         // Dump Multicast total stats
+        final long multicastWakeLockTimeTotalMicros =
+                getWifiMulticastWakelockTime(rawRealtime, which);
+        final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
         dumpLine(pw, 0 /* uid */, category, WIFI_MULTICAST_TOTAL_DATA,
                 multicastWakeLockTimeTotalMicros / 1000,
                 multicastWakeLockCountTotal);
@@ -4453,18 +4470,15 @@
             pw.print("  Connectivity changes: "); pw.println(connChanges);
         }
 
-        // Calculate both wakelock and wifi multicast wakelock times across all uids.
+        // Calculate wakelock times across all uids.
         long fullWakeLockTimeTotalMicros = 0;
         long partialWakeLockTimeTotalMicros = 0;
-        long multicastWakeLockTimeTotalMicros = 0;
-        int multicastWakeLockCountTotal = 0;
 
         final ArrayList<TimerEntry> timers = new ArrayList<>();
 
         for (int iu = 0; iu < NU; iu++) {
             final Uid u = uidStats.valueAt(iu);
 
-            // First calculate wakelock statistics
             final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
                     = u.getWakelockStats();
             for (int iw=wakelocks.size()-1; iw>=0; iw--) {
@@ -4492,13 +4506,6 @@
                     }
                 }
             }
-
-            // Next calculate wifi multicast wakelock statistics
-            final Timer mcTimer = u.getMulticastWakelockStats();
-            if (mcTimer != null) {
-                multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which);
-                multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
-            }
         }
 
         final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
@@ -4528,6 +4535,9 @@
             pw.println(sb.toString());
         }
 
+        final long multicastWakeLockTimeTotalMicros =
+                getWifiMulticastWakelockTime(rawRealtime, which);
+        final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
         if (multicastWakeLockTimeTotalMicros != 0) {
             sb.setLength(0);
             sb.append(prefix);
@@ -7048,6 +7058,28 @@
                     }
                 }
             }
+
+            for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+                final long[] timesMs = u.getCpuFreqTimes(which, procState);
+                if (timesMs != null && timesMs.length == cpuFreqs.length) {
+                    long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(which, procState);
+                    if (screenOffTimesMs == null) {
+                        screenOffTimesMs = new long[timesMs.length];
+                    }
+                    final long procToken = proto.start(UidProto.Cpu.BY_PROCESS_STATE);
+                    proto.write(UidProto.Cpu.ByProcessState.PROCESS_STATE, procState);
+                    for (int ic = 0; ic < timesMs.length; ++ic) {
+                        long cToken = proto.start(UidProto.Cpu.ByProcessState.BY_FREQUENCY);
+                        proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1);
+                        proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS,
+                                timesMs[ic]);
+                        proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+                                screenOffTimesMs[ic]);
+                        proto.end(cToken);
+                    }
+                    proto.end(procToken);
+                }
+            }
             proto.end(cpuToken);
 
             // Flashlight (FLASHLIGHT_DATA)
@@ -7532,22 +7564,9 @@
         proto.end(mToken);
 
         // Wifi multicast wakelock total stats (WIFI_MULTICAST_WAKELOCK_TOTAL_DATA)
-        // Calculate multicast wakelock stats across all uids.
-        long multicastWakeLockTimeTotalUs = 0;
-        int multicastWakeLockCountTotal = 0;
-
-        for (int iu = 0; iu < uidStats.size(); iu++) {
-            final Uid u = uidStats.valueAt(iu);
-
-            final Timer mcTimer = u.getMulticastWakelockStats();
-
-            if (mcTimer != null) {
-                multicastWakeLockTimeTotalUs +=
-                        mcTimer.getTotalTimeLocked(rawRealtimeUs, which);
-                multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
-            }
-        }
-
+        final long multicastWakeLockTimeTotalUs =
+                getWifiMulticastWakelockTime(rawRealtimeUs, which);
+        final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
         final long wmctToken = proto.start(SystemProto.WIFI_MULTICAST_WAKELOCK_TOTAL);
         proto.write(SystemProto.WifiMulticastWakelockTotal.DURATION_MS,
                 multicastWakeLockTimeTotalUs / 1000);
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index b7a4645..eb264d6d 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -35,6 +35,9 @@
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Base class for a remotable object, the core part of a lightweight
@@ -802,7 +805,7 @@
         /**
          * Return the total number of pairs in the map.
          */
-        int size() {
+        private int size() {
             int size = 0;
             for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
                 if (a != null) {
@@ -813,6 +816,24 @@
         }
 
         /**
+         * Return the total number of pairs in the map containing values that have
+         * not been cleared. More expensive than the above size function.
+         */
+        private int unclearedSize() {
+            int size = 0;
+            for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+                if (a != null) {
+                    for (WeakReference<BinderProxy> ref : a) {
+                        if (ref.get() != null) {
+                            ++size;
+                        }
+                    }
+                }
+            }
+            return size;
+        }
+
+        /**
          * Remove ith entry from the hash bucket indicated by hash.
          */
         private void remove(int hash, int index) {
@@ -901,17 +922,71 @@
                 keyArray[size] = key;
             }
             if (size >= mWarnBucketSize) {
-                final int total_size = size();
+                final int totalSize = size();
                 Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size
-                        + " total = " + total_size);
+                        + " total = " + totalSize);
                 mWarnBucketSize += WARN_INCREMENT;
-                if (Build.IS_DEBUGGABLE && total_size > CRASH_AT_SIZE) {
-                    throw new AssertionError("Binder ProxyMap has too many entries. "
-                            + "BinderProxy leak?");
+                if (Build.IS_DEBUGGABLE && totalSize >= CRASH_AT_SIZE) {
+                    // Use the number of uncleared entries to determine whether we should
+                    // really report a histogram and crash. We don't want to fundamentally
+                    // change behavior for a debuggable process, so we GC only if we are
+                    // about to crash.
+                    final int totalUnclearedSize = unclearedSize();
+                    if (totalUnclearedSize >= CRASH_AT_SIZE) {
+                        dumpProxyInterfaceCounts();
+                        Runtime.getRuntime().gc();
+                        throw new AssertionError("Binder ProxyMap has too many entries: "
+                                + totalSize + " (total), " + totalUnclearedSize + " (uncleared), "
+                                + unclearedSize() + " (uncleared after GC). BinderProxy leak?");
+                    } else if (totalSize > 3 * totalUnclearedSize / 2) {
+                        Log.v(Binder.TAG, "BinderProxy map has many cleared entries: "
+                                + (totalSize - totalUnclearedSize) + " of " + totalSize
+                                + " are cleared");
+                    }
                 }
             }
         }
 
+        /**
+         * Dump a histogram to the logcat. Used to diagnose abnormally large proxy maps.
+         */
+        private void dumpProxyInterfaceCounts() {
+            Map<String, Integer> counts = new HashMap<>();
+            for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+                if (a != null) {
+                    for (WeakReference<BinderProxy> weakRef : a) {
+                        BinderProxy bp = weakRef.get();
+                        String key;
+                        if (bp == null) {
+                            key = "<cleared weak-ref>";
+                        } else {
+                            try {
+                                key = bp.getInterfaceDescriptor();
+                            } catch (Throwable t) {
+                                key = "<exception during getDescriptor>";
+                            }
+                        }
+                        Integer i = counts.get(key);
+                        if (i == null) {
+                            counts.put(key, 1);
+                        } else {
+                            counts.put(key, i + 1);
+                        }
+                    }
+                }
+            }
+            Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray(
+                    new Map.Entry[counts.size()]);
+            Arrays.sort(sorted, (Map.Entry<String, Integer> a, Map.Entry<String, Integer> b)
+                    -> b.getValue().compareTo(a.getValue()));
+            Log.v(Binder.TAG, "BinderProxy descriptor histogram (top ten):");
+            int printLength = Math.min(10, sorted.length);
+            for (int i = 0; i < printLength; i++) {
+                Log.v(Binder.TAG, " #" + (i + 1) + ": " + sorted[i].getKey() + " x"
+                        + sorted[i].getValue());
+            }
+        }
+
         // 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.
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index c58153a..7ae5a67 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -21,6 +21,7 @@
 import android.util.Size;
 import android.util.SizeF;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -1272,4 +1273,21 @@
         }
         return mMap.toString();
     }
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        if (mParcelledData != null) {
+            if (isEmptyParcel()) {
+                proto.write(BundleProto.PARCELLED_DATA_SIZE, 0);
+            } else {
+                proto.write(BundleProto.PARCELLED_DATA_SIZE, mParcelledData.dataSize());
+            }
+        } else {
+            proto.write(BundleProto.MAP_DATA, mMap.toString());
+        }
+
+        proto.end(token);
+    }
 }
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 3ca1005..5c5e351 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -388,6 +388,8 @@
      * The runnable will be run on the thread to which this handler is attached.
      *
      * @param r The Runnable that will be executed.
+     * @param token An instance which can be used to cancel {@code r} via
+     *         {@link #removeCallbacksAndMessages}.
      * @param uptimeMillis The absolute time at which the callback should run,
      *         using the {@link android.os.SystemClock#uptimeMillis} time-base.
      * 
@@ -430,6 +432,32 @@
     }
     
     /**
+     * Causes the Runnable r to be added to the message queue, to be run
+     * after the specified amount of time elapses.
+     * The runnable will be run on the thread to which this handler
+     * is attached.
+     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+     * Time spent in deep sleep will add an additional delay to execution.
+     *
+     * @param r The Runnable that will be executed.
+     * @param token An instance which can be used to cancel {@code r} via
+     *         {@link #removeCallbacksAndMessages}.
+     * @param delayMillis The delay (in milliseconds) until the Runnable
+     *        will be executed.
+     *
+     * @return Returns true if the Runnable was successfully placed in to the
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the Runnable will be processed --
+     *         if the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public final boolean postDelayed(Runnable r, Object token, long delayMillis)
+    {
+        return sendMessageDelayed(getPostMessage(r, token), delayMillis);
+    }
+
+    /**
      * Posts a message to an object that implements Runnable.
      * Causes the Runnable r to executed on the next iteration through the
      * message queue. The runnable will be run on the thread to which this
diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java
index a080c8d..4d7d931 100644
--- a/core/java/android/os/HidlSupport.java
+++ b/core/java/android/os/HidlSupport.java
@@ -86,6 +86,25 @@
     }
 
     /**
+     * Class which can be used to fetch an object out of a lambda. Fetching an object
+     * out of a local scope with HIDL is a common operation (although usually it can
+     * and should be avoided).
+     *
+     * @param <E> Inner object type.
+     */
+    public static final class Mutable<E> {
+        public E value;
+
+        public Mutable() {
+            value = null;
+        }
+
+        public Mutable(E value) {
+            this.value = value;
+        }
+    }
+
+    /**
      * Similar to Arrays.deepHashCode, but also take care of lists.
      */
     public static int deepHashCode(Object o) {
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 3db12ed..29812e8 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -71,7 +71,7 @@
      * Fetches data for the specified configuration key. Returns a byte array representing proto
      * wire-encoded of ConfigMetricsReportList.
      */
-    byte[] getData(in String key);
+    byte[] getData(in long key);
 
     /**
      * Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
@@ -86,7 +86,7 @@
      *
      * Returns if this configuration was correctly registered.
      */
-    boolean addConfiguration(in String configKey, in byte[] config, in String pkg, in String cls);
+    boolean addConfiguration(in long configKey, in byte[] config, in String pkg, in String cls);
 
     /**
      * Removes the configuration with the matching config key. No-op if this config key does not
@@ -94,5 +94,5 @@
      *
      * Returns if this configuration key was removed.
      */
-    boolean removeConfiguration(in String configKey);
+    boolean removeConfiguration(in long configKey);
 }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index f643c57..01d6b02 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -98,4 +98,6 @@
     boolean isUserNameSet(int userHandle);
     boolean hasRestrictedProfiles();
     boolean trySetQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target);
+    long getUserStartRealtime();
+    long getUserUnlockRealtime();
 }
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index 3ed5b17..40eceb8 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.util.ArrayMap;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.XmlUtils;
 
@@ -321,4 +322,21 @@
         }
         return mMap.toString();
     }
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        if (mParcelledData != null) {
+            if (isEmptyParcel()) {
+                proto.write(PersistableBundleProto.PARCELLED_DATA_SIZE, 0);
+            } else {
+                proto.write(PersistableBundleProto.PARCELLED_DATA_SIZE, mParcelledData.dataSize());
+            }
+        } else {
+            proto.write(PersistableBundleProto.MAP_DATA, mMap.toString());
+        }
+
+        proto.end(token);
+    }
 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 56c6391..cd6d41b 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1540,7 +1540,7 @@
          */
         public void setWorkSource(WorkSource ws) {
             synchronized (mToken) {
-                if (ws != null && ws.size() == 0) {
+                if (ws != null && ws.isEmpty()) {
                     ws = null;
                 }
 
@@ -1552,7 +1552,7 @@
                     changed = true;
                     mWorkSource = new WorkSource(ws);
                 } else {
-                    changed = mWorkSource.diff(ws);
+                    changed = !mWorkSource.equals(ws);
                     if (changed) {
                         mWorkSource.set(ws);
                     }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 75cbd57..44238df 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -194,6 +194,21 @@
     public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
 
     /**
+     * Specifies if airplane mode is disallowed on the device.
+     *
+     * <p> This restriction can only be set by the device owner and the profile owner on the
+     * primary user and it applies globally - i.e. it disables airplane mode on the entire device.
+     * <p>The default value is <code>false</code>.
+     *
+     * <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_AIRPLANE_MODE = "no_airplane_mode";
+
+    /**
      * Specifies if a user is disallowed from enabling the
      * "Unknown Sources" setting, that allows installation of apps from unknown sources.
      * The default value is <code>false</code>.
@@ -335,6 +350,28 @@
     public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";
 
     /**
+     * Specifies if a user is disallowed from configuring location mode. Device owner and profile
+     * owners can set this restriction and it only applies on the managed user.
+     *
+     * <p>In a managed profile, location sharing is forced off when it's off on primary user, so
+     * user can still turn off location sharing on managed profile when the restriction is set by
+     * profile owner on managed profile.
+     *
+     * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
+     * as the device owner or profile owner can still enable or disable location mode via
+     * {@link DevicePolicyManager#setSecureSetting} when this restriction is on.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <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_LOCATION_MODE = "no_config_location_mode";
+
+    /**
      * 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,
@@ -1355,6 +1392,34 @@
     }
 
     /**
+     * Return the time when the calling user started in elapsed milliseconds since boot,
+     * or 0 if not started.
+     *
+     * @hide
+     */
+    public long getUserStartRealtime() {
+        try {
+            return mService.getUserStartRealtime();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the time when the calling user was unlocked elapsed milliseconds since boot,
+     * or 0 if not unlocked.
+     *
+     * @hide
+     */
+    public long getUserUnlockRealtime() {
+        try {
+            return mService.getUserUnlockRealtime();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the UserInfo object describing a specific user.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      * @param userHandle the user handle of the user whose information is being requested.
@@ -2160,7 +2225,8 @@
 
     /**
      * Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify
-     * a target to start when user is unlocked.
+     * a target to start when user is unlocked. If {@code target} is specified, caller must have
+     * the {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
      * @see {@link #trySetQuietModeEnabled(boolean, UserHandle)}
      * @hide
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index bf145a0..d0c2870 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -7,7 +7,6 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Objects;
 
 /**
  * Describes the source of some work that may be done by someone else.
@@ -162,9 +161,21 @@
 
     @Override
     public boolean equals(Object o) {
-        return o instanceof WorkSource
-            && !diff((WorkSource) o)
-            && Objects.equals(mChains, ((WorkSource) o).mChains);
+        if (o instanceof WorkSource) {
+            WorkSource other = (WorkSource) o;
+
+            if (diff(other)) {
+                return false;
+            }
+
+            if (mChains != null && !mChains.isEmpty()) {
+                return mChains.equals(other.mChains);
+            } else {
+                return other.mChains == null || other.mChains.isEmpty();
+            }
+        }
+
+        return false;
     }
 
     @Override
@@ -407,11 +418,11 @@
     }
 
     public boolean remove(WorkSource other) {
-        if (mNum <= 0 || other.mNum <= 0) {
+        if (isEmpty() || other.isEmpty()) {
             return false;
         }
 
-        boolean uidRemoved = false;
+        boolean uidRemoved;
         if (mNames == null && other.mNames == null) {
             uidRemoved = removeUids(other);
         } else {
@@ -427,13 +438,8 @@
         }
 
         boolean chainRemoved = false;
-        if (other.mChains != null) {
-            if (mChains != null) {
-                chainRemoved = mChains.removeAll(other.mChains);
-            }
-        } else if (mChains != null) {
-            mChains.clear();
-            chainRemoved = true;
+        if (other.mChains != null && mChains != null) {
+            chainRemoved = mChains.removeAll(other.mChains);
         }
 
         return uidRemoved || chainRemoved;
@@ -456,6 +462,16 @@
     }
 
     /**
+     * Returns {@code true} iff. this work source contains zero UIDs and zero WorkChains to
+     * attribute usage to.
+     *
+     * @hide for internal use only.
+     */
+    public boolean isEmpty() {
+        return mNum == 0 && (mChains == null || mChains.isEmpty());
+    }
+
+    /**
      * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
      * @hide
      */
@@ -842,6 +858,14 @@
             return this;
         }
 
+        /**
+         * Return the UID to which this WorkChain should be attributed to, i.e, the UID that
+         * initiated the work and not the UID performing it.
+         */
+        public int getAttributionUid() {
+            return mUids[0];
+        }
+
         // TODO: The following three trivial getters are purely for testing and will be removed
         // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
         // diffing it etc.
@@ -932,6 +956,55 @@
                 };
     }
 
+    /**
+     * Computes the differences in WorkChains contained between {@code oldWs} and {@code newWs}.
+     *
+     * Returns {@code null} if no differences exist, otherwise returns a two element array. The
+     * first element is a list of "new" chains, i.e WorkChains present in {@code newWs} but not in
+     * {@code oldWs}. The second element is a list of "gone" chains, i.e WorkChains present in
+     * {@code oldWs} but not in {@code newWs}.
+     *
+     * @hide
+     */
+    public static ArrayList<WorkChain>[] diffChains(WorkSource oldWs, WorkSource newWs) {
+        ArrayList<WorkChain> newChains = null;
+        ArrayList<WorkChain> goneChains = null;
+
+        // TODO(narayan): This is a dumb O(M*N) algorithm that determines what has changed across
+        // WorkSource objects. We can replace this with something smarter, for e.g by defining
+        // a Comparator between WorkChains. It's unclear whether that will be more efficient if
+        // the number of chains associated with a WorkSource is expected to be small
+        if (oldWs.mChains != null) {
+            for (int i = 0; i < oldWs.mChains.size(); ++i) {
+                final WorkChain wc = oldWs.mChains.get(i);
+                if (newWs.mChains == null || !newWs.mChains.contains(wc)) {
+                    if (goneChains == null) {
+                        goneChains = new ArrayList<>(oldWs.mChains.size());
+                    }
+                    goneChains.add(wc);
+                }
+            }
+        }
+
+        if (newWs.mChains != null) {
+            for (int i = 0; i < newWs.mChains.size(); ++i) {
+                final WorkChain wc = newWs.mChains.get(i);
+                if (oldWs.mChains == null || !oldWs.mChains.contains(wc)) {
+                    if (newChains == null) {
+                        newChains = new ArrayList<>(newWs.mChains.size());
+                    }
+                    newChains.add(wc);
+                }
+            }
+        }
+
+        if (newChains != null || goneChains != null) {
+            return new ArrayList[] { newChains, goneChains };
+        }
+
+        return null;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -991,6 +1064,25 @@
             }
             proto.end(contentProto);
         }
+
+        if (mChains != null) {
+            for (int i = 0; i < mChains.size(); i++) {
+                final WorkChain wc = mChains.get(i);
+                final long workChain = proto.start(WorkSourceProto.WORK_CHAINS);
+
+                final String[] tags = wc.getTags();
+                final int[] uids = wc.getUids();
+                for (int j = 0; j < tags.length; j++) {
+                    final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS);
+                    proto.write(WorkSourceProto.WorkSourceContentProto.UID, uids[j]);
+                    proto.write(WorkSourceProto.WorkSourceContentProto.NAME, tags[j]);
+                    proto.end(contentProto);
+                }
+
+                proto.end(workChain);
+            }
+        }
+
         proto.end(workSourceToken);
     }
 
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 9833fe1..f4deeed 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,9 +16,6 @@
 
 package android.os.storage;
 
-import static android.net.TrafficStats.GB_IN_BYTES;
-import static android.net.TrafficStats.MB_IN_BYTES;
-
 import android.annotation.BytesLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -59,6 +56,7 @@
 import android.system.Os;
 import android.system.OsConstants;
 import android.text.TextUtils;
+import android.util.DataUnit;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -116,6 +114,8 @@
     /** {@hide} */
     public static final String PROP_HAS_ADOPTABLE = "vold.has_adoptable";
     /** {@hide} */
+    public static final String PROP_HAS_RESERVED = "vold.has_reserved";
+    /** {@hide} */
     public static final String PROP_FORCE_ADOPTABLE = "persist.fw.force_adoptable";
     /** {@hide} */
     public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
@@ -123,8 +123,6 @@
     public static final String PROP_SDCARDFS = "persist.sys.sdcardfs";
     /** {@hide} */
     public static final String PROP_VIRTUAL_DISK = "persist.sys.virtual_disk";
-    /** {@hide} */
-    public static final String PROP_ADOPTABLE_FBE = "persist.sys.adoptable_fbe";
 
     /** {@hide} */
     public static final String UUID_PRIVATE_INTERNAL = null;
@@ -1115,12 +1113,14 @@
     /** {@hide} */
     public static Pair<String, Long> getPrimaryStoragePathAndSize() {
         return Pair.create(null,
-                FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()));
+                FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
+                    + Environment.getRootDirectory().getTotalSpace()));
     }
 
     /** {@hide} */
     public long getPrimaryStorageSize() {
-        return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace());
+        return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
+                + Environment.getRootDirectory().getTotalSpace());
     }
 
     /** {@hide} */
@@ -1197,12 +1197,12 @@
     }
 
     private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
-    private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
+    private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);
 
     private static final int DEFAULT_CACHE_PERCENTAGE = 10;
-    private static final long DEFAULT_CACHE_MAX_BYTES = 5 * GB_IN_BYTES;
+    private static final long DEFAULT_CACHE_MAX_BYTES = DataUnit.GIBIBYTES.toBytes(5);
 
-    private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
+    private static final long DEFAULT_FULL_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(1);
 
     /**
      * Return the number of available bytes until the given path is considered
@@ -1474,6 +1474,11 @@
     }
 
     /** {@hide} */
+    public static boolean hasAdoptable() {
+        return SystemProperties.getBoolean(PROP_HAS_ADOPTABLE, false);
+    }
+
+    /** {@hide} */
     public static File maybeTranslateEmulatedPathToInternal(File path) {
         // Disabled now that FUSE has been replaced by sdcardfs
         return path;
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 766ad84..60df467 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -212,16 +212,19 @@
         public static final String FEATURES = "features";
 
         /** Call had video. */
-        public static final int FEATURES_VIDEO = 0x1;
+        public static final int FEATURES_VIDEO = 1 << 0;
 
         /** Call was pulled externally. */
-        public static final int FEATURES_PULLED_EXTERNALLY = 0x2;
+        public static final int FEATURES_PULLED_EXTERNALLY = 1 << 1;
 
         /** Call was HD. */
-        public static final int FEATURES_HD_CALL = 0x4;
+        public static final int FEATURES_HD_CALL = 1 << 2;
 
         /** Call was WIFI call. */
-        public static final int FEATURES_WIFI = 0x8;
+        public static final int FEATURES_WIFI = 1 << 3;
+
+        /** Call was on RTT at some point */
+        public static final int FEATURES_RTT = 1 << 4;
 
         /**
          * Indicates the call underwent Assisted Dialing.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 99fcdad..e7fd59e 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -16,7 +16,6 @@
 
 package android.provider;
 
-import static android.net.TrafficStats.KB_IN_BYTES;
 import static android.system.OsConstants.SEEK_SET;
 
 import static com.android.internal.util.Preconditions.checkArgument;
@@ -51,6 +50,7 @@
 import android.os.storage.StorageVolume;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.util.DataUnit;
 import android.util.Log;
 
 import libcore.io.IoUtils;
@@ -173,7 +173,7 @@
     /**
      * Buffer is large enough to rewind past any EXIF headers.
      */
-    private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
+    private static final int THUMBNAIL_BUFFER_SIZE = (int) DataUnit.KIBIBYTES.toBytes(128);
 
     /** {@hide} */
     public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2223263..2ec4906 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5778,6 +5778,14 @@
             "touch_exploration_granted_accessibility_services";
 
         /**
+         * Uri of the slice that's presented on the keyguard.
+         * Defaults to a slice with the date and next alarm.
+         *
+         * @hide
+         */
+        public static final String KEYGUARD_SLICE_URI = "keyguard_slice_uri";
+
+        /**
          * Whether to speak passwords while in accessibility mode.
          *
          * @deprecated The speaking of passwords is controlled by individual accessibility services.
@@ -7183,13 +7191,6 @@
         public static final String QS_TILES = "sysui_qs_tiles";
 
         /**
-         * Whether preloaded APKs have been installed for the user.
-         * @hide
-         */
-        public static final String DEMO_USER_SETUP_COMPLETE
-                = "demo_user_setup_complete";
-
-        /**
          * Specifies whether the web action API is enabled.
          *
          * @hide
@@ -7252,8 +7253,11 @@
          * full_backup_interval_milliseconds       (long)
          * full_backup_require_charging            (boolean)
          * full_backup_required_network_type       (int)
+         * backup_finished_notification_receivers  (String[])
          * </pre>
          *
+         * backup_finished_notification_receivers uses ":" as delimeter for values.
+         *
          * <p>
          * Type: string
          * @hide
@@ -8700,6 +8704,8 @@
 
         /**
          * Whether soft AP will shut down after a timeout period when no devices are connected.
+         *
+         * Type: int (0 for false, 1 for true)
          * @hide
          */
         public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
@@ -8779,6 +8785,7 @@
          * Type: string package name or null if the feature is either not provided or disabled.
          * @hide
          */
+        @TestApi
         public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
 
         /**
@@ -9571,6 +9578,31 @@
         public static final String ANOMALY_DETECTION_CONSTANTS = "anomaly_detection_constants";
 
         /**
+         * Battery tip specific settings
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "battery_tip_enabled=true,summary_enabled=true,high_usage_enabled=true,"
+         * "high_usage_app_count=3,reduced_battery_enabled=false,reduced_battery_percent=50"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * battery_tip_enabled              (boolean)
+         * summary_enabled                  (boolean)
+         * battery_saver_tip_enabled        (boolean)
+         * high_usage_enabled               (boolean)
+         * high_usage_app_count             (int)
+         * app_restriction_enabled          (boolean)
+         * reduced_battery_enabled          (boolean)
+         * reduced_battery_percent          (int)
+         * low_battery_enabled              (boolean)
+         * low_battery_hour                 (int)
+         * </pre>
+         * @hide
+         */
+        public static final String BATTERY_TIP_CONSTANTS = "battery_tip_constants";
+
+        /**
          * Always on display(AOD) specific settings
          * This is encoded as a key=value list, separated by commas. Ex:
          *
@@ -9745,6 +9777,22 @@
         public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants";
 
         /**
+         * BatteryStats specific settings.
+         * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true"
+         *
+         * The following keys are supported:
+         * <pre>
+         * track_cpu_times_by_proc_state (boolean)
+         * </pre>
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * see also com.android.internal.os.BatteryStatsImpl.Constants
+         */
+        public static final String BATTERY_STATS_CONSTANTS = "battery_stats_constants";
+
+        /**
          * Whether or not App Standby feature is enabled. This controls throttling of apps
          * based on usage patterns and predictions.
          * Type: int (0 for false, 1 for true)
@@ -9754,6 +9802,14 @@
         public static final java.lang.String APP_STANDBY_ENABLED = "app_standby_enabled";
 
         /**
+         * Whether or not Network Watchlist feature is enabled.
+         * Type: int (0 for false, 1 for true)
+         * Default: 0
+         * @hide
+         */
+        public static final String NETWORK_WATCHLIST_ENABLED = "network_watchlist_enabled";
+
+        /**
          * Get the key that retrieves a bluetooth headset's priority.
          * @hide
          */
@@ -11108,6 +11164,7 @@
             INSTANT_APP_SETTINGS.add(DEBUG_VIEW_ATTRIBUTES);
             INSTANT_APP_SETTINGS.add(WTF_IS_FATAL);
             INSTANT_APP_SETTINGS.add(SEND_ACTION_APP_ERROR);
+            INSTANT_APP_SETTINGS.add(ZEN_MODE);
         }
 
         /**
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index 42282ac..57477f5 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -56,7 +56,7 @@
     int clear_uid(long uid);
 
     // Keymaster 0.4 methods
-    int addRngEntropy(in byte[] data);
+    int addRngEntropy(in byte[] data, int flags);
     int generateKey(String alias, in KeymasterArguments arguments, in byte[] entropy, int uid,
         int flags, out KeyCharacteristics characteristics);
     int getKeyCharacteristics(String alias, in KeymasterBlob clientId, in KeymasterBlob appId,
@@ -78,4 +78,8 @@
     int attestKey(String alias, in KeymasterArguments params, out KeymasterCertificateChain chain);
     int attestDeviceIds(in KeymasterArguments params, out KeymasterCertificateChain chain);
     int onDeviceOffBody();
+    int importWrappedKey(in String wrappedKeyAlias, in byte[] wrappedKey,
+        in String wrappingKeyAlias, in byte[] maskingKey, in KeymasterArguments arguments,
+        in long rootSid, in long fingerprintSid,
+        out KeyCharacteristics characteristics);
 }
diff --git a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
index 978e60e..d162455 100644
--- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
+++ b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
@@ -60,7 +60,7 @@
     /**
      * Creates instance of the class to to derive key using salted SHA256 hash.
      */
-    public static KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) {
+    public static KeyDerivationParameters createSha256Parameters(@NonNull byte[] salt) {
         return new KeyDerivationParameters(ALGORITHM_SHA256, salt);
     }
 
diff --git a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.java b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.java
index 80f5aa7..5f56c91 100644
--- a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.java
+++ b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.java
@@ -22,13 +22,12 @@
 
 import com.android.internal.util.Preconditions;
 
-
 /**
  * Helper class with data necessary recover a single application key, given a recovery key.
  *
  * <ul>
- * <li>Alias - Keystore alias of the key.
- * <li>Encrypted key material.
+ *   <li>Alias - Keystore alias of the key.
+ *   <li>Encrypted key material.
  * </ul>
  *
  * Note that Application info is not included. Recovery Agent can only make its own keys
@@ -37,49 +36,48 @@
  * @hide
  */
 public final class KeyEntryRecoveryData implements Parcelable {
-    private final byte[] mAlias;
+    private final String mAlias;
     // The only supported format is AES-256 symmetric key.
     private final byte[] mEncryptedKeyMaterial;
 
-    public KeyEntryRecoveryData(@NonNull byte[] alias, @NonNull byte[] encryptedKeyMaterial) {
+    public KeyEntryRecoveryData(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) {
         mAlias = Preconditions.checkNotNull(alias);
         mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial);
     }
 
     /**
      * Application-specific alias of the key.
+     *
      * @see java.security.KeyStore.aliases
      */
-    public @NonNull byte[] getAlias() {
+    public @NonNull String getAlias() {
         return mAlias;
     }
 
-    /**
-     * Encrypted key material encrypted by recovery key.
-     */
+    /** Encrypted key material encrypted by recovery key. */
     public @NonNull byte[] getEncryptedKeyMaterial() {
         return mEncryptedKeyMaterial;
     }
 
     public static final Parcelable.Creator<KeyEntryRecoveryData> CREATOR =
             new Parcelable.Creator<KeyEntryRecoveryData>() {
-        public KeyEntryRecoveryData createFromParcel(Parcel in) {
-                return new KeyEntryRecoveryData(in);
-        }
+                public KeyEntryRecoveryData createFromParcel(Parcel in) {
+                    return new KeyEntryRecoveryData(in);
+                }
 
-        public KeyEntryRecoveryData[] newArray(int length) {
-            return new KeyEntryRecoveryData[length];
-        }
-    };
+                public KeyEntryRecoveryData[] newArray(int length) {
+                    return new KeyEntryRecoveryData[length];
+                }
+            };
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeByteArray(mAlias);
+        out.writeString(mAlias);
         out.writeByteArray(mEncryptedKeyMaterial);
     }
 
     protected KeyEntryRecoveryData(Parcel in) {
-        mAlias = in.createByteArray();
+        mAlias = in.readString();
         mEncryptedKeyMaterial = in.createByteArray();
     }
 
diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
index f88768b..b5ec795 100644
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
@@ -23,7 +23,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
-import android.os.UserHandle;
 import android.security.KeyStore;
 import android.util.AndroidException;
 
@@ -44,14 +43,61 @@
 
     public static final int NO_ERROR = KeyStore.NO_ERROR;
     public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
-    public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
+
+    /**
+     * Failed because the loader has not been initialized with a recovery public key yet.
+     */
+    public static final int ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
+
+    /**
+     * Failed because no snapshot is yet pending to be synced for the user.
+     */
+    public static final int ERROR_NO_SNAPSHOT_PENDING = 21;
+
+    /**
+     * Failed due to an error internal to AndroidKeyStore.
+     */
+    public static final int ERROR_KEYSTORE_INTERNAL_ERROR = 22;
+
+    /**
+     * Failed because the user does not have a lock screen set.
+     */
+    public static final int ERROR_INSECURE_USER = 24;
+
+    /**
+     * Failed because of an internal database error.
+     */
+    public static final int ERROR_DATABASE_ERROR = 25;
+
+    /**
+     * Failed because the provided certificate was not a valid X509 certificate.
+     */
+    public static final int ERROR_BAD_X509_CERTIFICATE = 26;
+
+    /**
+     * Should never be thrown - some algorithm that all AOSP implementations must support is
+     * not available.
+     */
+    public static final int ERROR_UNEXPECTED_MISSING_ALGORITHM = 27;
+
+    /**
+     * The caller is attempting to perform an operation that is not yet fully supported in the API.
+     */
+    public static final int ERROR_NOT_YET_SUPPORTED = 28;
+
+    /**
+     * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong,
+     * the data has become corrupted, the data has been tampered with, etc.
+     */
+    public static final int ERROR_DECRYPTION_FAILED = 29;
+
     /**
      * Rate limit is enforced to prevent using too many trusted remote devices, since each device
      * can have its own number of user secret guesses allowed.
      *
      * @hide
      */
-    public static final int RATE_LIMIT_EXCEEDED = 21;
+    public static final int ERROR_RATE_LIMIT_EXCEEDED = 30;
 
     /** Key has been successfully synced. */
     public static final int RECOVERY_STATUS_SYNCED = 0;
@@ -86,12 +132,13 @@
         /**
          * Creates new {@link #RecoverableKeyStoreLoaderException} instance from the error code.
          *
-         * @param errorCode
+         * @param errorCode An error code, as listed at the top of this file.
+         * @param message The associated error message.
          * @hide
          */
-        public static RecoverableKeyStoreLoaderException fromErrorCode(int errorCode) {
-            return new RecoverableKeyStoreLoaderException(
-                    errorCode, getMessageFromErrorCode(errorCode));
+        public static RecoverableKeyStoreLoaderException fromErrorCode(
+                int errorCode, String message) {
+            return new RecoverableKeyStoreLoaderException(errorCode, message);
         }
 
         /**
@@ -103,33 +150,18 @@
          */
         static RecoverableKeyStoreLoaderException fromServiceSpecificException(
                 ServiceSpecificException e) throws RecoverableKeyStoreLoaderException {
-            throw RecoverableKeyStoreLoaderException.fromErrorCode(e.errorCode);
+            throw RecoverableKeyStoreLoaderException.fromErrorCode(e.errorCode, e.getMessage());
         }
 
         private RecoverableKeyStoreLoaderException(int errorCode, String message) {
             super(message);
+            mErrorCode = errorCode;
         }
 
         /** Returns errorCode. */
         public int getErrorCode() {
             return mErrorCode;
         }
-
-        /** @hide */
-        private static String getMessageFromErrorCode(int errorCode) {
-            switch (errorCode) {
-                case NO_ERROR:
-                    return "OK";
-                case SYSTEM_ERROR:
-                    return "System error";
-                case UNINITIALIZED_RECOVERY_PUBLIC_KEY:
-                    return "Recovery service is not initialized";
-                case RATE_LIMIT_EXCEEDED:
-                    return "Rate limit exceeded";
-                default:
-                    return String.valueOf("Unknown error code " + errorCode);
-            }
-        }
     }
 
     /**
@@ -154,8 +186,7 @@
             @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
             throws RecoverableKeyStoreLoaderException {
         try {
-            mBinder.initRecoveryService(
-                    rootCertificateAlias, signedPublicKeyList, UserHandle.getCallingUserId());
+            mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -174,8 +205,7 @@
     public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
             throws RecoverableKeyStoreLoaderException {
         try {
-            KeyStoreRecoveryData recoveryData =
-                    mBinder.getRecoveryData(account, UserHandle.getCallingUserId());
+            KeyStoreRecoveryData recoveryData = mBinder.getRecoveryData(account);
             return recoveryData;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -196,7 +226,7 @@
     public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
             throws RecoverableKeyStoreLoaderException {
         try {
-            mBinder.setSnapshotCreatedPendingIntent(intent, UserHandle.getCallingUserId());
+            mBinder.setSnapshotCreatedPendingIntent(intent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -209,7 +239,7 @@
      * version. Version zero is used, if no snapshots were created for the account.
      *
      * @return Map from recovery agent accounts to snapshot versions.
-     * @see KeyStoreRecoveryData.getSnapshotVersion
+     * @see KeyStoreRecoveryData#getSnapshotVersion
      * @hide
      */
     public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
@@ -218,8 +248,7 @@
             // IPC doesn't support generic Maps.
             @SuppressWarnings("unchecked")
             Map<byte[], Integer> result =
-                    (Map<byte[], Integer>)
-                            mBinder.getRecoverySnapshotVersions(UserHandle.getCallingUserId());
+                    (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions();
             return result;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -231,7 +260,7 @@
     /**
      * Server parameters used to generate new recovery key blobs. This value will be included in
      * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
-     * in vaultParams {@link startRecoverySession}
+     * in vaultParams {@link #startRecoverySession}
      *
      * @param serverParameters included in recovery key blob.
      * @see #getRecoveryData
@@ -241,7 +270,7 @@
     public void setServerParameters(long serverParameters)
             throws RecoverableKeyStoreLoaderException {
         try {
-            mBinder.setServerParameters(serverParameters, UserHandle.getCallingUserId());
+            mBinder.setServerParameters(serverParameters);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -263,7 +292,7 @@
             @NonNull String packageName, @Nullable String[] aliases, int status)
             throws NameNotFoundException, RecoverableKeyStoreLoaderException {
         try {
-            mBinder.setRecoveryStatus(packageName, aliases, status, UserHandle.getCallingUserId());
+            mBinder.setRecoveryStatus(packageName, aliases, status);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -295,7 +324,7 @@
             @SuppressWarnings("unchecked")
             Map<String, Integer> result =
                     (Map<String, Integer>)
-                            mBinder.getRecoveryStatus(packageName, UserHandle.getCallingUserId());
+                            mBinder.getRecoveryStatus(packageName);
             return result;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -315,7 +344,7 @@
             @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes)
             throws RecoverableKeyStoreLoaderException {
         try {
-            mBinder.setRecoverySecretTypes(secretTypes, UserHandle.getCallingUserId());
+            mBinder.setRecoverySecretTypes(secretTypes);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -333,7 +362,7 @@
     public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getRecoverySecretTypes()
             throws RecoverableKeyStoreLoaderException {
         try {
-            return mBinder.getRecoverySecretTypes(UserHandle.getCallingUserId());
+            return mBinder.getRecoverySecretTypes();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -351,7 +380,7 @@
     public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes()
             throws RecoverableKeyStoreLoaderException {
         try {
-            return mBinder.getPendingRecoverySecretTypes(UserHandle.getCallingUserId());
+            return mBinder.getPendingRecoverySecretTypes();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -371,7 +400,7 @@
     public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret)
             throws RecoverableKeyStoreLoaderException {
         try {
-            mBinder.recoverySecretAvailable(recoverySecret, UserHandle.getCallingUserId());
+            mBinder.recoverySecretAvailable(recoverySecret);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -410,8 +439,7 @@
                             verifierPublicKey,
                             vaultParams,
                             vaultChallenge,
-                            secrets,
-                            UserHandle.getCallingUserId());
+                            secrets);
             return recoveryClaim;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -423,20 +451,39 @@
     /**
      * Imports keys.
      *
-     * @param sessionId Id for recovery session, same as in = {@link startRecoverySession}.
+     * @param sessionId Id for recovery session, same as in
+     *     {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
      * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
      * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
      *     and session. KeyStore only uses package names from the application info in {@link
      *     KeyEntryRecoveryData}. Caller is responsibility to perform certificates check.
+     * @return Map from alias to raw key material.
      */
-    public void recoverKeys(
+    public Map<String, byte[]> recoverKeys(
             @NonNull String sessionId,
             @NonNull byte[] recoveryKeyBlob,
             @NonNull List<KeyEntryRecoveryData> applicationKeys)
             throws RecoverableKeyStoreLoaderException {
         try {
-            mBinder.recoverKeys(
-                    sessionId, recoveryKeyBlob, applicationKeys, UserHandle.getCallingUserId());
+            return (Map<String, byte[]>) mBinder.recoverKeys(
+                    sessionId, recoveryKeyBlob, applicationKeys);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
+     * raw material of the key.
+     *
+     * @throws RecoverableKeyStoreLoaderException if an error occurred generating and storing the
+     *     key.
+     */
+    public byte[] generateAndStoreKey(String alias) throws RecoverableKeyStoreLoaderException {
+        try {
+            return mBinder.generateAndStoreKey(alias);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
diff --git a/core/java/android/service/autofill/EditDistanceScorer.java b/core/java/android/service/autofill/EditDistanceScorer.java
index 0706b37..97a3868 100644
--- a/core/java/android/service/autofill/EditDistanceScorer.java
+++ b/core/java/android/service/autofill/EditDistanceScorer.java
@@ -16,8 +16,7 @@
 package android.service.autofill;
 
 import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.annotation.TestApi;
 import android.view.autofill.AutofillValue;
 
 /**
@@ -25,13 +24,20 @@
  * by the user and the expected value predicted by an autofill service.
  */
 // TODO(b/70291841): explain algorithm once it's fully implemented
-public final class EditDistanceScorer extends InternalScorer implements Scorer, Parcelable {
+/** @hide */
+@TestApi
+public final class EditDistanceScorer {
 
     private static final EditDistanceScorer sInstance = new EditDistanceScorer();
 
+    /** @hide */
+    public static final String NAME = "EDIT_DISTANCE";
+
     /**
      * Gets the singleton instance.
      */
+    @TestApi
+    /** @hide */
     public static EditDistanceScorer getInstance() {
         return sInstance;
     }
@@ -39,59 +45,32 @@
     private EditDistanceScorer() {
     }
 
-    /** @hide */
-    @Override
-    public float getScore(@NonNull AutofillValue actualValue, @NonNull String userData) {
-        if (actualValue == null || !actualValue.isText() || userData == null) return 0;
+    /**
+     * Returns the classification score between an actual {@link AutofillValue} filled
+     * by the user and the expected value predicted by an autofill service.
+     *
+     * <p>A full-match is {@code 1.0} (representing 100%), a full mismatch is {@code 0.0} and
+     * partial mathces are something in between, typically using edit-distance algorithms.
+     *
+     * @hide
+     */
+    @TestApi
+    public float getScore(@NonNull AutofillValue actualValue, @NonNull String userDataValue) {
+        if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0;
         // TODO(b/70291841): implement edit distance - currently it's returning either 0, 100%, or
         // partial match when number of chars match
         final String textValue = actualValue.getTextValue().toString();
         final int total = textValue.length();
-        if (total != userData.length()) return 0F;
+        if (total != userDataValue.length()) return 0F;
 
         int matches = 0;
         for (int i = 0; i < total; i++) {
             if (Character.toLowerCase(textValue.charAt(i)) == Character
-                    .toLowerCase(userData.charAt(i))) {
+                    .toLowerCase(userDataValue.charAt(i))) {
                 matches++;
             }
         }
 
         return ((float) matches) / total;
     }
-
-    /////////////////////////////////////
-    // Object "contract" methods. //
-    /////////////////////////////////////
-    @Override
-    public String toString() {
-        return "EditDistanceScorer";
-    }
-
-    /////////////////////////////////////
-    // Parcelable "contract" methods. //
-    /////////////////////////////////////
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        // Do nothing
-    }
-
-    public static final Parcelable.Creator<EditDistanceScorer> CREATOR =
-            new Parcelable.Creator<EditDistanceScorer>() {
-        @Override
-        public EditDistanceScorer createFromParcel(Parcel parcel) {
-            return EditDistanceScorer.getInstance();
-        }
-
-        @Override
-        public EditDistanceScorer[] newArray(int size) {
-            return new EditDistanceScorer[size];
-        }
-    };
 }
diff --git a/core/java/android/service/autofill/FieldClassification.aidl b/core/java/android/service/autofill/FieldClassification.aidl
new file mode 100644
index 0000000..42f7f02
--- /dev/null
+++ b/core/java/android/service/autofill/FieldClassification.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.autofill;
+
+parcelable FieldClassification.AlgorithmInfo;
diff --git a/core/java/android/service/autofill/FieldClassification.java b/core/java/android/service/autofill/FieldClassification.java
index 001b291..5361803 100644
--- a/core/java/android/service/autofill/FieldClassification.java
+++ b/core/java/android/service/autofill/FieldClassification.java
@@ -106,18 +106,20 @@
     /**
      * Represents the score of a {@link UserData} entry for the field.
      *
-     * <p>The score is defined by {@link #getScore()} and the entry is identified by
-     * {@link #getRemoteId()}.
+     * <p>The score is calculated by the given {@link #getAlgorithm() algorithm} and
+     * the entry is identified by {@link #getRemoteId()}.
      */
     public static final class Match {
 
         private final String mRemoteId;
         private final float mScore;
+        private final String mAlgorithm;
 
         /** @hide */
-        public Match(String remoteId, float score) {
+        public Match(String remoteId, float score, String algorithm) {
             mRemoteId = Preconditions.checkNotNull(remoteId);
             mScore = score;
+            mAlgorithm = algorithm;
         }
 
         /**
@@ -140,29 +142,46 @@
          *   <li>Any other value is a partial match.
          * </ul>
          *
-         * <p>How the score is calculated depends on the algorithm used by the {@link Scorer}
-         * implementation.
+         * <p>How the score is calculated depends on the
+         * {@link UserData.Builder#setFieldClassificationAlgorithm(String, android.os.Bundle)
+         * algorithm} used.
          */
         public float getScore() {
             return mScore;
         }
 
+        /**
+         * Gets the algorithm used to calculate this score.
+         *
+         * <p>Typically, this is either the algorithm set by
+         * {@link UserData.Builder#setFieldClassificationAlgorithm(String, android.os.Bundle)},
+         * or the
+         * {@link android.view.autofill.AutofillManager#getDefaultFieldClassificationAlgorithm()}.
+         */
+        @NonNull
+        public String getAlgorithm() {
+            return mAlgorithm;
+        }
+
         @Override
         public String toString() {
             if (!sDebug) return super.toString();
 
             final StringBuilder string = new StringBuilder("Match: remoteId=");
             Helper.appendRedacted(string, mRemoteId);
-            return string.append(", score=").append(mScore).toString();
+            return string.append(", score=").append(mScore)
+                    .append(", algorithm=").append(mAlgorithm)
+                    .toString();
         }
 
         private void writeToParcel(@NonNull Parcel parcel) {
             parcel.writeString(mRemoteId);
             parcel.writeFloat(mScore);
+            parcel.writeString(mAlgorithm);
         }
 
         private static Match readFromParcel(@NonNull Parcel parcel) {
-            return new Match(parcel.readString(), parcel.readFloat());
+            return new Match(parcel.readString(), parcel.readFloat(), parcel.readString());
         }
     }
 }
diff --git a/core/java/android/service/autofill/InternalScorer.java b/core/java/android/service/autofill/InternalScorer.java
deleted file mode 100644
index 0da5afc..0000000
--- a/core/java/android/service/autofill/InternalScorer.java
+++ /dev/null
@@ -1,40 +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;
-
-import android.annotation.NonNull;
-import android.annotation.TestApi;
-import android.os.Parcelable;
-import android.view.autofill.AutofillValue;
-
-/**
- * Superclass of all scorer the system understands. As this is not public all
- * subclasses have to implement {@link Scorer} again.
- *
- * @hide
- */
-@TestApi
-public abstract class InternalScorer implements Scorer, Parcelable {
-
-    /**
-     * Returns the classification score between an actual {@link AutofillValue} filled
-     * by the user and the expected value predicted by an autofill service.
-     *
-     * <p>A full-match is {@code 1.0} (representing 100%), a full mismatch is {@code 0.0} and
-     * partial mathces are something in between, typically using edit-distance algorithms.
-     */
-    public abstract float getScore(@NonNull AutofillValue actualValue, @NonNull String userData);
-}
diff --git a/core/java/android/service/autofill/UserData.aidl b/core/java/android/service/autofill/UserData.aidl
index 76016de..19282e0 100644
--- a/core/java/android/service/autofill/UserData.aidl
+++ b/core/java/android/service/autofill/UserData.aidl
@@ -17,4 +17,3 @@
 package android.service.autofill;
 
 parcelable UserData;
-parcelable UserData.Constraints;
diff --git a/core/java/android/service/autofill/UserData.java b/core/java/android/service/autofill/UserData.java
index f0cc360..2f9225a 100644
--- a/core/java/android/service/autofill/UserData.java
+++ b/core/java/android/service/autofill/UserData.java
@@ -25,10 +25,13 @@
 import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.content.ContentResolver;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.Settings;
+import android.service.autofill.FieldClassification.Match;
 import android.util.Log;
+import android.view.autofill.AutofillManager;
 import android.view.autofill.Helper;
 
 import com.android.internal.util.Preconditions;
@@ -49,21 +52,32 @@
     private static final int DEFAULT_MIN_VALUE_LENGTH = 5;
     private static final int DEFAULT_MAX_VALUE_LENGTH = 100;
 
-    private final InternalScorer mScorer;
+    private final String mAlgorithm;
+    private final Bundle mAlgorithmArgs;
     private final String[] mRemoteIds;
     private final String[] mValues;
 
     private UserData(Builder builder) {
-        mScorer = builder.mScorer;
+        mAlgorithm = builder.mAlgorithm;
+        mAlgorithmArgs = builder.mAlgorithmArgs;
         mRemoteIds = new String[builder.mRemoteIds.size()];
         builder.mRemoteIds.toArray(mRemoteIds);
         mValues = new String[builder.mValues.size()];
         builder.mValues.toArray(mValues);
     }
 
+    /**
+     * Gets the name of the algorithm that is used to calculate
+     * {@link Match#getScore() match scores}.
+     */
+    @Nullable
+    public String getFieldClassificationAlgorithm() {
+        return mAlgorithm;
+    }
+
     /** @hide */
-    public InternalScorer getScorer() {
-        return mScorer;
+    public Bundle getAlgorithmArgs() {
+        return mAlgorithmArgs;
     }
 
     /** @hide */
@@ -78,7 +92,9 @@
 
     /** @hide */
     public void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("Scorer: "); pw.println(mScorer);
+        pw.print(prefix); pw.print("Algorithm: "); pw.print(mAlgorithm);
+        pw.print(" Args: "); pw.println(mAlgorithmArgs);
+
         // Cannot disclose remote ids or values because they could contain PII
         pw.print(prefix); pw.print("Remote ids size: "); pw.println(mRemoteIds.length);
         for (int i = 0; i < mRemoteIds.length; i++) {
@@ -105,9 +121,10 @@
      * A builder for {@link UserData} objects.
      */
     public static final class Builder {
-        private final InternalScorer mScorer;
         private final ArrayList<String> mRemoteIds;
         private final ArrayList<String> mValues;
+        private String mAlgorithm;
+        private Bundle mAlgorithmArgs;
         private boolean mDestroyed;
 
         /**
@@ -120,13 +137,9 @@
          *   <li>{@code value} is empty
          *   <li>the length of {@code value} is lower than {@link UserData#getMinValueLength()}
          *   <li>the length of {@code value} is higher than {@link UserData#getMaxValueLength()}
-         *   <li>{@code scorer} is not instance of a class provided by the Android System.
          * </ol>
          */
-        public Builder(@NonNull Scorer scorer, @NonNull String remoteId, @NonNull String value) {
-            Preconditions.checkArgument((scorer instanceof InternalScorer),
-                    "not provided by Android System: " + scorer);
-            mScorer = (InternalScorer) scorer;
+        public Builder(@NonNull String remoteId, @NonNull String value) {
             checkValidRemoteId(remoteId);
             checkValidValue(value);
             final int capacity = getMaxUserDataSize();
@@ -137,6 +150,31 @@
         }
 
         /**
+         * Sets the algorithm used for <a href="#FieldClassification">field classification</a>.
+         *
+         * <p>The currently available algorithms can be retrieve through
+         * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}.
+         *
+         * <p><b>Note: </b>The available algorithms in the Android System can change dinamically,
+         * so it's not guaranteed that the algorithm set here is the one that will be effectually
+         * used. If the algorithm set here is not available at runtime, the
+         * {@link AutofillManager#getDefaultFieldClassificationAlgorithm()} is used instead.
+         * You can verify which algorithm was used by calling
+         * {@link FieldClassification.Match#getAlgorithm()}.
+         *
+         * @param name name of the algorithm or {@code null} to used default.
+         * @param args optional arguments to the algorithm.
+         *
+         * @return this builder
+         */
+        public Builder setFieldClassificationAlgorithm(@Nullable String name,
+                @Nullable Bundle args) {
+            mAlgorithm = name;
+            mAlgorithmArgs = args;
+            return this;
+        }
+
+        /**
          * Adds a new value for user data.
          *
          * @param remoteId unique string used to identify the user data.
@@ -211,7 +249,7 @@
     public String toString() {
         if (!sDebug) return super.toString();
 
-        final StringBuilder builder = new StringBuilder("UserData: [scorer=").append(mScorer);
+        final StringBuilder builder = new StringBuilder("UserData: [algorithm=").append(mAlgorithm);
         // Cannot disclose remote ids or values because they could contain PII
         builder.append(", remoteIds=");
         Helper.appendRedacted(builder, mRemoteIds);
@@ -231,9 +269,10 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeParcelable(mScorer, flags);
         parcel.writeStringArray(mRemoteIds);
         parcel.writeStringArray(mValues);
+        parcel.writeString(mAlgorithm);
+        parcel.writeBundle(mAlgorithmArgs);
     }
 
     public static final Parcelable.Creator<UserData> CREATOR =
@@ -243,10 +282,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 InternalScorer scorer = parcel.readParcelable(null);
             final String[] remoteIds = parcel.readStringArray();
             final String[] values = parcel.readStringArray();
-            final Builder builder = new Builder(scorer, remoteIds[0], values[0]);
+            final Builder builder = new Builder(remoteIds[0], values[0])
+                    .setFieldClassificationAlgorithm(parcel.readString(), parcel.readBundle());
             for (int i = 1; i < remoteIds.length; i++) {
                 builder.add(remoteIds[i], values[i]);
             }
diff --git a/core/java/android/service/carrier/CarrierIdentifier.java b/core/java/android/service/carrier/CarrierIdentifier.java
index b47e872..09bba4b 100644
--- a/core/java/android/service/carrier/CarrierIdentifier.java
+++ b/core/java/android/service/carrier/CarrierIdentifier.java
@@ -16,9 +16,14 @@
 
 package android.service.carrier;
 
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.uicc.IccUtils;
+
+import java.util.Objects;
+
 /**
  * Used to pass info to CarrierConfigService implementations so they can decide what values to
  * return.
@@ -40,13 +45,13 @@
 
     private String mMcc;
     private String mMnc;
-    private String mSpn;
-    private String mImsi;
-    private String mGid1;
-    private String mGid2;
+    private @Nullable String mSpn;
+    private @Nullable String mImsi;
+    private @Nullable String mGid1;
+    private @Nullable String mGid2;
 
-    public CarrierIdentifier(String mcc, String mnc, String spn, String imsi, String gid1,
-            String gid2) {
+    public CarrierIdentifier(String mcc, String mnc, @Nullable String spn, @Nullable String imsi,
+            @Nullable String gid1, @Nullable String gid2) {
         mMcc = mcc;
         mMnc = mnc;
         mSpn = spn;
@@ -55,6 +60,32 @@
         mGid2 = gid2;
     }
 
+    /**
+     * Creates a carrier identifier instance.
+     *
+     * @param mccMnc A 3-byte array as defined by 3GPP TS 24.008.
+     * @param gid1 The group identifier level 1.
+     * @param gid2 The group identifier level 2.
+     * @throws IllegalArgumentException If the length of {@code mccMnc} is not 3.
+     */
+    public CarrierIdentifier(byte[] mccMnc, @Nullable String gid1, @Nullable String gid2) {
+        if (mccMnc.length != 3) {
+            throw new IllegalArgumentException(
+                    "MCC & MNC must be set by a 3-byte array: byte[" + mccMnc.length + "]");
+        }
+        String hex = IccUtils.bytesToHexString(mccMnc);
+        mMcc = new String(new char[] {hex.charAt(1), hex.charAt(0), hex.charAt(3)});
+        if (hex.charAt(2) == 'F') {
+            mMnc = new String(new char[] {hex.charAt(5), hex.charAt(4)});
+        } else {
+            mMnc = new String(new char[] {hex.charAt(5), hex.charAt(4), hex.charAt(2)});
+        }
+        mGid1 = gid1;
+        mGid2 = gid2;
+        mSpn = null;
+        mImsi = null;
+    }
+
     /** @hide */
     public CarrierIdentifier(Parcel parcel) {
         readFromParcel(parcel);
@@ -71,26 +102,60 @@
     }
 
     /** Get the service provider name. */
+    @Nullable
     public String getSpn() {
         return mSpn;
     }
 
     /** Get the international mobile subscriber identity. */
+    @Nullable
     public String getImsi() {
         return mImsi;
     }
 
     /** Get the group identifier level 1. */
+    @Nullable
     public String getGid1() {
         return mGid1;
     }
 
     /** Get the group identifier level 2. */
+    @Nullable
     public String getGid2() {
         return mGid2;
     }
 
     @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        CarrierIdentifier that = (CarrierIdentifier) obj;
+        return Objects.equals(mMcc, that.mMcc)
+                && Objects.equals(mMnc, that.mMnc)
+                && Objects.equals(mSpn, that.mSpn)
+                && Objects.equals(mImsi, that.mImsi)
+                && Objects.equals(mGid1, that.mGid1)
+                && Objects.equals(mGid2, that.mGid2);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 1;
+        result = 31 * result + Objects.hashCode(mMcc);
+        result = 31 * result + Objects.hashCode(mMnc);
+        result = 31 * result + Objects.hashCode(mSpn);
+        result = 31 * result + Objects.hashCode(mImsi);
+        result = 31 * result + Objects.hashCode(mGid1);
+        result = 31 * result + Objects.hashCode(mGid2);
+        return result;
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/service/euicc/EuiccProfileInfo.java b/core/java/android/service/euicc/EuiccProfileInfo.java
index ba6c9a2..8e752d1 100644
--- a/core/java/android/service/euicc/EuiccProfileInfo.java
+++ b/core/java/android/service/euicc/EuiccProfileInfo.java
@@ -15,12 +15,19 @@
  */
 package android.service.euicc;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.service.carrier.CarrierIdentifier;
 import android.telephony.UiccAccessRule;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+
 /**
  * Information about an embedded profile (subscription) on an eUICC.
  *
@@ -30,18 +37,90 @@
  */
 public final class EuiccProfileInfo implements Parcelable {
 
+    /** Profile policy rules (bit mask) */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "POLICY_RULE_" }, value = {
+            POLICY_RULE_DO_NOT_DISABLE,
+            POLICY_RULE_DO_NOT_DELETE,
+            POLICY_RULE_DELETE_AFTER_DISABLING
+    })
+    public @interface PolicyRule {}
+    /** Once this profile is enabled, it cannot be disabled. */
+    public static final int POLICY_RULE_DO_NOT_DISABLE = 1;
+    /** This profile cannot be deleted. */
+    public static final int POLICY_RULE_DO_NOT_DELETE = 1 << 1;
+    /** This profile should be deleted after being disabled. */
+    public static final int POLICY_RULE_DELETE_AFTER_DISABLING = 1 << 2;
+
+    /** Class of the profile */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "PROFILE_CLASS_" }, value = {
+            PROFILE_CLASS_TESTING,
+            PROFILE_CLASS_PROVISIONING,
+            PROFILE_CLASS_OPERATIONAL,
+            PROFILE_CLASS_UNSET
+    })
+    public @interface ProfileClass {}
+    /** Testing profiles */
+    public static final int PROFILE_CLASS_TESTING = 0;
+    /** Provisioning profiles which are pre-loaded on eUICC */
+    public static final int PROFILE_CLASS_PROVISIONING = 1;
+    /** Operational profiles which can be pre-loaded or downloaded */
+    public static final int PROFILE_CLASS_OPERATIONAL = 2;
+    /**
+     * Profile class not set.
+     * @hide
+     */
+    public static final int PROFILE_CLASS_UNSET = -1;
+
+    /** State of the profile */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "PROFILE_STATE_" }, value = {
+            PROFILE_STATE_DISABLED,
+            PROFILE_STATE_ENABLED,
+            PROFILE_STATE_UNSET
+    })
+    public @interface ProfileState {}
+    /** Disabled profiles */
+    public static final int PROFILE_STATE_DISABLED = 0;
+    /** Enabled profile */
+    public static final int PROFILE_STATE_ENABLED = 1;
+    /**
+     * Profile state not set.
+     * @hide
+     */
+    public static final int PROFILE_STATE_UNSET = -1;
+
     /** The iccid of the subscription. */
     public final String iccid;
 
+    /** An optional nickname for the subscription. */
+    public final @Nullable String nickname;
+
+    /** The service provider name for the subscription. */
+    public final String serviceProviderName;
+
+    /** The profile name for the subscription. */
+    public final String profileName;
+
+    /** Profile class for the subscription. */
+    @ProfileClass public final int profileClass;
+
+    /** The profile state of the subscription. */
+    @ProfileState public final int state;
+
+    /** The operator Id of the subscription. */
+    public final CarrierIdentifier carrierIdentifier;
+
+    /** The policy rules of the subscription. */
+    @PolicyRule public final int policyRules;
+
     /**
      * Optional access rules defining which apps can manage this subscription. If unset, only the
      * platform can manage it.
      */
     public final @Nullable UiccAccessRule[] accessRules;
 
-    /** An optional nickname for the subscription. */
-    public final @Nullable String nickname;
-
     public static final Creator<EuiccProfileInfo> CREATOR = new Creator<EuiccProfileInfo>() {
         @Override
         public EuiccProfileInfo createFromParcel(Parcel in) {
@@ -54,6 +133,12 @@
         }
     };
 
+    // TODO(b/70292228): Remove this method when LPA can be updated.
+    /**
+     * @hide
+     * @deprecated - Do not use.
+     */
+    @Deprecated
     public EuiccProfileInfo(String iccid, @Nullable UiccAccessRule[] accessRules,
             @Nullable String nickname) {
         if (!TextUtils.isDigitsOnly(iccid)) {
@@ -62,23 +147,290 @@
         this.iccid = iccid;
         this.accessRules = accessRules;
         this.nickname = nickname;
+
+        this.serviceProviderName = null;
+        this.profileName = null;
+        this.profileClass = PROFILE_CLASS_UNSET;
+        this.state = PROFILE_CLASS_UNSET;
+        this.carrierIdentifier = null;
+        this.policyRules = 0;
     }
 
     private EuiccProfileInfo(Parcel in) {
         iccid = in.readString();
-        accessRules = in.createTypedArray(UiccAccessRule.CREATOR);
         nickname = in.readString();
+        serviceProviderName = in.readString();
+        profileName = in.readString();
+        profileClass = in.readInt();
+        state = in.readInt();
+        byte exist = in.readByte();
+        if (exist == (byte) 1) {
+            carrierIdentifier = CarrierIdentifier.CREATOR.createFromParcel(in);
+        } else {
+            carrierIdentifier = null;
+        }
+        policyRules = in.readInt();
+        accessRules = in.createTypedArray(UiccAccessRule.CREATOR);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(iccid);
-        dest.writeTypedArray(accessRules, flags);
         dest.writeString(nickname);
+        dest.writeString(serviceProviderName);
+        dest.writeString(profileName);
+        dest.writeInt(profileClass);
+        dest.writeInt(state);
+        if (carrierIdentifier != null) {
+            dest.writeByte((byte) 1);
+            carrierIdentifier.writeToParcel(dest, flags);
+        } else {
+            dest.writeByte((byte) 0);
+        }
+        dest.writeInt(policyRules);
+        dest.writeTypedArray(accessRules, flags);
     }
 
     @Override
     public int describeContents() {
         return 0;
     }
+
+    /** The builder to build a new {@link EuiccProfileInfo} instance. */
+    public static final class Builder {
+        public String iccid;
+        public UiccAccessRule[] accessRules;
+        public String nickname;
+        public String serviceProviderName;
+        public String profileName;
+        @ProfileClass public int profileClass;
+        @ProfileState public int state;
+        public CarrierIdentifier carrierIdentifier;
+        @PolicyRule public int policyRules;
+
+        public Builder() {}
+
+        public Builder(EuiccProfileInfo baseProfile) {
+            iccid = baseProfile.iccid;
+            nickname = baseProfile.nickname;
+            serviceProviderName = baseProfile.serviceProviderName;
+            profileName = baseProfile.profileName;
+            profileClass = baseProfile.profileClass;
+            state = baseProfile.state;
+            carrierIdentifier = baseProfile.carrierIdentifier;
+            policyRules = baseProfile.policyRules;
+            accessRules = baseProfile.accessRules;
+        }
+
+        /** Builds the profile instance. */
+        public EuiccProfileInfo build() {
+            if (iccid == null) {
+                throw new IllegalStateException("ICCID must be set for a profile.");
+            }
+            return new EuiccProfileInfo(
+                    iccid,
+                    nickname,
+                    serviceProviderName,
+                    profileName,
+                    profileClass,
+                    state,
+                    carrierIdentifier,
+                    policyRules,
+                    accessRules);
+        }
+
+        /** Sets the iccId of the subscription. */
+        public Builder setIccid(String value) {
+            if (!TextUtils.isDigitsOnly(value)) {
+                throw new IllegalArgumentException("iccid contains invalid characters: " + value);
+            }
+            iccid = value;
+            return this;
+        }
+
+        /** Sets the nickname of the subscription. */
+        public Builder setNickname(String value) {
+            nickname = value;
+            return this;
+        }
+
+        /** Sets the service provider name of the subscription. */
+        public Builder setServiceProviderName(String value) {
+            serviceProviderName = value;
+            return this;
+        }
+
+        /** Sets the profile name of the subscription. */
+        public Builder setProfileName(String value) {
+            profileName = value;
+            return this;
+        }
+
+        /** Sets the profile class of the subscription. */
+        public Builder setProfileClass(@ProfileClass int value) {
+            profileClass = value;
+            return this;
+        }
+
+        /** Sets the state of the subscription. */
+        public Builder setState(@ProfileState int value) {
+            state = value;
+            return this;
+        }
+
+        /** Sets the carrier identifier of the subscription. */
+        public Builder setCarrierIdentifier(CarrierIdentifier value) {
+            carrierIdentifier = value;
+            return this;
+        }
+
+        /** Sets the policy rules of the subscription. */
+        public Builder setPolicyRules(@PolicyRule int value) {
+            policyRules = value;
+            return this;
+        }
+
+        /** Sets the access rules of the subscription. */
+        public Builder setUiccAccessRule(@Nullable UiccAccessRule[] value) {
+            accessRules = value;
+            return this;
+        }
+    }
+
+    private EuiccProfileInfo(
+            String iccid,
+            @Nullable String nickname,
+            String serviceProviderName,
+            String profileName,
+            @ProfileClass int profileClass,
+            @ProfileState int state,
+            CarrierIdentifier carrierIdentifier,
+            @PolicyRule int policyRules,
+            @Nullable UiccAccessRule[] accessRules) {
+        this.iccid = iccid;
+        this.nickname = nickname;
+        this.serviceProviderName = serviceProviderName;
+        this.profileName = profileName;
+        this.profileClass = profileClass;
+        this.state = state;
+        this.carrierIdentifier = carrierIdentifier;
+        this.policyRules = policyRules;
+        this.accessRules = accessRules;
+    }
+
+    /** Gets the ICCID string. */
+    public String getIccid() {
+        return iccid;
+    }
+
+    /** Gets the access rules. */
+    @Nullable
+    public UiccAccessRule[] getUiccAccessRules() {
+        return accessRules;
+    }
+
+    /** Gets the nickname. */
+    public String getNickname() {
+        return nickname;
+    }
+
+    /** Gets the service provider name. */
+    public String getServiceProviderName() {
+        return serviceProviderName;
+    }
+
+    /** Gets the profile name. */
+    public String getProfileName() {
+        return profileName;
+    }
+
+    /** Gets the profile class. */
+    @ProfileClass
+    public int getProfileClass() {
+        return profileClass;
+    }
+
+    /** Gets the state of the subscription. */
+    @ProfileState
+    public int getState() {
+        return state;
+    }
+
+    /** Gets the carrier identifier. */
+    public CarrierIdentifier getCarrierIdentifier() {
+        return carrierIdentifier;
+    }
+
+    /** Gets the policy rules. */
+    @PolicyRule
+    public int getPolicyRules() {
+        return policyRules;
+    }
+
+    /** Returns whether any policy rule exists. */
+    public boolean hasPolicyRules() {
+        return policyRules != 0;
+    }
+
+    /** Checks whether a certain policy rule exists. */
+    public boolean hasPolicyRule(@PolicyRule int policy) {
+        return (policyRules & policy) != 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        EuiccProfileInfo that = (EuiccProfileInfo) obj;
+        return Objects.equals(iccid, that.iccid)
+                && Objects.equals(nickname, that.nickname)
+                && Objects.equals(serviceProviderName, that.serviceProviderName)
+                && Objects.equals(profileName, that.profileName)
+                && profileClass == that.profileClass
+                && state == that.state
+                && Objects.equals(carrierIdentifier, that.carrierIdentifier)
+                && policyRules == that.policyRules
+                && Arrays.equals(accessRules, that.accessRules);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 1;
+        result = 31 * result + Objects.hashCode(iccid);
+        result = 31 * result + Objects.hashCode(nickname);
+        result = 31 * result + Objects.hashCode(serviceProviderName);
+        result = 31 * result + Objects.hashCode(profileName);
+        result = 31 * result + profileClass;
+        result = 31 * result + state;
+        result = 31 * result + Objects.hashCode(carrierIdentifier);
+        result = 31 * result + policyRules;
+        result = 31 * result + Arrays.hashCode(accessRules);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "EuiccProfileInfo (nickname="
+                + nickname
+                + ", serviceProviderName="
+                + serviceProviderName
+                + ", profileName="
+                + profileName
+                + ", profileClass="
+                + profileClass
+                + ", state="
+                + state
+                + ", CarrierIdentifier="
+                + carrierIdentifier.toString()
+                + ", policyRules="
+                + policyRules
+                + ", accessRules="
+                + Arrays.toString(accessRules)
+                + ")";
+    }
 }
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index df0842f..be85800 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -23,6 +23,7 @@
 import android.os.RemoteException;
 import android.telephony.euicc.DownloadableSubscription;
 import android.telephony.euicc.EuiccInfo;
+import android.telephony.euicc.EuiccManager.OtaStatus;
 import android.util.ArraySet;
 
 import java.util.concurrent.LinkedBlockingQueue;
@@ -192,6 +193,18 @@
     }
 
     /**
+     * Callback class for {@link #onStartOtaIfNecessary(int, OtaStatusChangedCallback)}
+     *
+     * The status of OTA which can be {@code android.telephony.euicc.EuiccManager#EUICC_OTA_}
+     *
+     * @see IEuiccService#startOtaIfNecessary
+     */
+    public interface OtaStatusChangedCallback {
+        /** Called when OTA status is changed. */
+        void onOtaStatusChanged(int status);
+    }
+
+    /**
      * Return the EID of the eUICC.
      *
      * @param slotId ID of the SIM slot being queried. This is currently not populated but is here
@@ -203,6 +216,26 @@
     public abstract String onGetEid(int slotId);
 
     /**
+     * Return the status of OTA update.
+     *
+     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+     *     but is here to future-proof the APIs.
+     * @return The status of Euicc OTA update.
+     * @see android.telephony.euicc.EuiccManager#getOtaStatus
+     */
+    public abstract @OtaStatus int onGetOtaStatus(int slotId);
+
+    /**
+     * Perform OTA if current OS is not the latest one.
+     *
+     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+     *     but is here to future-proof the APIs.
+     * @param statusChangedCallback Function called when OTA status changed.
+     */
+    public abstract void onStartOtaIfNecessary(
+            int slotId, OtaStatusChangedCallback statusChangedCallback);
+
+    /**
      * Populate {@link DownloadableSubscription} metadata for the given downloadable subscription.
      *
      * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
@@ -385,6 +418,41 @@
         }
 
         @Override
+        public void startOtaIfNecessary(
+                int slotId, IOtaStatusChangedCallback statusChangedCallback) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    EuiccService.this.onStartOtaIfNecessary(slotId, new OtaStatusChangedCallback() {
+                        @Override
+                        public void onOtaStatusChanged(int status) {
+                            try {
+                                statusChangedCallback.onOtaStatusChanged(status);
+                            } catch (RemoteException e) {
+                                // Can't communicate with the phone process; ignore.
+                            }
+                        }
+                    });
+                }
+            });
+        }
+
+        @Override
+        public void getOtaStatus(int slotId, IGetOtaStatusCallback callback) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    int status = EuiccService.this.onGetOtaStatus(slotId);
+                    try {
+                        callback.onSuccess(status);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
+        }
+
+        @Override
         public void getDownloadableSubscriptionMetadata(int slotId,
                 DownloadableSubscription subscription,
                 boolean forceDeactivateSim,
diff --git a/core/java/android/service/euicc/IEuiccService.aidl b/core/java/android/service/euicc/IEuiccService.aidl
index e10dd8c..45be527 100644
--- a/core/java/android/service/euicc/IEuiccService.aidl
+++ b/core/java/android/service/euicc/IEuiccService.aidl
@@ -24,6 +24,8 @@
 import android.service.euicc.IGetEidCallback;
 import android.service.euicc.IGetEuiccInfoCallback;
 import android.service.euicc.IGetEuiccProfileInfoListCallback;
+import android.service.euicc.IGetOtaStatusCallback;
+import android.service.euicc.IOtaStatusChangedCallback;
 import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback;
 import android.service.euicc.ISwitchToSubscriptionCallback;
 import android.service.euicc.IUpdateSubscriptionNicknameCallback;
@@ -37,6 +39,8 @@
     void getDownloadableSubscriptionMetadata(int slotId, in DownloadableSubscription subscription,
             boolean forceDeactivateSim, in IGetDownloadableSubscriptionMetadataCallback callback);
     void getEid(int slotId, in IGetEidCallback callback);
+    void getOtaStatus(int slotId, in IGetOtaStatusCallback callback);
+    void startOtaIfNecessary(int slotId, in IOtaStatusChangedCallback statusChangedCallback);
     void getEuiccProfileInfoList(int slotId, in IGetEuiccProfileInfoListCallback callback);
     void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim,
             in IGetDefaultDownloadableSubscriptionListCallback callback);
diff --git a/core/java/android/service/autofill/Scorer.java b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl
similarity index 61%
copy from core/java/android/service/autofill/Scorer.java
copy to core/java/android/service/euicc/IGetOtaStatusCallback.aidl
index c401855..f667888 100644
--- a/core/java/android/service/autofill/Scorer.java
+++ b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl
@@ -13,16 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.service.autofill;
 
-/**
- * Helper class used to calculate a score.
- *
- * <p>Typically used to calculate the
- * <a href="AutofillService.html#FieldClassification">field classification</a> score between an
- * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value
- * predicted by an autofill service.
- */
-public interface Scorer {
+package android.service.euicc;
 
-}
+/** @hide */
+oneway interface IGetOtaStatusCallback {
+    void onSuccess(int status);
+}
\ No newline at end of file
diff --git a/core/java/android/service/autofill/Scorer.java b/core/java/android/service/euicc/IOtaStatusChangedCallback.aidl
similarity index 61%
copy from core/java/android/service/autofill/Scorer.java
copy to core/java/android/service/euicc/IOtaStatusChangedCallback.aidl
index c401855..caec75f 100644
--- a/core/java/android/service/autofill/Scorer.java
+++ b/core/java/android/service/euicc/IOtaStatusChangedCallback.aidl
@@ -13,16 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.service.autofill;
 
-/**
- * Helper class used to calculate a score.
- *
- * <p>Typically used to calculate the
- * <a href="AutofillService.html#FieldClassification">field classification</a> score between an
- * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value
- * predicted by an autofill service.
- */
-public interface Scorer {
+package android.service.euicc;
 
-}
+/** @hide */
+oneway interface IOtaStatusChangedCallback {
+    void onOtaStatusChanged(int status);
+}
\ No newline at end of file
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index b8f2191..dccce40 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -27,7 +27,7 @@
     void setDesiredSize(int width, int height);
     void setDisplayPadding(in Rect padding);
     void setVisibility(boolean visible);
-    void setInAmbientMode(boolean inAmbientDisplay);
+    void setInAmbientMode(boolean inAmbientDisplay, boolean animated);
     void dispatchPointer(in MotionEvent event);
     void dispatchWallpaperCommand(String action, int x, int y,
             int z, in Bundle extras);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 595bfb7..8588df7 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -563,9 +563,12 @@
          * Called when the device enters or exits ambient mode.
          *
          * @param inAmbientMode {@code true} if in ambient mode.
+         * @param animated {@code true} if you'll have te opportunity of animating your transition
+         *                 {@code false} when the screen will blank and the wallpaper should be
+         *                 set to ambient mode immediately.
          * @hide
          */
-        public void onAmbientModeChanged(boolean inAmbientMode) {
+        public void onAmbientModeChanged(boolean inAmbientMode, boolean animated) {
         }
 
         /**
@@ -1021,18 +1024,20 @@
          * Executes life cycle event and updates internal ambient mode state based on
          * message sent from handler.
          *
-         * @param inAmbientMode True if in ambient mode.
+         * @param inAmbientMode {@code true} if in ambient mode.
+         * @param animated {@code true} if the transition will be animated.
          * @hide
          */
         @VisibleForTesting
-        public void doAmbientModeChanged(boolean inAmbientMode) {
+        public void doAmbientModeChanged(boolean inAmbientMode, boolean animated) {
             if (!mDestroyed) {
                 if (DEBUG) {
-                    Log.v(TAG, "onAmbientModeChanged(" + inAmbientMode + "): " + this);
+                    Log.v(TAG, "onAmbientModeChanged(" + inAmbientMode + ", "
+                            + animated + "): " + this);
                 }
                 mIsInAmbientMode = inAmbientMode;
                 if (mCreated) {
-                    onAmbientModeChanged(inAmbientMode);
+                    onAmbientModeChanged(inAmbientMode, animated);
                 }
             }
         }
@@ -1278,8 +1283,10 @@
         }
 
         @Override
-        public void setInAmbientMode(boolean inAmbientDisplay) throws RemoteException {
-            Message msg = mCaller.obtainMessageI(DO_IN_AMBIENT_MODE, inAmbientDisplay ? 1 : 0);
+        public void setInAmbientMode(boolean inAmbientDisplay, boolean animated)
+                throws RemoteException {
+            Message msg = mCaller.obtainMessageII(DO_IN_AMBIENT_MODE, inAmbientDisplay ? 1 : 0,
+                    animated ? 1 : 0);
             mCaller.sendMessage(msg);
         }
 
@@ -1350,7 +1357,7 @@
                     return;
                 }
                 case DO_IN_AMBIENT_MODE: {
-                    mEngine.doAmbientModeChanged(message.arg1 != 0);
+                    mEngine.doAmbientModeChanged(message.arg1 != 0, message.arg2 != 0);
                     return;
                 }
                 case MSG_UPDATE_SURFACE:
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 6bca37a..18431ca 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -1096,6 +1096,11 @@
 
         public void onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend) {
             if (o instanceof UpdateLayout) {
+                if (start > end) {
+                    // Bug: 67926915 start cannot be determined, fallback to reflow from start
+                    // instead of causing an exception
+                    start = 0;
+                }
                 reflow(s, start, end - start, end - start);
                 reflow(s, nstart, nend - nstart, nend - nstart);
             }
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index 8c90156..ad3b4b6 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -20,11 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
-import android.icu.text.DecimalFormat;
 import android.icu.text.MeasureFormat;
-import android.icu.text.NumberFormat;
-import android.icu.text.UnicodeSet;
-import android.icu.text.UnicodeSetSpanner;
 import android.icu.util.Measure;
 import android.icu.util.MeasureUnit;
 import android.net.NetworkUtils;
@@ -32,8 +28,6 @@
 import android.text.TextUtils;
 import android.view.View;
 
-import java.lang.reflect.Constructor;
-import java.math.BigDecimal;
 import java.util.Locale;
 
 /**
@@ -43,8 +37,6 @@
 public final class Formatter {
 
     /** {@hide} */
-    public static final int FLAG_DEFAULT = 0;
-    /** {@hide} */
     public static final int FLAG_SHORTER = 1 << 0;
     /** {@hide} */
     public static final int FLAG_CALCULATE_ROUNDED = 1 << 1;
@@ -66,9 +58,7 @@
         return context.getResources().getConfiguration().getLocales().get(0);
     }
 
-    /**
-     * Wraps the source string in bidi formatting characters in RTL locales.
-     */
+    /* Wraps the source string in bidi formatting characters in RTL locales */
     private static String bidiWrap(@NonNull Context context, String source) {
         final Locale locale = localeFromContext(context);
         if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL) {
@@ -97,7 +87,12 @@
      * @return formatted string with the number
      */
     public static String formatFileSize(@Nullable Context context, long sizeBytes) {
-        return formatFileSize(context, sizeBytes, FLAG_DEFAULT);
+        if (context == null) {
+            return "";
+        }
+        final BytesResult res = formatBytes(context.getResources(), sizeBytes, 0);
+        return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix,
+                res.value, res.units));
     }
 
     /**
@@ -105,207 +100,88 @@
      * (showing fewer digits of precision).
      */
     public static String formatShortFileSize(@Nullable Context context, long sizeBytes) {
-        return formatFileSize(context, sizeBytes, FLAG_SHORTER);
-    }
-
-    private static String formatFileSize(@Nullable Context context, long sizeBytes, int flags) {
         if (context == null) {
             return "";
         }
-        final RoundedBytesResult res = RoundedBytesResult.roundBytes(sizeBytes, flags);
-        return bidiWrap(context, formatRoundedBytesResult(context, res));
-    }
-
-    private static String getSuffixOverride(@NonNull Resources res, MeasureUnit unit) {
-        if (unit == MeasureUnit.BYTE) {
-            return res.getString(com.android.internal.R.string.byteShort);
-        } else { // unit == PETABYTE
-            return res.getString(com.android.internal.R.string.petabyteShort);
-        }
-    }
-
-    private static NumberFormat getNumberFormatter(Locale locale, int fractionDigits) {
-        final NumberFormat numberFormatter = NumberFormat.getInstance(locale);
-        numberFormatter.setMinimumFractionDigits(fractionDigits);
-        numberFormatter.setMaximumFractionDigits(fractionDigits);
-        numberFormatter.setGroupingUsed(false);
-        if (numberFormatter instanceof DecimalFormat) {
-            // We do this only for DecimalFormat, since in the general NumberFormat case, calling
-            // setRoundingMode may throw an exception.
-            numberFormatter.setRoundingMode(BigDecimal.ROUND_HALF_UP);
-        }
-        return numberFormatter;
-    }
-
-    private static String deleteFirstFromString(String source, String toDelete) {
-        final int location = source.indexOf(toDelete);
-        if (location == -1) {
-            return source;
-        } else {
-            return source.substring(0, location)
-                    + source.substring(location + toDelete.length(), source.length());
-        }
-    }
-
-    private static String formatMeasureShort(Locale locale, NumberFormat numberFormatter,
-            float value, MeasureUnit units) {
-        final MeasureFormat measureFormatter = MeasureFormat.getInstance(
-                locale, MeasureFormat.FormatWidth.SHORT, numberFormatter);
-        return measureFormatter.format(new Measure(value, units));
-    }
-
-    private static final UnicodeSetSpanner SPACES_AND_CONTROLS =
-            new UnicodeSetSpanner(new UnicodeSet("[[:Zs:][:Cf:]]").freeze());
-
-    private static String formatRoundedBytesResult(
-            @NonNull Context context, @NonNull RoundedBytesResult input) {
-        final Locale locale = localeFromContext(context);
-        final NumberFormat numberFormatter = getNumberFormatter(locale, input.fractionDigits);
-        if (input.units == MeasureUnit.BYTE || input.units == PETABYTE) {
-            // ICU spells out "byte" instead of "B", and can't format petabytes yet.
-            final String formattedNumber = numberFormatter.format(input.value);
-            return context.getString(com.android.internal.R.string.fileSizeSuffix,
-                    formattedNumber, getSuffixOverride(context.getResources(), input.units));
-        } else {
-            return formatMeasureShort(locale, numberFormatter, input.value, input.units);
-        }
+        final BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SHORTER);
+        return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix,
+                res.value, res.units));
     }
 
     /** {@hide} */
     public static BytesResult formatBytes(Resources res, long sizeBytes, int flags) {
-        final RoundedBytesResult rounded = RoundedBytesResult.roundBytes(sizeBytes, flags);
-        final Locale locale = res.getConfiguration().getLocales().get(0);
-        final NumberFormat numberFormatter = getNumberFormatter(locale, rounded.fractionDigits);
-        final String formattedNumber = numberFormatter.format(rounded.value);
-        final String units;
-        if (rounded.units == MeasureUnit.BYTE || rounded.units == PETABYTE) {
-            // ICU spells out "byte" instead of "B", and can't format petabytes yet.
-            units = getSuffixOverride(res, rounded.units);
-        } else {
-            // Since ICU does not give us access to the pattern, we need to extract the unit string
-            // from ICU, which we do by taking out the formatted number out of the formatted string
-            // and trimming the result of spaces and controls.
-            final String formattedMeasure = formatMeasureShort(
-                    locale, numberFormatter, rounded.value, rounded.units);
-            final String numberRemoved = deleteFirstFromString(formattedMeasure, formattedNumber);
-            units = SPACES_AND_CONTROLS.trim(numberRemoved).toString();
+        final boolean isNegative = (sizeBytes < 0);
+        float result = isNegative ? -sizeBytes : sizeBytes;
+        int suffix = com.android.internal.R.string.byteShort;
+        long mult = 1;
+        if (result > 900) {
+            suffix = com.android.internal.R.string.kilobyteShort;
+            mult = 1000;
+            result = result / 1000;
         }
-        return new BytesResult(formattedNumber, units, rounded.roundedBytes);
-    }
-
-    /**
-     * ICU doesn't support PETABYTE yet. Fake it so that we can treat all units the same way.
-     */
-    private static final MeasureUnit PETABYTE = createPetaByte();
-
-    /**
-     * Create a petabyte MeasureUnit without registering it with ICU.
-     * ICU doesn't support user-create MeasureUnit and the only public (but hidden) method to do so
-     * is {@link MeasureUnit#internalGetInstance(String, String)} which also registers the unit as
-     * an available type and thus leaks it to code that doesn't expect or support it.
-     * <p>This method uses reflection to create an instance of MeasureUnit to avoid leaking it. This
-     * instance is <b>only</b> to be used in this class.
-     */
-    private static MeasureUnit createPetaByte() {
-        try {
-            Constructor<MeasureUnit> constructor = MeasureUnit.class
-                    .getDeclaredConstructor(String.class, String.class);
-            constructor.setAccessible(true);
-            return constructor.newInstance("digital", "petabyte");
-        } catch (ReflectiveOperationException e) {
-            throw new RuntimeException("Failed to create petabyte MeasureUnit", e);
+        if (result > 900) {
+            suffix = com.android.internal.R.string.megabyteShort;
+            mult *= 1000;
+            result = result / 1000;
         }
-    }
-
-    private static class RoundedBytesResult {
-        public final float value;
-        public final MeasureUnit units;
-        public final int fractionDigits;
-        public final long roundedBytes;
-
-        private RoundedBytesResult(
-                float value, MeasureUnit units, int fractionDigits, long roundedBytes) {
-            this.value = value;
-            this.units = units;
-            this.fractionDigits = fractionDigits;
-            this.roundedBytes = roundedBytes;
+        if (result > 900) {
+            suffix = com.android.internal.R.string.gigabyteShort;
+            mult *= 1000;
+            result = result / 1000;
         }
-
-        /**
-         * Returns a RoundedBytesResult object based on the input size in bytes and the rounding
-         * flags. The result can be used for formatting.
-         */
-        static RoundedBytesResult roundBytes(long sizeBytes, int flags) {
-            final boolean isNegative = (sizeBytes < 0);
-            float result = isNegative ? -sizeBytes : sizeBytes;
-            MeasureUnit units = MeasureUnit.BYTE;
-            long mult = 1;
-            if (result > 900) {
-                units = MeasureUnit.KILOBYTE;
-                mult = 1000;
-                result = result / 1000;
-            }
-            if (result > 900) {
-                units = MeasureUnit.MEGABYTE;
-                mult *= 1000;
-                result = result / 1000;
-            }
-            if (result > 900) {
-                units = MeasureUnit.GIGABYTE;
-                mult *= 1000;
-                result = result / 1000;
-            }
-            if (result > 900) {
-                units = MeasureUnit.TERABYTE;
-                mult *= 1000;
-                result = result / 1000;
-            }
-            if (result > 900) {
-                units = PETABYTE;
-                mult *= 1000;
-                result = result / 1000;
-            }
-            // Note we calculate the rounded long by ourselves, but still let NumberFormat compute
-            // the rounded value. NumberFormat.format(0.1) might not return "0.1" due to floating
-            // point errors.
-            final int roundFactor;
-            final int roundDigits;
-            if (mult == 1 || result >= 100) {
-                roundFactor = 1;
-                roundDigits = 0;
-            } else if (result < 1) {
+        if (result > 900) {
+            suffix = com.android.internal.R.string.terabyteShort;
+            mult *= 1000;
+            result = result / 1000;
+        }
+        if (result > 900) {
+            suffix = com.android.internal.R.string.petabyteShort;
+            mult *= 1000;
+            result = result / 1000;
+        }
+        // Note we calculate the rounded long by ourselves, but still let String.format()
+        // compute the rounded value. String.format("%f", 0.1) might not return "0.1" due to
+        // floating point errors.
+        final int roundFactor;
+        final String roundFormat;
+        if (mult == 1 || result >= 100) {
+            roundFactor = 1;
+            roundFormat = "%.0f";
+        } else if (result < 1) {
+            roundFactor = 100;
+            roundFormat = "%.2f";
+        } else if (result < 10) {
+            if ((flags & FLAG_SHORTER) != 0) {
+                roundFactor = 10;
+                roundFormat = "%.1f";
+            } else {
                 roundFactor = 100;
-                roundDigits = 2;
-            } else if (result < 10) {
-                if ((flags & FLAG_SHORTER) != 0) {
-                    roundFactor = 10;
-                    roundDigits = 1;
-                } else {
-                    roundFactor = 100;
-                    roundDigits = 2;
-                }
-            } else { // 10 <= result < 100
-                if ((flags & FLAG_SHORTER) != 0) {
-                    roundFactor = 1;
-                    roundDigits = 0;
-                } else {
-                    roundFactor = 100;
-                    roundDigits = 2;
-                }
+                roundFormat = "%.2f";
             }
-
-            if (isNegative) {
-                result = -result;
+        } else { // 10 <= result < 100
+            if ((flags & FLAG_SHORTER) != 0) {
+                roundFactor = 1;
+                roundFormat = "%.0f";
+            } else {
+                roundFactor = 100;
+                roundFormat = "%.2f";
             }
-
-            // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like
-            // 80PB so it's okay (for now)...
-            final long roundedBytes =
-                    (flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0
-                    : (((long) Math.round(result * roundFactor)) * mult / roundFactor);
-
-            return new RoundedBytesResult(result, units, roundDigits, roundedBytes);
         }
+
+        if (isNegative) {
+            result = -result;
+        }
+        final String roundedString = String.format(roundFormat, result);
+
+        // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like 80PB so
+        // it's okay (for now)...
+        final long roundedBytes =
+                (flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0
+                : (((long) Math.round(result * roundFactor)) * mult / roundFactor);
+
+        final String units = res.getString(suffix);
+
+        return new BytesResult(roundedString, units, roundedBytes);
     }
 
     /**
diff --git a/core/java/android/util/DataUnit.java b/core/java/android/util/DataUnit.java
new file mode 100644
index 0000000..3cc1bd8
--- /dev/null
+++ b/core/java/android/util/DataUnit.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Constants for common byte-related units. Note that both SI and IEC units are
+ * supported, and you'll need to pick the correct one for your use-case.
+ * <p>
+ * This design is mirrored after {@link TimeUnit} and {@link ChronoUnit}.
+ *
+ * @hide
+ */
+public enum DataUnit {
+    KILOBYTES { @Override public long toBytes(long v) { return v * 1_000; } },
+    MEGABYTES { @Override public long toBytes(long v) { return v * 1_000_000; } },
+    GIGABYTES { @Override public long toBytes(long v) { return v * 1_000_000_000; } },
+    KIBIBYTES { @Override public long toBytes(long v) { return v * 1_024; } },
+    MEBIBYTES { @Override public long toBytes(long v) { return v * 1_048_576; } },
+    GIBIBYTES { @Override public long toBytes(long v) { return v * 1_073_741_824; } };
+
+    public long toBytes(long v) {
+        throw new AbstractMethodError();
+    }
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 87f3bc7..1d5392e 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -38,12 +38,12 @@
     static {
         DEFAULT_FLAGS = new HashMap<>();
         DEFAULT_FLAGS.put("device_info_v2", "true");
-        DEFAULT_FLAGS.put("new_settings_suggestion", "true");
         DEFAULT_FLAGS.put("settings_search_v2", "true");
         DEFAULT_FLAGS.put("settings_app_info_v2", "false");
         DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
         DEFAULT_FLAGS.put("settings_battery_v2", "false");
         DEFAULT_FLAGS.put("settings_battery_display_app_list", "false");
+        DEFAULT_FLAGS.put("settings_security_settings_v2", "true");
     }
 
     /**
diff --git a/core/java/android/util/KeyValueListParser.java b/core/java/android/util/KeyValueListParser.java
index 0a00794..7eef63e 100644
--- a/core/java/android/util/KeyValueListParser.java
+++ b/core/java/android/util/KeyValueListParser.java
@@ -17,6 +17,9 @@
 
 import android.text.TextUtils;
 
+import java.time.Duration;
+import java.time.format.DateTimeParseException;
+
 /**
  * Parses a list of key=value pairs, separated by some delimiter, and puts the results in
  * an internal Map. Values can be then queried by key, or if not found, a default value
@@ -189,4 +192,24 @@
     public String keyAt(int index) {
         return mValues.keyAt(index);
     }
+
+    /**
+     * {@hide}
+     * Parse a duration in millis based on java.time.Duration or just a number (millis)
+     */
+    public long getDurationMillis(String key, long def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            try {
+                if (value.startsWith("P") || value.startsWith("p")) {
+                    return Duration.parse(value).toMillis();
+                } else {
+                    return Long.parseLong(value);
+                }
+            } catch (NumberFormatException | DateTimeParseException e) {
+                // fallthrough
+            }
+        }
+        return def;
+    }
 }
diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
index 4f76463..68d347c 100644
--- a/core/java/android/util/SparseBooleanArray.java
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -117,7 +117,11 @@
         }
     }
 
-    /** @hide */
+    /**
+     * Removes the mapping at the specified index.
+     * <p>
+     * For indices outside of the range {@code 0...size()-1}, the behavior is undefined.
+     */
     public void removeAt(int index) {
         System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
         System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java
index 26a3c36..c25b272 100644
--- a/core/java/android/util/StatsManager.java
+++ b/core/java/android/util/StatsManager.java
@@ -53,7 +53,7 @@
      * @return true if successful
      */
     @RequiresPermission(Manifest.permission.DUMP)
-    public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) {
+    public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) {
         synchronized (this) {
             try {
                 IStatsManager service = getIStatsManagerLocked();
@@ -76,7 +76,7 @@
      * @return true if successful
      */
     @RequiresPermission(Manifest.permission.DUMP)
-    public boolean removeConfiguration(String configKey) {
+    public boolean removeConfiguration(long configKey) {
         synchronized (this) {
             try {
                 IStatsManager service = getIStatsManagerLocked();
@@ -100,7 +100,7 @@
      * @return Serialized ConfigMetricsReportList proto. Returns null on failure.
      */
     @RequiresPermission(Manifest.permission.DUMP)
-    public byte[] getData(String configKey) {
+    public byte[] getData(long configKey) {
         synchronized (this) {
             try {
                 IStatsManager service = getIStatsManagerLocked();
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 0a54f3a..530937e 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -16,6 +16,21 @@
 
 package android.util.apk;
 
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
 import android.util.ArrayMap;
 import android.util.Pair;
 
@@ -23,56 +38,47 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.RandomAccessFile;
-import java.math.BigInteger;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.security.DigestException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Principal;
 import java.security.PublicKey;
 import java.security.Signature;
 import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
-import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateFactory;
-import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.InvalidKeySpecException;
-import java.security.spec.MGF1ParameterSpec;
-import java.security.spec.PSSParameterSpec;
 import java.security.spec.X509EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * APK Signature Scheme v2 verifier.
  *
+ * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
+ * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
+ * uncompressed contents of ZIP entries.
+ *
+ * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a>
+ *
  * @hide for internal use only.
  */
 public class ApkSignatureSchemeV2Verifier {
 
     /**
-     * {@code .SF} file header section attribute indicating that the APK is signed not just with
-     * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
-     * facilitates v2 signature stripping detection.
-     *
-     * <p>The attribute contains a comma-separated set of signature scheme IDs.
+     * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
      */
-    public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
     public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
 
+    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+
     /**
      * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
      *
@@ -103,7 +109,7 @@
     /**
      * Returns the certificates associated with each signer for the given APK without verification.
      * This method is dangerous and should not be used, unless the caller is absolutely certain the
-     * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme V2
+     * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v2
      * Block while gathering signer information.  The APK contents are not verified.
      *
      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
@@ -120,6 +126,7 @@
             return verify(apk, verifyIntegrity);
         }
     }
+
     /**
      * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
      * associated with each signer.
@@ -144,30 +151,7 @@
      */
     private static SignatureInfo findSignature(RandomAccessFile apk)
             throws IOException, SignatureNotFoundException {
-        // Find the ZIP End of Central Directory (EoCD) record.
-        Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
-        ByteBuffer eocd = eocdAndOffsetInFile.first;
-        long eocdOffset = eocdAndOffsetInFile.second;
-        if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
-            throw new SignatureNotFoundException("ZIP64 APK not supported");
-        }
-
-        // Find the APK Signing Block. The block immediately precedes the Central Directory.
-        long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
-        Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
-                findApkSigningBlock(apk, centralDirOffset);
-        ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
-        long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
-
-        // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
-        ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock);
-
-        return new SignatureInfo(
-                apkSignatureSchemeV2Block,
-                apkSigningBlockOffset,
-                centralDirOffset,
-                eocdOffset,
-                eocd);
+        return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
     }
 
     /**
@@ -218,7 +202,7 @@
         }
 
         if (doVerifyIntegrity) {
-            verifyIntegrity(
+            ApkSigningBlockUtils.verifyIntegrity(
                     contentDigests,
                     apkFileDescriptor,
                     signatureInfo.apkSigningBlockOffset,
@@ -349,7 +333,8 @@
             } catch (CertificateException e) {
                 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
             }
-            certificate = new VerbatimX509Certificate(certificate, encodedCert);
+            certificate = new VerbatimX509Certificate(
+                    certificate, encodedCert);
             certs.add(certificate);
         }
 
@@ -363,235 +348,44 @@
                     "Public key mismatch between certificate and signature record");
         }
 
+        ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
+        verifyAdditionalAttributes(additionalAttrs);
+
         return certs.toArray(new X509Certificate[certs.size()]);
     }
 
-    private static void verifyIntegrity(
-            Map<Integer, byte[]> expectedDigests,
-            FileDescriptor apkFileDescriptor,
-            long apkSigningBlockOffset,
-            long centralDirOffset,
-            long eocdOffset,
-            ByteBuffer eocdBuf) throws SecurityException {
+    // Attribute to check whether a newer APK Signature Scheme signature was stripped
+    private static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d;
 
-        if (expectedDigests.isEmpty()) {
-            throw new SecurityException("No digests provided");
-        }
-
-        // We need to verify the integrity of the following three sections of the file:
-        // 1. Everything up to the start of the APK Signing Block.
-        // 2. ZIP Central Directory.
-        // 3. ZIP End of Central Directory (EoCD).
-        // Each of these sections is represented as a separate DataSource instance below.
-
-        // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
-        // avoid wasting physical memory. In most APK verification scenarios, the contents of the
-        // APK are already there in the OS's page cache and thus mmap does not use additional
-        // physical memory.
-        DataSource beforeApkSigningBlock =
-                new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
-        DataSource centralDir =
-                new MemoryMappedFileDataSource(
-                        apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
-
-        // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
-        // Central Directory must be considered to point to the offset of the APK Signing Block.
-        eocdBuf = eocdBuf.duplicate();
-        eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
-        ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
-        DataSource eocd = new ByteBufferDataSource(eocdBuf);
-
-        int[] digestAlgorithms = new int[expectedDigests.size()];
-        int digestAlgorithmCount = 0;
-        for (int digestAlgorithm : expectedDigests.keySet()) {
-            digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
-            digestAlgorithmCount++;
-        }
-        byte[][] actualDigests;
-        try {
-            actualDigests =
-                    computeContentDigests(
-                            digestAlgorithms,
-                            new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
-        } catch (DigestException e) {
-            throw new SecurityException("Failed to compute digest(s) of contents", e);
-        }
-        for (int i = 0; i < digestAlgorithms.length; i++) {
-            int digestAlgorithm = digestAlgorithms[i];
-            byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
-            byte[] actualDigest = actualDigests[i];
-            if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
-                throw new SecurityException(
-                        getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
-                                + " digest of contents did not verify");
+    private static void verifyAdditionalAttributes(ByteBuffer attrs)
+            throws SecurityException, IOException {
+        while (attrs.hasRemaining()) {
+            ByteBuffer attr = getLengthPrefixedSlice(attrs);
+            if (attr.remaining() < 4) {
+                throw new IOException("Remaining buffer too short to contain additional attribute "
+                        + "ID. Remaining: " + attr.remaining());
             }
-        }
-    }
-
-    private static byte[][] computeContentDigests(
-            int[] digestAlgorithms,
-            DataSource[] contents) throws DigestException {
-        // For each digest algorithm the result is computed as follows:
-        // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
-        //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
-        //    No chunks are produced for empty (zero length) segments.
-        // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
-        //    length in bytes (uint32 little-endian) and the chunk's contents.
-        // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
-        //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
-        //    segments in-order.
-
-        long totalChunkCountLong = 0;
-        for (DataSource input : contents) {
-            totalChunkCountLong += getChunkCount(input.size());
-        }
-        if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
-            throw new DigestException("Too many chunks: " + totalChunkCountLong);
-        }
-        int totalChunkCount = (int) totalChunkCountLong;
-
-        byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
-        for (int i = 0; i < digestAlgorithms.length; i++) {
-            int digestAlgorithm = digestAlgorithms[i];
-            int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
-            byte[] concatenationOfChunkCountAndChunkDigests =
-                    new byte[5 + totalChunkCount * digestOutputSizeBytes];
-            concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
-            setUnsignedInt32LittleEndian(
-                    totalChunkCount,
-                    concatenationOfChunkCountAndChunkDigests,
-                    1);
-            digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
-        }
-
-        byte[] chunkContentPrefix = new byte[5];
-        chunkContentPrefix[0] = (byte) 0xa5;
-        int chunkIndex = 0;
-        MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
-        for (int i = 0; i < digestAlgorithms.length; i++) {
-            String jcaAlgorithmName =
-                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
-            try {
-                mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
-            } catch (NoSuchAlgorithmException e) {
-                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
-            }
-        }
-        // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
-        // into how to parallelize (if at all) based on the capabilities of the hardware on which
-        // this code is running and based on the size of input.
-        DataDigester digester = new MultipleDigestDataDigester(mds);
-        int dataSourceIndex = 0;
-        for (DataSource input : contents) {
-            long inputOffset = 0;
-            long inputRemaining = input.size();
-            while (inputRemaining > 0) {
-                int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
-                setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
-                for (int i = 0; i < mds.length; i++) {
-                    mds[i].update(chunkContentPrefix);
-                }
-                try {
-                    input.feedIntoDataDigester(digester, inputOffset, chunkSize);
-                } catch (IOException e) {
-                    throw new DigestException(
-                            "Failed to digest chunk #" + chunkIndex + " of section #"
-                                    + dataSourceIndex,
-                            e);
-                }
-                for (int i = 0; i < digestAlgorithms.length; i++) {
-                    int digestAlgorithm = digestAlgorithms[i];
-                    byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
-                    int expectedDigestSizeBytes =
-                            getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
-                    MessageDigest md = mds[i];
-                    int actualDigestSizeBytes =
-                            md.digest(
-                                    concatenationOfChunkCountAndChunkDigests,
-                                    5 + chunkIndex * expectedDigestSizeBytes,
-                                    expectedDigestSizeBytes);
-                    if (actualDigestSizeBytes != expectedDigestSizeBytes) {
-                        throw new RuntimeException(
-                                "Unexpected output size of " + md.getAlgorithm() + " digest: "
-                                        + actualDigestSizeBytes);
+            int id = attr.getInt();
+            switch (id) {
+                case STRIPPING_PROTECTION_ATTR_ID:
+                    if (attr.remaining() < 4) {
+                        throw new IOException("V2 Signature Scheme Stripping Protection Attribute "
+                                + " value too small.  Expected 4 bytes, but found "
+                                + attr.remaining());
                     }
-                }
-                inputOffset += chunkSize;
-                inputRemaining -= chunkSize;
-                chunkIndex++;
+                    int vers = attr.getInt();
+                    if (vers == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+                        throw new SecurityException("V2 signature indicates APK is signed using APK"
+                                + " Signature Scheme v3, but none was found. Signature stripped?");
+                    }
+                    break;
+                default:
+                    // not the droid we're looking for, move along, move along.
+                    break;
             }
-            dataSourceIndex++;
         }
-
-        byte[][] result = new byte[digestAlgorithms.length][];
-        for (int i = 0; i < digestAlgorithms.length; i++) {
-            int digestAlgorithm = digestAlgorithms[i];
-            byte[] input = digestsOfChunks[i];
-            String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
-            MessageDigest md;
-            try {
-                md = MessageDigest.getInstance(jcaAlgorithmName);
-            } catch (NoSuchAlgorithmException e) {
-                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
-            }
-            byte[] output = md.digest(input);
-            result[i] = output;
-        }
-        return result;
+        return;
     }
-
-    /**
-     * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
-     *
-     * @throws IOException if an I/O error occurs while reading the file.
-     * @throws SignatureNotFoundException if the EoCD could not be found.
-     */
-    private static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
-            throws IOException, SignatureNotFoundException {
-        Pair<ByteBuffer, Long> eocdAndOffsetInFile =
-                ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
-        if (eocdAndOffsetInFile == null) {
-            throw new SignatureNotFoundException(
-                    "Not an APK file: ZIP End of Central Directory record not found");
-        }
-        return eocdAndOffsetInFile;
-    }
-
-    private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
-            throws SignatureNotFoundException {
-        // Look up the offset of ZIP Central Directory.
-        long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
-        if (centralDirOffset > eocdOffset) {
-            throw new SignatureNotFoundException(
-                    "ZIP Central Directory offset out of range: " + centralDirOffset
-                    + ". ZIP End of Central Directory offset: " + eocdOffset);
-        }
-        long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
-        if (centralDirOffset + centralDirSize != eocdOffset) {
-            throw new SignatureNotFoundException(
-                    "ZIP Central Directory is not immediately followed by End of Central"
-                    + " Directory");
-        }
-        return centralDirOffset;
-    }
-
-    private static final long getChunkCount(long inputSizeBytes) {
-        return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
-    }
-
-    private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
-
-    private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
-    private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
-    private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
-    private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
-    private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
-    private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
-    private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
-
-    private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
-    private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
-
     private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
         switch (sigAlgorithm) {
             case SIGNATURE_RSA_PSS_WITH_SHA256:
@@ -606,519 +400,4 @@
                 return false;
         }
     }
-
-    private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
-        int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
-        int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
-        return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
-    }
-
-    private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
-        switch (digestAlgorithm1) {
-            case CONTENT_DIGEST_CHUNKED_SHA256:
-                switch (digestAlgorithm2) {
-                    case CONTENT_DIGEST_CHUNKED_SHA256:
-                        return 0;
-                    case CONTENT_DIGEST_CHUNKED_SHA512:
-                        return -1;
-                    default:
-                        throw new IllegalArgumentException(
-                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
-                }
-            case CONTENT_DIGEST_CHUNKED_SHA512:
-                switch (digestAlgorithm2) {
-                    case CONTENT_DIGEST_CHUNKED_SHA256:
-                        return 1;
-                    case CONTENT_DIGEST_CHUNKED_SHA512:
-                        return 0;
-                    default:
-                        throw new IllegalArgumentException(
-                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
-                }
-            default:
-                throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
-        }
-    }
-
-    private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
-        switch (sigAlgorithm) {
-            case SIGNATURE_RSA_PSS_WITH_SHA256:
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
-            case SIGNATURE_ECDSA_WITH_SHA256:
-            case SIGNATURE_DSA_WITH_SHA256:
-                return CONTENT_DIGEST_CHUNKED_SHA256;
-            case SIGNATURE_RSA_PSS_WITH_SHA512:
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
-            case SIGNATURE_ECDSA_WITH_SHA512:
-                return CONTENT_DIGEST_CHUNKED_SHA512;
-            default:
-                throw new IllegalArgumentException(
-                        "Unknown signature algorithm: 0x"
-                                + Long.toHexString(sigAlgorithm & 0xffffffff));
-        }
-    }
-
-    private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
-        switch (digestAlgorithm) {
-            case CONTENT_DIGEST_CHUNKED_SHA256:
-                return "SHA-256";
-            case CONTENT_DIGEST_CHUNKED_SHA512:
-                return "SHA-512";
-            default:
-                throw new IllegalArgumentException(
-                        "Unknown content digest algorthm: " + digestAlgorithm);
-        }
-    }
-
-    private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
-        switch (digestAlgorithm) {
-            case CONTENT_DIGEST_CHUNKED_SHA256:
-                return 256 / 8;
-            case CONTENT_DIGEST_CHUNKED_SHA512:
-                return 512 / 8;
-            default:
-                throw new IllegalArgumentException(
-                        "Unknown content digest algorthm: " + digestAlgorithm);
-        }
-    }
-
-    private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
-        switch (sigAlgorithm) {
-            case SIGNATURE_RSA_PSS_WITH_SHA256:
-            case SIGNATURE_RSA_PSS_WITH_SHA512:
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
-                return "RSA";
-            case SIGNATURE_ECDSA_WITH_SHA256:
-            case SIGNATURE_ECDSA_WITH_SHA512:
-                return "EC";
-            case SIGNATURE_DSA_WITH_SHA256:
-                return "DSA";
-            default:
-                throw new IllegalArgumentException(
-                        "Unknown signature algorithm: 0x"
-                                + Long.toHexString(sigAlgorithm & 0xffffffff));
-        }
-    }
-
-    private static Pair<String, ? extends AlgorithmParameterSpec>
-            getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
-        switch (sigAlgorithm) {
-            case SIGNATURE_RSA_PSS_WITH_SHA256:
-                return Pair.create(
-                        "SHA256withRSA/PSS",
-                        new PSSParameterSpec(
-                                "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
-            case SIGNATURE_RSA_PSS_WITH_SHA512:
-                return Pair.create(
-                        "SHA512withRSA/PSS",
-                        new PSSParameterSpec(
-                                "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
-                return Pair.create("SHA256withRSA", null);
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
-                return Pair.create("SHA512withRSA", null);
-            case SIGNATURE_ECDSA_WITH_SHA256:
-                return Pair.create("SHA256withECDSA", null);
-            case SIGNATURE_ECDSA_WITH_SHA512:
-                return Pair.create("SHA512withECDSA", null);
-            case SIGNATURE_DSA_WITH_SHA256:
-                return Pair.create("SHA256withDSA", null);
-            default:
-                throw new IllegalArgumentException(
-                        "Unknown signature algorithm: 0x"
-                                + Long.toHexString(sigAlgorithm & 0xffffffff));
-        }
-    }
-
-    /**
-     * Returns new byte buffer whose content is a shared subsequence of this buffer's content
-     * between the specified start (inclusive) and end (exclusive) positions. As opposed to
-     * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
-     * buffer's byte order.
-     */
-    private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
-        if (start < 0) {
-            throw new IllegalArgumentException("start: " + start);
-        }
-        if (end < start) {
-            throw new IllegalArgumentException("end < start: " + end + " < " + start);
-        }
-        int capacity = source.capacity();
-        if (end > source.capacity()) {
-            throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
-        }
-        int originalLimit = source.limit();
-        int originalPosition = source.position();
-        try {
-            source.position(0);
-            source.limit(end);
-            source.position(start);
-            ByteBuffer result = source.slice();
-            result.order(source.order());
-            return result;
-        } finally {
-            source.position(0);
-            source.limit(originalLimit);
-            source.position(originalPosition);
-        }
-    }
-
-    /**
-     * Relative <em>get</em> method for reading {@code size} number of bytes from the current
-     * position of this buffer.
-     *
-     * <p>This method reads the next {@code size} bytes at this buffer's current position,
-     * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
-     * {@code size}, byte order set to this buffer's byte order; and then increments the position by
-     * {@code size}.
-     */
-    private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
-            throws BufferUnderflowException {
-        if (size < 0) {
-            throw new IllegalArgumentException("size: " + size);
-        }
-        int originalLimit = source.limit();
-        int position = source.position();
-        int limit = position + size;
-        if ((limit < position) || (limit > originalLimit)) {
-            throw new BufferUnderflowException();
-        }
-        source.limit(limit);
-        try {
-            ByteBuffer result = source.slice();
-            result.order(source.order());
-            source.position(limit);
-            return result;
-        } finally {
-            source.limit(originalLimit);
-        }
-    }
-
-    private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
-        if (source.remaining() < 4) {
-            throw new IOException(
-                    "Remaining buffer too short to contain length of length-prefixed field."
-                            + " Remaining: " + source.remaining());
-        }
-        int len = source.getInt();
-        if (len < 0) {
-            throw new IllegalArgumentException("Negative length");
-        } else if (len > source.remaining()) {
-            throw new IOException("Length-prefixed field longer than remaining buffer."
-                    + " Field length: " + len + ", remaining: " + source.remaining());
-        }
-        return getByteBuffer(source, len);
-    }
-
-    private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
-        int len = buf.getInt();
-        if (len < 0) {
-            throw new IOException("Negative length");
-        } else if (len > buf.remaining()) {
-            throw new IOException("Underflow while reading length-prefixed value. Length: " + len
-                    + ", available: " + buf.remaining());
-        }
-        byte[] result = new byte[len];
-        buf.get(result);
-        return result;
-    }
-
-    private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
-        result[offset] = (byte) (value & 0xff);
-        result[offset + 1] = (byte) ((value >>> 8) & 0xff);
-        result[offset + 2] = (byte) ((value >>> 16) & 0xff);
-        result[offset + 3] = (byte) ((value >>> 24) & 0xff);
-    }
-
-    private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
-    private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
-    private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
-
-    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
-
-    private static Pair<ByteBuffer, Long> findApkSigningBlock(
-            RandomAccessFile apk, long centralDirOffset)
-                    throws IOException, SignatureNotFoundException {
-        // FORMAT:
-        // OFFSET       DATA TYPE  DESCRIPTION
-        // * @+0  bytes uint64:    size in bytes (excluding this field)
-        // * @+8  bytes payload
-        // * @-24 bytes uint64:    size in bytes (same as the one above)
-        // * @-16 bytes uint128:   magic
-
-        if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
-            throw new SignatureNotFoundException(
-                    "APK too small for APK Signing Block. ZIP Central Directory offset: "
-                            + centralDirOffset);
-        }
-        // Read the magic and offset in file from the footer section of the block:
-        // * uint64:   size of block
-        // * 16 bytes: magic
-        ByteBuffer footer = ByteBuffer.allocate(24);
-        footer.order(ByteOrder.LITTLE_ENDIAN);
-        apk.seek(centralDirOffset - footer.capacity());
-        apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
-        if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
-                || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
-            throw new SignatureNotFoundException(
-                    "No APK Signing Block before ZIP Central Directory");
-        }
-        // Read and compare size fields
-        long apkSigBlockSizeInFooter = footer.getLong(0);
-        if ((apkSigBlockSizeInFooter < footer.capacity())
-                || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
-            throw new SignatureNotFoundException(
-                    "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
-        }
-        int totalSize = (int) (apkSigBlockSizeInFooter + 8);
-        long apkSigBlockOffset = centralDirOffset - totalSize;
-        if (apkSigBlockOffset < 0) {
-            throw new SignatureNotFoundException(
-                    "APK Signing Block offset out of range: " + apkSigBlockOffset);
-        }
-        ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
-        apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
-        apk.seek(apkSigBlockOffset);
-        apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
-        long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
-        if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
-            throw new SignatureNotFoundException(
-                    "APK Signing Block sizes in header and footer do not match: "
-                            + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
-        }
-        return Pair.create(apkSigBlock, apkSigBlockOffset);
-    }
-
-    private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)
-            throws SignatureNotFoundException {
-        checkByteOrderLittleEndian(apkSigningBlock);
-        // FORMAT:
-        // OFFSET       DATA TYPE  DESCRIPTION
-        // * @+0  bytes uint64:    size in bytes (excluding this field)
-        // * @+8  bytes pairs
-        // * @-24 bytes uint64:    size in bytes (same as the one above)
-        // * @-16 bytes uint128:   magic
-        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
-
-        int entryCount = 0;
-        while (pairs.hasRemaining()) {
-            entryCount++;
-            if (pairs.remaining() < 8) {
-                throw new SignatureNotFoundException(
-                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
-            }
-            long lenLong = pairs.getLong();
-            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
-                throw new SignatureNotFoundException(
-                        "APK Signing Block entry #" + entryCount
-                                + " size out of range: " + lenLong);
-            }
-            int len = (int) lenLong;
-            int nextEntryPos = pairs.position() + len;
-            if (len > pairs.remaining()) {
-                throw new SignatureNotFoundException(
-                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
-                                + ", available: " + pairs.remaining());
-            }
-            int id = pairs.getInt();
-            if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
-                return getByteBuffer(pairs, len - 4);
-            }
-            pairs.position(nextEntryPos);
-        }
-
-        throw new SignatureNotFoundException(
-                "No APK Signature Scheme v2 block in APK Signing Block");
-    }
-
-    private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
-        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
-            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
-        }
-    }
-
-    /**
-     * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded.
-     */
-    private static class MultipleDigestDataDigester implements DataDigester {
-        private final MessageDigest[] mMds;
-
-        MultipleDigestDataDigester(MessageDigest[] mds) {
-            mMds = mds;
-        }
-
-        @Override
-        public void consume(ByteBuffer buffer) {
-            buffer = buffer.slice();
-            for (MessageDigest md : mMds) {
-                buffer.position(0);
-                md.update(buffer);
-            }
-        }
-
-        @Override
-        public void finish() {}
-    }
-
-    /**
-     * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
-     * of letting the underlying implementation have a shot at re-encoding the data.
-     */
-    private static class VerbatimX509Certificate extends WrappedX509Certificate {
-        private byte[] encodedVerbatim;
-
-        public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
-            super(wrapped);
-            this.encodedVerbatim = encodedVerbatim;
-        }
-
-        @Override
-        public byte[] getEncoded() throws CertificateEncodingException {
-            return encodedVerbatim;
-        }
-    }
-
-    private static class WrappedX509Certificate extends X509Certificate {
-        private final X509Certificate wrapped;
-
-        public WrappedX509Certificate(X509Certificate wrapped) {
-            this.wrapped = wrapped;
-        }
-
-        @Override
-        public Set<String> getCriticalExtensionOIDs() {
-            return wrapped.getCriticalExtensionOIDs();
-        }
-
-        @Override
-        public byte[] getExtensionValue(String oid) {
-            return wrapped.getExtensionValue(oid);
-        }
-
-        @Override
-        public Set<String> getNonCriticalExtensionOIDs() {
-            return wrapped.getNonCriticalExtensionOIDs();
-        }
-
-        @Override
-        public boolean hasUnsupportedCriticalExtension() {
-            return wrapped.hasUnsupportedCriticalExtension();
-        }
-
-        @Override
-        public void checkValidity()
-                throws CertificateExpiredException, CertificateNotYetValidException {
-            wrapped.checkValidity();
-        }
-
-        @Override
-        public void checkValidity(Date date)
-                throws CertificateExpiredException, CertificateNotYetValidException {
-            wrapped.checkValidity(date);
-        }
-
-        @Override
-        public int getVersion() {
-            return wrapped.getVersion();
-        }
-
-        @Override
-        public BigInteger getSerialNumber() {
-            return wrapped.getSerialNumber();
-        }
-
-        @Override
-        public Principal getIssuerDN() {
-            return wrapped.getIssuerDN();
-        }
-
-        @Override
-        public Principal getSubjectDN() {
-            return wrapped.getSubjectDN();
-        }
-
-        @Override
-        public Date getNotBefore() {
-            return wrapped.getNotBefore();
-        }
-
-        @Override
-        public Date getNotAfter() {
-            return wrapped.getNotAfter();
-        }
-
-        @Override
-        public byte[] getTBSCertificate() throws CertificateEncodingException {
-            return wrapped.getTBSCertificate();
-        }
-
-        @Override
-        public byte[] getSignature() {
-            return wrapped.getSignature();
-        }
-
-        @Override
-        public String getSigAlgName() {
-            return wrapped.getSigAlgName();
-        }
-
-        @Override
-        public String getSigAlgOID() {
-            return wrapped.getSigAlgOID();
-        }
-
-        @Override
-        public byte[] getSigAlgParams() {
-            return wrapped.getSigAlgParams();
-        }
-
-        @Override
-        public boolean[] getIssuerUniqueID() {
-            return wrapped.getIssuerUniqueID();
-        }
-
-        @Override
-        public boolean[] getSubjectUniqueID() {
-            return wrapped.getSubjectUniqueID();
-        }
-
-        @Override
-        public boolean[] getKeyUsage() {
-            return wrapped.getKeyUsage();
-        }
-
-        @Override
-        public int getBasicConstraints() {
-            return wrapped.getBasicConstraints();
-        }
-
-        @Override
-        public byte[] getEncoded() throws CertificateEncodingException {
-            return wrapped.getEncoded();
-        }
-
-        @Override
-        public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
-                InvalidKeyException, NoSuchProviderException, SignatureException {
-            wrapped.verify(key);
-        }
-
-        @Override
-        public void verify(PublicKey key, String sigProvider)
-                throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
-                NoSuchProviderException, SignatureException {
-            wrapped.verify(key, sigProvider);
-        }
-
-        @Override
-        public String toString() {
-            return wrapped.toString();
-        }
-
-        @Override
-        public PublicKey getPublicKey() {
-            return wrapped.getPublicKey();
-        }
-    }
 }
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
new file mode 100644
index 0000000..e43dee3
--- /dev/null
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
+import android.os.Build;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * APK Signature Scheme v3 verifier.
+ *
+ * @hide for internal use only.
+ */
+public class ApkSignatureSchemeV3Verifier {
+
+    /**
+     * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
+     */
+    public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
+
+    private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
+
+    /**
+     * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
+     *
+     * <p><b>NOTE: This method does not verify the signature.</b>
+     */
+    public static boolean hasSignature(String apkFile) throws IOException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            findSignature(apk);
+            return true;
+        } catch (SignatureNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not
+     * verify.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static VerifiedSigner verify(String apkFile)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        return verify(apkFile, true);
+    }
+
+    /**
+     * Returns the certificates associated with each signer for the given APK without verification.
+     * This method is dangerous and should not be used, unless the caller is absolutely certain the
+     * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v3
+     * Block while gathering signer information.  The APK contents are not verified.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        return verify(apkFile, false);
+    }
+
+    private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            return verify(apk, verifyIntegrity);
+        }
+    }
+
+    /**
+     * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
+     *         verify.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        SignatureInfo signatureInfo = findSignature(apk);
+        return verify(apk.getFD(), signatureInfo, verifyIntegrity);
+    }
+
+    /**
+     * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
+     * additional information relevant for verifying the block against the file.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    private static SignatureInfo findSignature(RandomAccessFile apk)
+            throws IOException, SignatureNotFoundException {
+        return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
+    }
+
+    /**
+     * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
+     * Block.
+     *
+     * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
+     *        against the APK file.
+     */
+    private static VerifiedSigner verify(
+            FileDescriptor apkFileDescriptor,
+            SignatureInfo signatureInfo,
+            boolean doVerifyIntegrity) throws SecurityException {
+        int signerCount = 0;
+        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
+        VerifiedSigner result = null;
+        CertificateFactory certFactory;
+        try {
+            certFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+        }
+        ByteBuffer signers;
+        try {
+            signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
+        } catch (IOException e) {
+            throw new SecurityException("Failed to read list of signers", e);
+        }
+        while (signers.hasRemaining()) {
+            try {
+                ByteBuffer signer = getLengthPrefixedSlice(signers);
+                result = verifySigner(signer, contentDigests, certFactory);
+                signerCount++;
+            } catch (PlatformNotSupportedException e) {
+                // this signer is for a different platform, ignore it.
+                continue;
+            } catch (IOException | BufferUnderflowException | SecurityException e) {
+                throw new SecurityException(
+                        "Failed to parse/verify signer #" + signerCount + " block",
+                        e);
+            }
+        }
+
+        if (signerCount < 1 || result == null) {
+            throw new SecurityException("No signers found");
+        }
+
+        if (signerCount != 1) {
+            throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
+                    + "multiple signers found.");
+        }
+
+        if (contentDigests.isEmpty()) {
+            throw new SecurityException("No content digests found");
+        }
+
+        if (doVerifyIntegrity) {
+            ApkSigningBlockUtils.verifyIntegrity(
+                    contentDigests,
+                    apkFileDescriptor,
+                    signatureInfo.apkSigningBlockOffset,
+                    signatureInfo.centralDirOffset,
+                    signatureInfo.eocdOffset,
+                    signatureInfo.eocd);
+        }
+
+        return result;
+    }
+
+    private static VerifiedSigner verifySigner(
+            ByteBuffer signerBlock,
+            Map<Integer, byte[]> contentDigests,
+            CertificateFactory certFactory)
+            throws SecurityException, IOException, PlatformNotSupportedException {
+        ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
+        int minSdkVersion = signerBlock.getInt();
+        int maxSdkVersion = signerBlock.getInt();
+
+        if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
+            // this signature isn't meant to be used with this platform, skip it.
+            throw new PlatformNotSupportedException(
+                    "Signer not supported by this platform "
+                    + "version. This platform: " + Build.VERSION.SDK_INT
+                    + ", signer minSdkVersion: " + minSdkVersion
+                    + ", maxSdkVersion: " + maxSdkVersion);
+        }
+
+        ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
+        byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
+
+        int signatureCount = 0;
+        int bestSigAlgorithm = -1;
+        byte[] bestSigAlgorithmSignatureBytes = null;
+        List<Integer> signaturesSigAlgorithms = new ArrayList<>();
+        while (signatures.hasRemaining()) {
+            signatureCount++;
+            try {
+                ByteBuffer signature = getLengthPrefixedSlice(signatures);
+                if (signature.remaining() < 8) {
+                    throw new SecurityException("Signature record too short");
+                }
+                int sigAlgorithm = signature.getInt();
+                signaturesSigAlgorithms.add(sigAlgorithm);
+                if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
+                    continue;
+                }
+                if ((bestSigAlgorithm == -1)
+                        || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
+                    bestSigAlgorithm = sigAlgorithm;
+                    bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new SecurityException(
+                        "Failed to parse signature record #" + signatureCount,
+                        e);
+            }
+        }
+        if (bestSigAlgorithm == -1) {
+            if (signatureCount == 0) {
+                throw new SecurityException("No signatures found");
+            } else {
+                throw new SecurityException("No supported signatures found");
+            }
+        }
+
+        String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
+        Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
+                getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
+        String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
+        AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
+        boolean sigVerified;
+        try {
+            PublicKey publicKey =
+                    KeyFactory.getInstance(keyAlgorithm)
+                            .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+            Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+            sig.initVerify(publicKey);
+            if (jcaSignatureAlgorithmParams != null) {
+                sig.setParameter(jcaSignatureAlgorithmParams);
+            }
+            sig.update(signedData);
+            sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
+                | InvalidAlgorithmParameterException | SignatureException e) {
+            throw new SecurityException(
+                    "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
+        }
+        if (!sigVerified) {
+            throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
+        }
+
+        // Signature over signedData has verified.
+
+        byte[] contentDigest = null;
+        signedData.clear();
+        ByteBuffer digests = getLengthPrefixedSlice(signedData);
+        List<Integer> digestsSigAlgorithms = new ArrayList<>();
+        int digestCount = 0;
+        while (digests.hasRemaining()) {
+            digestCount++;
+            try {
+                ByteBuffer digest = getLengthPrefixedSlice(digests);
+                if (digest.remaining() < 8) {
+                    throw new IOException("Record too short");
+                }
+                int sigAlgorithm = digest.getInt();
+                digestsSigAlgorithms.add(sigAlgorithm);
+                if (sigAlgorithm == bestSigAlgorithm) {
+                    contentDigest = readLengthPrefixedByteArray(digest);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new IOException("Failed to parse digest record #" + digestCount, e);
+            }
+        }
+
+        if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
+            throw new SecurityException(
+                    "Signature algorithms don't match between digests and signatures records");
+        }
+        int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
+        byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
+        if ((previousSignerDigest != null)
+                && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
+            throw new SecurityException(
+                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+                    + " contents digest does not match the digest specified by a preceding signer");
+        }
+
+        ByteBuffer certificates = getLengthPrefixedSlice(signedData);
+        List<X509Certificate> certs = new ArrayList<>();
+        int certificateCount = 0;
+        while (certificates.hasRemaining()) {
+            certificateCount++;
+            byte[] encodedCert = readLengthPrefixedByteArray(certificates);
+            X509Certificate certificate;
+            try {
+                certificate = (X509Certificate)
+                        certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+            } catch (CertificateException e) {
+                throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
+            }
+            certificate = new VerbatimX509Certificate(
+                    certificate, encodedCert);
+            certs.add(certificate);
+        }
+
+        if (certs.isEmpty()) {
+            throw new SecurityException("No certificates listed");
+        }
+        X509Certificate mainCertificate = certs.get(0);
+        byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+        if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
+            throw new SecurityException(
+                    "Public key mismatch between certificate and signature record");
+        }
+
+        int signedMinSDK = signedData.getInt();
+        if (signedMinSDK != minSdkVersion) {
+            throw new SecurityException(
+                    "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
+        }
+
+        int signedMaxSDK = signedData.getInt();
+        if (signedMaxSDK != maxSdkVersion) {
+            throw new SecurityException(
+                    "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
+        }
+
+        ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
+        return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
+    }
+
+    private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
+
+    private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
+            List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
+        X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
+        VerifiedProofOfRotation por = null;
+
+        while (attrs.hasRemaining()) {
+            ByteBuffer attr = getLengthPrefixedSlice(attrs);
+            if (attr.remaining() < 4) {
+                throw new IOException("Remaining buffer too short to contain additional attribute "
+                        + "ID. Remaining: " + attr.remaining());
+            }
+            int id = attr.getInt();
+            switch(id) {
+                case PROOF_OF_ROTATION_ATTR_ID:
+                    if (por != null) {
+                        throw new SecurityException("Encountered multiple Proof-of-rotation records"
+                                + " when verifying APK Signature Scheme v3 signature");
+                    }
+                    por = verifyProofOfRotationStruct(attr, certFactory);
+                    // make sure that the last certificate in the Proof-of-rotation record matches
+                    // the one used to sign this APK.
+                    try {
+                        if (por.certs.size() > 0
+                                && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
+                                        certChain[0].getEncoded())) {
+                            throw new SecurityException("Terminal certificate in Proof-of-rotation"
+                                    + " record does not match APK signing certificate");
+                        }
+                    } catch (CertificateEncodingException e) {
+                        throw new SecurityException("Failed to encode certificate when comparing"
+                                + " Proof-of-rotation record and signing certificate", e);
+                    }
+
+                    break;
+                default:
+                    // not the droid we're looking for, move along, move along.
+                    break;
+            }
+        }
+        return new VerifiedSigner(certChain, por);
+    }
+
+    private static VerifiedProofOfRotation verifyProofOfRotationStruct(
+            ByteBuffer porBuf,
+            CertificateFactory certFactory)
+            throws SecurityException, IOException {
+        int levelCount = 0;
+        int lastSigAlgorithm = -1;
+        X509Certificate lastCert = null;
+        List<X509Certificate> certs = new ArrayList<>();
+        List<Integer> flagsList = new ArrayList<>();
+
+        // Proof-of-rotation struct:
+        // is basically a singly linked list of nodes, called levels here, each of which have the
+        // following structure:
+        // * length-prefix for the entire level
+        //     - length-prefixed signed data (if previous level exists)
+        //         * length-prefixed X509 Certificate
+        //         * uint32 signature algorithm ID describing how this signed data was signed
+        //     - uint32 flags describing how to treat the cert contained in this level
+        //     - uint32 signature algorithm ID to use to verify the signature of the next level. The
+        //         algorithm here must match the one in the signed data section of the next level.
+        //     - length-prefixed signature over the signed data in this level.  The signature here
+        //         is verified using the certificate from the previous level.
+        // The linking is provided by the certificate of each level signing the one of the next.
+        while (porBuf.hasRemaining()) {
+            levelCount++;
+            try {
+                ByteBuffer level = getLengthPrefixedSlice(porBuf);
+                ByteBuffer signedData = getLengthPrefixedSlice(level);
+                int flags = level.getInt();
+                int sigAlgorithm = level.getInt();
+                byte[] signature = readLengthPrefixedByteArray(level);
+
+                if (lastCert != null) {
+                    // Use previous level cert to verify current level
+                    Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
+                            getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
+                    PublicKey publicKey = lastCert.getPublicKey();
+                    Signature sig = Signature.getInstance(sigAlgParams.first);
+                    sig.initVerify(publicKey);
+                    if (sigAlgParams.second != null) {
+                        sig.setParameter(sigAlgParams.second);
+                    }
+                    sig.update(signedData);
+                    if (!sig.verify(signature)) {
+                        throw new SecurityException("Unable to verify signature of certificate #"
+                                + levelCount + " using " + sigAlgParams.first + " when verifying"
+                                + " Proof-of-rotation record");
+                    }
+                }
+
+                byte[] encodedCert = readLengthPrefixedByteArray(signedData);
+                int signedSigAlgorithm = signedData.getInt();
+                if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
+                    throw new SecurityException("Signing algorithm ID mismatch for certificate #"
+                            + levelCount + " when verifying Proof-of-rotation record");
+                }
+                lastCert = (X509Certificate)
+                        certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+                lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
+
+                lastSigAlgorithm = sigAlgorithm;
+                certs.add(lastCert);
+                flagsList.add(flags);
+            } catch (IOException | BufferUnderflowException e) {
+                throw new IOException("Failed to parse Proof-of-rotation record", e);
+            } catch (NoSuchAlgorithmException | InvalidKeyException
+                    | InvalidAlgorithmParameterException | SignatureException e) {
+                throw new SecurityException(
+                        "Failed to verify signature over signed data for certificate #"
+                                + levelCount + " when verifying Proof-of-rotation record", e);
+            } catch (CertificateException e) {
+                throw new SecurityException("Failed to decode certificate #" + levelCount
+                        + " when verifying Proof-of-rotation record", e);
+            }
+        }
+        return new VerifiedProofOfRotation(certs, flagsList);
+    }
+
+    private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+            case SIGNATURE_DSA_WITH_SHA256:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Verified processed proof of rotation.
+     *
+     * @hide for internal use only.
+     */
+    public static class VerifiedProofOfRotation {
+        public final List<X509Certificate> certs;
+        public final List<Integer> flagsList;
+
+        public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
+            this.certs = certs;
+            this.flagsList = flagsList;
+        }
+    }
+
+    /**
+     * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
+     *
+     * @hide for internal use only.
+     */
+    public static class VerifiedSigner {
+        public final X509Certificate[] certs;
+        public final VerifiedProofOfRotation por;
+
+        public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
+            this.certs = certs;
+            this.por = por;
+        }
+
+    }
+
+    private static class PlatformNotSupportedException extends Exception {
+
+        PlatformNotSupportedException(String s) {
+            super(s);
+        }
+    }
+}
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 17b11a9..8146729 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -54,6 +54,7 @@
 
     public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
     public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
+    public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
 
     private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
 
@@ -65,7 +66,45 @@
     public static Result verify(String apkPath, int minSignatureSchemeVersion)
             throws PackageParserException {
 
-        // first try v2
+        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+            // V3 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+            + " or newer for package " + apkPath);
+        }
+
+        // first try v3
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
+        try {
+            ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
+                    ApkSignatureSchemeV3Verifier.verify(apkPath);
+            Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+            return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+        } catch (SignatureNotFoundException e) {
+            // not signed with v2, try older if allowed
+            if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No APK Signature Scheme v3 signature in package " + apkPath, e);
+            }
+        } catch (Exception e) {
+            // APK Signature Scheme v2 signature found but did not verify
+            throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath
+                            + " using APK Signature Scheme v2", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+
+        // redundant, protective version check
+        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+            // V2 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
+        // try v2
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
         try {
             Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
@@ -87,6 +126,14 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
+        // redundant, protective version check
+        if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+            // V1 and is older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
         // v2 didn't work, try jarsigner
         return verifyV1Signature(apkPath, true);
     }
@@ -245,6 +292,44 @@
     public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion)
             throws PackageParserException {
 
+        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+            // V3 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
+        // first try v3
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
+        try {
+            ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
+                    ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
+            Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+            return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+        } catch (SignatureNotFoundException e) {
+            // not signed with v2, try older if allowed
+            if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No APK Signature Scheme v3 signature in package " + apkPath, e);
+            }
+        } catch (Exception e) {
+            // APK Signature Scheme v2 signature found but did not verify
+            throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath
+                            + " using APK Signature Scheme v2", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+
+        // redundant, protective version check
+        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+            // V2 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
         // first try v2
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2");
         try {
@@ -267,6 +352,14 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
+        // redundant, protective version check
+        if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+            // V1 and is older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
         // v2 didn't work, try jarsigner
         return verifyV1Signature(apkPath, false);
     }
diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java
new file mode 100644
index 0000000..9279510
--- /dev/null
+++ b/core/java/android/util/apk/ApkSigningBlockUtils.java
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import android.util.Pair;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+import java.util.Map;
+
+/**
+ * Utility class for an APK Signature Scheme using the APK Signing Block.
+ *
+ * @hide for internal use only.
+ */
+final class ApkSigningBlockUtils {
+
+    private ApkSigningBlockUtils() {
+    }
+
+    /**
+     * Returns the APK Signature Scheme block contained in the provided APK file and the
+     * additional information relevant for verifying the block against the file.
+     *
+     * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs
+     *                identifying the appropriate block to find, e.g. the APK Signature Scheme v2
+     *                block ID.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using this scheme.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
+            throws IOException, SignatureNotFoundException {
+        // Find the ZIP End of Central Directory (EoCD) record.
+        Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
+        ByteBuffer eocd = eocdAndOffsetInFile.first;
+        long eocdOffset = eocdAndOffsetInFile.second;
+        if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
+            throw new SignatureNotFoundException("ZIP64 APK not supported");
+        }
+
+        // Find the APK Signing Block. The block immediately precedes the Central Directory.
+        long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
+        Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
+                findApkSigningBlock(apk, centralDirOffset);
+        ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
+        long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
+
+        // Find the APK Signature Scheme Block inside the APK Signing Block.
+        ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock,
+                blockId);
+
+        return new SignatureInfo(
+                apkSignatureSchemeBlock,
+                apkSigningBlockOffset,
+                centralDirOffset,
+                eocdOffset,
+                eocd);
+    }
+
+    static void verifyIntegrity(
+            Map<Integer, byte[]> expectedDigests,
+            FileDescriptor apkFileDescriptor,
+            long apkSigningBlockOffset,
+            long centralDirOffset,
+            long eocdOffset,
+            ByteBuffer eocdBuf) throws SecurityException {
+
+        if (expectedDigests.isEmpty()) {
+            throw new SecurityException("No digests provided");
+        }
+
+        // We need to verify the integrity of the following three sections of the file:
+        // 1. Everything up to the start of the APK Signing Block.
+        // 2. ZIP Central Directory.
+        // 3. ZIP End of Central Directory (EoCD).
+        // Each of these sections is represented as a separate DataSource instance below.
+
+        // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
+        // avoid wasting physical memory. In most APK verification scenarios, the contents of the
+        // APK are already there in the OS's page cache and thus mmap does not use additional
+        // physical memory.
+        DataSource beforeApkSigningBlock =
+                new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
+        DataSource centralDir =
+                new MemoryMappedFileDataSource(
+                        apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
+
+        // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
+        // Central Directory must be considered to point to the offset of the APK Signing Block.
+        eocdBuf = eocdBuf.duplicate();
+        eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
+        ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
+        DataSource eocd = new ByteBufferDataSource(eocdBuf);
+
+        int[] digestAlgorithms = new int[expectedDigests.size()];
+        int digestAlgorithmCount = 0;
+        for (int digestAlgorithm : expectedDigests.keySet()) {
+            digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
+            digestAlgorithmCount++;
+        }
+        byte[][] actualDigests;
+        try {
+            actualDigests =
+                    computeContentDigests(
+                            digestAlgorithms,
+                            new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
+        } catch (DigestException e) {
+            throw new SecurityException("Failed to compute digest(s) of contents", e);
+        }
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            int digestAlgorithm = digestAlgorithms[i];
+            byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
+            byte[] actualDigest = actualDigests[i];
+            if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
+                throw new SecurityException(
+                        getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+                                + " digest of contents did not verify");
+            }
+        }
+    }
+
+    private static byte[][] computeContentDigests(
+            int[] digestAlgorithms,
+            DataSource[] contents) throws DigestException {
+        // For each digest algorithm the result is computed as follows:
+        // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
+        //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
+        //    No chunks are produced for empty (zero length) segments.
+        // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
+        //    length in bytes (uint32 little-endian) and the chunk's contents.
+        // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
+        //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
+        //    segments in-order.
+
+        long totalChunkCountLong = 0;
+        for (DataSource input : contents) {
+            totalChunkCountLong += getChunkCount(input.size());
+        }
+        if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
+            throw new DigestException("Too many chunks: " + totalChunkCountLong);
+        }
+        int totalChunkCount = (int) totalChunkCountLong;
+
+        byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            int digestAlgorithm = digestAlgorithms[i];
+            int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+            byte[] concatenationOfChunkCountAndChunkDigests =
+                    new byte[5 + totalChunkCount * digestOutputSizeBytes];
+            concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
+            setUnsignedInt32LittleEndian(
+                    totalChunkCount,
+                    concatenationOfChunkCountAndChunkDigests,
+                    1);
+            digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
+        }
+
+        byte[] chunkContentPrefix = new byte[5];
+        chunkContentPrefix[0] = (byte) 0xa5;
+        int chunkIndex = 0;
+        MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            String jcaAlgorithmName =
+                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
+            try {
+                mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+            }
+        }
+        // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
+        // into how to parallelize (if at all) based on the capabilities of the hardware on which
+        // this code is running and based on the size of input.
+        DataDigester digester = new MultipleDigestDataDigester(mds);
+        int dataSourceIndex = 0;
+        for (DataSource input : contents) {
+            long inputOffset = 0;
+            long inputRemaining = input.size();
+            while (inputRemaining > 0) {
+                int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
+                setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
+                for (int i = 0; i < mds.length; i++) {
+                    mds[i].update(chunkContentPrefix);
+                }
+                try {
+                    input.feedIntoDataDigester(digester, inputOffset, chunkSize);
+                } catch (IOException e) {
+                    throw new DigestException(
+                            "Failed to digest chunk #" + chunkIndex + " of section #"
+                                    + dataSourceIndex,
+                            e);
+                }
+                for (int i = 0; i < digestAlgorithms.length; i++) {
+                    int digestAlgorithm = digestAlgorithms[i];
+                    byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
+                    int expectedDigestSizeBytes =
+                            getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+                    MessageDigest md = mds[i];
+                    int actualDigestSizeBytes =
+                            md.digest(
+                                    concatenationOfChunkCountAndChunkDigests,
+                                    5 + chunkIndex * expectedDigestSizeBytes,
+                                    expectedDigestSizeBytes);
+                    if (actualDigestSizeBytes != expectedDigestSizeBytes) {
+                        throw new RuntimeException(
+                                "Unexpected output size of " + md.getAlgorithm() + " digest: "
+                                        + actualDigestSizeBytes);
+                    }
+                }
+                inputOffset += chunkSize;
+                inputRemaining -= chunkSize;
+                chunkIndex++;
+            }
+            dataSourceIndex++;
+        }
+
+        byte[][] result = new byte[digestAlgorithms.length][];
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            int digestAlgorithm = digestAlgorithms[i];
+            byte[] input = digestsOfChunks[i];
+            String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
+            MessageDigest md;
+            try {
+                md = MessageDigest.getInstance(jcaAlgorithmName);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+            }
+            byte[] output = md.digest(input);
+            result[i] = output;
+        }
+        return result;
+    }
+
+    /**
+     * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
+     *
+     * @throws IOException if an I/O error occurs while reading the file.
+     * @throws SignatureNotFoundException if the EoCD could not be found.
+     */
+    static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
+            throws IOException, SignatureNotFoundException {
+        Pair<ByteBuffer, Long> eocdAndOffsetInFile =
+                ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
+        if (eocdAndOffsetInFile == null) {
+            throw new SignatureNotFoundException(
+                    "Not an APK file: ZIP End of Central Directory record not found");
+        }
+        return eocdAndOffsetInFile;
+    }
+
+    static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
+            throws SignatureNotFoundException {
+        // Look up the offset of ZIP Central Directory.
+        long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
+        if (centralDirOffset > eocdOffset) {
+            throw new SignatureNotFoundException(
+                    "ZIP Central Directory offset out of range: " + centralDirOffset
+                    + ". ZIP End of Central Directory offset: " + eocdOffset);
+        }
+        long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
+        if (centralDirOffset + centralDirSize != eocdOffset) {
+            throw new SignatureNotFoundException(
+                    "ZIP Central Directory is not immediately followed by End of Central"
+                    + " Directory");
+        }
+        return centralDirOffset;
+    }
+
+    private static long getChunkCount(long inputSizeBytes) {
+        return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
+    }
+
+    private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
+
+    static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
+    static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
+    static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
+    static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
+    static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
+    static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
+    static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
+
+    static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
+    static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
+
+    static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
+        int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
+        int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
+        return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
+    }
+
+    private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
+        switch (digestAlgorithm1) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                switch (digestAlgorithm2) {
+                    case CONTENT_DIGEST_CHUNKED_SHA256:
+                        return 0;
+                    case CONTENT_DIGEST_CHUNKED_SHA512:
+                        return -1;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
+                }
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                switch (digestAlgorithm2) {
+                    case CONTENT_DIGEST_CHUNKED_SHA256:
+                        return 1;
+                    case CONTENT_DIGEST_CHUNKED_SHA512:
+                        return 0;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
+                }
+            default:
+                throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
+        }
+    }
+
+    static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_DSA_WITH_SHA256:
+                return CONTENT_DIGEST_CHUNKED_SHA256;
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+                return CONTENT_DIGEST_CHUNKED_SHA512;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
+        switch (digestAlgorithm) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                return "SHA-256";
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                return "SHA-512";
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown content digest algorthm: " + digestAlgorithm);
+        }
+    }
+
+    private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
+        switch (digestAlgorithm) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                return 256 / 8;
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                return 512 / 8;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown content digest algorthm: " + digestAlgorithm);
+        }
+    }
+
+    static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+                return "RSA";
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+                return "EC";
+            case SIGNATURE_DSA_WITH_SHA256:
+                return "DSA";
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    static Pair<String, ? extends AlgorithmParameterSpec>
+            getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+                return Pair.create(
+                        "SHA256withRSA/PSS",
+                        new PSSParameterSpec(
+                                "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+                return Pair.create(
+                        "SHA512withRSA/PSS",
+                        new PSSParameterSpec(
+                                "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+                return Pair.create("SHA256withRSA", null);
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+                return Pair.create("SHA512withRSA", null);
+            case SIGNATURE_ECDSA_WITH_SHA256:
+                return Pair.create("SHA256withECDSA", null);
+            case SIGNATURE_ECDSA_WITH_SHA512:
+                return Pair.create("SHA512withECDSA", null);
+            case SIGNATURE_DSA_WITH_SHA256:
+                return Pair.create("SHA256withDSA", null);
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    /**
+     * Returns new byte buffer whose content is a shared subsequence of this buffer's content
+     * between the specified start (inclusive) and end (exclusive) positions. As opposed to
+     * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
+     * buffer's byte order.
+     */
+    static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
+        if (start < 0) {
+            throw new IllegalArgumentException("start: " + start);
+        }
+        if (end < start) {
+            throw new IllegalArgumentException("end < start: " + end + " < " + start);
+        }
+        int capacity = source.capacity();
+        if (end > source.capacity()) {
+            throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
+        }
+        int originalLimit = source.limit();
+        int originalPosition = source.position();
+        try {
+            source.position(0);
+            source.limit(end);
+            source.position(start);
+            ByteBuffer result = source.slice();
+            result.order(source.order());
+            return result;
+        } finally {
+            source.position(0);
+            source.limit(originalLimit);
+            source.position(originalPosition);
+        }
+    }
+
+    /**
+     * Relative <em>get</em> method for reading {@code size} number of bytes from the current
+     * position of this buffer.
+     *
+     * <p>This method reads the next {@code size} bytes at this buffer's current position,
+     * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
+     * {@code size}, byte order set to this buffer's byte order; and then increments the position by
+     * {@code size}.
+     */
+    static ByteBuffer getByteBuffer(ByteBuffer source, int size)
+            throws BufferUnderflowException {
+        if (size < 0) {
+            throw new IllegalArgumentException("size: " + size);
+        }
+        int originalLimit = source.limit();
+        int position = source.position();
+        int limit = position + size;
+        if ((limit < position) || (limit > originalLimit)) {
+            throw new BufferUnderflowException();
+        }
+        source.limit(limit);
+        try {
+            ByteBuffer result = source.slice();
+            result.order(source.order());
+            source.position(limit);
+            return result;
+        } finally {
+            source.limit(originalLimit);
+        }
+    }
+
+    static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
+        if (source.remaining() < 4) {
+            throw new IOException(
+                    "Remaining buffer too short to contain length of length-prefixed field."
+                            + " Remaining: " + source.remaining());
+        }
+        int len = source.getInt();
+        if (len < 0) {
+            throw new IllegalArgumentException("Negative length");
+        } else if (len > source.remaining()) {
+            throw new IOException("Length-prefixed field longer than remaining buffer."
+                    + " Field length: " + len + ", remaining: " + source.remaining());
+        }
+        return getByteBuffer(source, len);
+    }
+
+    static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
+        int len = buf.getInt();
+        if (len < 0) {
+            throw new IOException("Negative length");
+        } else if (len > buf.remaining()) {
+            throw new IOException("Underflow while reading length-prefixed value. Length: " + len
+                    + ", available: " + buf.remaining());
+        }
+        byte[] result = new byte[len];
+        buf.get(result);
+        return result;
+    }
+
+    static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
+        result[offset] = (byte) (value & 0xff);
+        result[offset + 1] = (byte) ((value >>> 8) & 0xff);
+        result[offset + 2] = (byte) ((value >>> 16) & 0xff);
+        result[offset + 3] = (byte) ((value >>> 24) & 0xff);
+    }
+
+    private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
+    private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
+    private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
+
+    static Pair<ByteBuffer, Long> findApkSigningBlock(
+            RandomAccessFile apk, long centralDirOffset)
+                    throws IOException, SignatureNotFoundException {
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint64:    size in bytes (excluding this field)
+        // * @+8  bytes payload
+        // * @-24 bytes uint64:    size in bytes (same as the one above)
+        // * @-16 bytes uint128:   magic
+
+        if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
+            throw new SignatureNotFoundException(
+                    "APK too small for APK Signing Block. ZIP Central Directory offset: "
+                            + centralDirOffset);
+        }
+        // Read the magic and offset in file from the footer section of the block:
+        // * uint64:   size of block
+        // * 16 bytes: magic
+        ByteBuffer footer = ByteBuffer.allocate(24);
+        footer.order(ByteOrder.LITTLE_ENDIAN);
+        apk.seek(centralDirOffset - footer.capacity());
+        apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
+        if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
+                || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
+            throw new SignatureNotFoundException(
+                    "No APK Signing Block before ZIP Central Directory");
+        }
+        // Read and compare size fields
+        long apkSigBlockSizeInFooter = footer.getLong(0);
+        if ((apkSigBlockSizeInFooter < footer.capacity())
+                || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
+        }
+        int totalSize = (int) (apkSigBlockSizeInFooter + 8);
+        long apkSigBlockOffset = centralDirOffset - totalSize;
+        if (apkSigBlockOffset < 0) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block offset out of range: " + apkSigBlockOffset);
+        }
+        ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
+        apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
+        apk.seek(apkSigBlockOffset);
+        apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
+        long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
+        if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block sizes in header and footer do not match: "
+                            + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
+        }
+        return Pair.create(apkSigBlock, apkSigBlockOffset);
+    }
+
+    static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)
+            throws SignatureNotFoundException {
+        checkByteOrderLittleEndian(apkSigningBlock);
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint64:    size in bytes (excluding this field)
+        // * @+8  bytes pairs
+        // * @-24 bytes uint64:    size in bytes (same as the one above)
+        // * @-16 bytes uint128:   magic
+        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
+
+        int entryCount = 0;
+        while (pairs.hasRemaining()) {
+            entryCount++;
+            if (pairs.remaining() < 8) {
+                throw new SignatureNotFoundException(
+                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
+            }
+            long lenLong = pairs.getLong();
+            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
+                throw new SignatureNotFoundException(
+                        "APK Signing Block entry #" + entryCount
+                                + " size out of range: " + lenLong);
+            }
+            int len = (int) lenLong;
+            int nextEntryPos = pairs.position() + len;
+            if (len > pairs.remaining()) {
+                throw new SignatureNotFoundException(
+                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
+                                + ", available: " + pairs.remaining());
+            }
+            int id = pairs.getInt();
+            if (id == blockId) {
+                return getByteBuffer(pairs, len - 4);
+            }
+            pairs.position(nextEntryPos);
+        }
+
+        throw new SignatureNotFoundException(
+                "No block with ID " + blockId + " in APK Signing Block.");
+    }
+
+    private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
+        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+        }
+    }
+
+    /**
+     * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed.
+     */
+    private static class MultipleDigestDataDigester implements DataDigester {
+        private final MessageDigest[] mMds;
+
+        MultipleDigestDataDigester(MessageDigest[] mds) {
+            mMds = mds;
+        }
+
+        @Override
+        public void consume(ByteBuffer buffer) {
+            buffer = buffer.slice();
+            for (MessageDigest md : mMds) {
+                buffer.position(0);
+                md.update(buffer);
+            }
+        }
+
+        @Override
+        public void finish() {}
+    }
+
+}
diff --git a/core/java/android/util/apk/VerbatimX509Certificate.java b/core/java/android/util/apk/VerbatimX509Certificate.java
new file mode 100644
index 0000000..9984c6d
--- /dev/null
+++ b/core/java/android/util/apk/VerbatimX509Certificate.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+/**
+ * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
+ * of letting the underlying implementation have a shot at re-encoding the data.
+ */
+class VerbatimX509Certificate extends WrappedX509Certificate {
+    private final byte[] mEncodedVerbatim;
+
+    VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
+        super(wrapped);
+        this.mEncodedVerbatim = encodedVerbatim;
+    }
+
+    @Override
+    public byte[] getEncoded() throws CertificateEncodingException {
+        return mEncodedVerbatim;
+    }
+}
diff --git a/core/java/android/util/apk/WrappedX509Certificate.java b/core/java/android/util/apk/WrappedX509Certificate.java
new file mode 100644
index 0000000..fdaa420
--- /dev/null
+++ b/core/java/android/util/apk/WrappedX509Certificate.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+class WrappedX509Certificate extends X509Certificate {
+    private final X509Certificate mWrapped;
+
+    WrappedX509Certificate(X509Certificate wrapped) {
+        this.mWrapped = wrapped;
+    }
+
+    @Override
+    public Set<String> getCriticalExtensionOIDs() {
+        return mWrapped.getCriticalExtensionOIDs();
+    }
+
+    @Override
+    public byte[] getExtensionValue(String oid) {
+        return mWrapped.getExtensionValue(oid);
+    }
+
+    @Override
+    public Set<String> getNonCriticalExtensionOIDs() {
+        return mWrapped.getNonCriticalExtensionOIDs();
+    }
+
+    @Override
+    public boolean hasUnsupportedCriticalExtension() {
+        return mWrapped.hasUnsupportedCriticalExtension();
+    }
+
+    @Override
+    public void checkValidity()
+            throws CertificateExpiredException, CertificateNotYetValidException {
+        mWrapped.checkValidity();
+    }
+
+    @Override
+    public void checkValidity(Date date)
+            throws CertificateExpiredException, CertificateNotYetValidException {
+        mWrapped.checkValidity(date);
+    }
+
+    @Override
+    public int getVersion() {
+        return mWrapped.getVersion();
+    }
+
+    @Override
+    public BigInteger getSerialNumber() {
+        return mWrapped.getSerialNumber();
+    }
+
+    @Override
+    public Principal getIssuerDN() {
+        return mWrapped.getIssuerDN();
+    }
+
+    @Override
+    public Principal getSubjectDN() {
+        return mWrapped.getSubjectDN();
+    }
+
+    @Override
+    public Date getNotBefore() {
+        return mWrapped.getNotBefore();
+    }
+
+    @Override
+    public Date getNotAfter() {
+        return mWrapped.getNotAfter();
+    }
+
+    @Override
+    public byte[] getTBSCertificate() throws CertificateEncodingException {
+        return mWrapped.getTBSCertificate();
+    }
+
+    @Override
+    public byte[] getSignature() {
+        return mWrapped.getSignature();
+    }
+
+    @Override
+    public String getSigAlgName() {
+        return mWrapped.getSigAlgName();
+    }
+
+    @Override
+    public String getSigAlgOID() {
+        return mWrapped.getSigAlgOID();
+    }
+
+    @Override
+    public byte[] getSigAlgParams() {
+        return mWrapped.getSigAlgParams();
+    }
+
+    @Override
+    public boolean[] getIssuerUniqueID() {
+        return mWrapped.getIssuerUniqueID();
+    }
+
+    @Override
+    public boolean[] getSubjectUniqueID() {
+        return mWrapped.getSubjectUniqueID();
+    }
+
+    @Override
+    public boolean[] getKeyUsage() {
+        return mWrapped.getKeyUsage();
+    }
+
+    @Override
+    public int getBasicConstraints() {
+        return mWrapped.getBasicConstraints();
+    }
+
+    @Override
+    public byte[] getEncoded() throws CertificateEncodingException {
+        return mWrapped.getEncoded();
+    }
+
+    @Override
+    public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException {
+        mWrapped.verify(key);
+    }
+
+    @Override
+    public void verify(PublicKey key, String sigProvider)
+            throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+            NoSuchProviderException, SignatureException {
+        mWrapped.verify(key, sigProvider);
+    }
+
+    @Override
+    public String toString() {
+        return mWrapped.toString();
+    }
+
+    @Override
+    public PublicKey getPublicKey() {
+        return mWrapped.getPublicKey();
+    }
+}
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
index debc170..4525490 100644
--- a/core/java/android/util/jar/StrictJarVerifier.java
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -18,6 +18,8 @@
 package android.util.jar;
 
 import android.util.apk.ApkSignatureSchemeV2Verifier;
+import android.util.apk.ApkSignatureSchemeV3Verifier;
+
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
@@ -36,6 +38,7 @@
 import java.util.StringTokenizer;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
+
 import sun.security.jca.Providers;
 import sun.security.pkcs.PKCS7;
 import sun.security.pkcs.SignerInfo;
@@ -56,6 +59,15 @@
  */
 class StrictJarVerifier {
     /**
+     * {@code .SF} file header section attribute indicating that the APK is signed not just with
+     * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
+     * facilitates v2 signature stripping detection.
+     *
+     * <p>The attribute contains a comma-separated set of signature scheme IDs.
+     */
+    private static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
+
+    /**
      * List of accepted digest algorithms. This list is in order from most
      * preferred to least preferred.
      */
@@ -373,17 +385,17 @@
             return;
         }
 
-        // If requested, check whether APK Signature Scheme v2 signature was stripped.
+        // If requested, check whether a newer APK Signature Scheme signature was stripped.
         if (signatureSchemeRollbackProtectionsEnforced) {
             String apkSignatureSchemeIdList =
-                    attributes.getValue(
-                            ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
+                    attributes.getValue(SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
             if (apkSignatureSchemeIdList != null) {
                 // This field contains a comma-separated list of APK signature scheme IDs which
                 // were used to sign this APK. If an ID is known to us, it means signatures of that
                 // scheme were stripped from the APK because otherwise we wouldn't have fallen back
                 // to verifying the APK using the JAR signature scheme.
                 boolean v2SignatureGenerated = false;
+                boolean v3SignatureGenerated = false;
                 StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ",");
                 while (tokenizer.hasMoreTokens()) {
                     String idText = tokenizer.nextToken().trim();
@@ -402,6 +414,12 @@
                         v2SignatureGenerated = true;
                         break;
                     }
+                    if (id == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+                        // This APK was supposed to be signed with APK Signature Scheme v3 but no
+                        // such signature was found.
+                        v3SignatureGenerated = true;
+                        break;
+                    }
                 }
 
                 if (v2SignatureGenerated) {
@@ -409,6 +427,11 @@
                             + " is signed using APK Signature Scheme v2, but no such signature was"
                             + " found. Signature stripped?");
                 }
+                if (v3SignatureGenerated) {
+                    throw new SecurityException(signatureFile + " indicates " + jarName
+                            + " is signed using APK Signature Scheme v3, but no such signature was"
+                            + " found. Signature stripped?");
+                }
             }
         }
 
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index ba6b6cf..33fbf73 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -235,6 +235,8 @@
         for (int i = 0; i <= CALLBACK_LAST; i++) {
             mCallbackQueues[i] = new CallbackQueue();
         }
+        // b/68769804: For low FPS experiments.
+        setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
     }
 
     private static float getRefreshRate() {
@@ -605,6 +607,7 @@
     void setFPSDivisor(int divisor) {
         if (divisor <= 0) divisor = 1;
         mFPSDivisor = divisor;
+        ThreadedRenderer.setFPSDivisor(divisor);
     }
 
     void doFrame(long frameTimeNanos, int frame) {
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index e448f14..3dcfd00 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.DisplayCutoutProto.BOUNDS;
+import static android.view.DisplayCutoutProto.INSETS;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
@@ -28,6 +30,7 @@
 import android.graphics.Region;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -155,6 +158,16 @@
     }
 
     /**
+     * @hide
+     */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        mSafeInsets.writeToProto(proto, INSETS);
+        mBounds.getBounds().writeToProto(proto, BOUNDS);
+        proto.end(token);
+    }
+
+    /**
      * Insets the reference frame of the cutout in the given directions.
      *
      * @return a copy of this instance which has been inset
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 998fd01..3fd4696 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -461,8 +461,10 @@
                     + "refer to a bitmap drawable.");
         }
 
+        final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
+        validateHotSpot(bitmap, hotSpotX, hotSpotY);
         // Set the properties now that we have successfully loaded the icon.
-        mBitmap = ((BitmapDrawable)drawable).getBitmap();
+        mBitmap = bitmap;
         mHotSpotX = hotSpotX;
         mHotSpotY = hotSpotY;
     }
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index a417a4a..1f7f8b9 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -182,6 +182,11 @@
      * SurfaceTexture}, which can attach them to an OpenGL ES texture via {@link
      * SurfaceTexture#updateTexImage}.
      *
+     * Please note that holding onto the Surface created here is not enough to
+     * keep the provided SurfaceTexture from being reclaimed.  In that sense,
+     * the Surface will act like a
+     * {@link java.lang.ref.WeakReference weak reference} to the SurfaceTexture.
+     *
      * @param surfaceTexture The {@link SurfaceTexture} that is updated by this
      * Surface.
      * @throws OutOfResourcesException if the surface could not be created.
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 6a8f8b1..8b730f2 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -969,8 +969,6 @@
             mInitialized = true;
             mAppContext = context.getApplicationContext();
 
-            // b/68769804: For low FPS experiments.
-            setFPSDivisor(SystemProperties.getInt(DEBUG_FPS_DIVISOR, 1));
             initSched(renderProxy);
             initGraphicsStats();
         }
@@ -1025,9 +1023,7 @@
 
     /** b/68769804: For low FPS experiments. */
     public static void setFPSDivisor(int divisor) {
-        if (divisor <= 0) divisor = 1;
-        Choreographer.getInstance().setFPSDivisor(divisor);
-        nHackySetRTAnimationsEnabled(divisor == 1);
+        nHackySetRTAnimationsEnabled(divisor <= 1);
     }
 
     /** Not actually public - internal use only. This doc to make lint happy */
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cc63a62..f62189e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4182,6 +4182,11 @@
      */
     private static boolean sUseDefaultFocusHighlight;
 
+    /**
+     * True if zero-sized views can be focused.
+     */
+    private static boolean sCanFocusZeroSized;
+
     private String mTransitionName;
 
     static class TintInfo {
@@ -4798,6 +4803,8 @@
 
             sThrowOnInvalidFloatProperties = targetSdkVersion >= Build.VERSION_CODES.P;
 
+            sCanFocusZeroSized = targetSdkVersion < Build.VERSION_CODES.P;
+
             sCompatibilityDone = true;
         }
     }
@@ -7010,6 +7017,7 @@
     void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
         if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
             mPrivateFlags &= ~PFLAG_FOCUSED;
+            clearParentsWantFocus();
 
             if (propagate && mParent != null) {
                 mParent.clearChildFocus(this);
@@ -10046,6 +10054,13 @@
     }
 
     /**
+     * @return {@code true} if laid-out and not about to do another layout.
+     */
+    boolean isLayoutValid() {
+        return isLaidOut() && ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == 0);
+    }
+
+    /**
      * If this view doesn't do any drawing on its own, set this flag to
      * allow further optimizations. By default, this flag is not set on
      * View, but could be set on some View subclasses such as ViewGroup.
@@ -10817,7 +10832,7 @@
         if (views == null) {
             return;
         }
-        if (!isFocusable() || !isEnabled()) {
+        if (!canTakeFocus()) {
             return;
         }
         if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
@@ -11031,8 +11046,9 @@
      * descendants.
      *
      * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
-     * false), or if it is focusable and it is not focusable in touch mode
-     * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
+     * false), or if it can't be focused due to other conditions (not focusable in touch mode
+     * ({@link #isFocusableInTouchMode}) while the device is in touch mode, not visible, not
+     * enabled, or has no size).
      *
      * See also {@link #focusSearch(int)}, which is what you call to say that you
      * have focus, and you want your parent to look for the next one.
@@ -11139,9 +11155,7 @@
 
     private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
         // need to be focusable
-        if ((mViewFlags & FOCUSABLE) != FOCUSABLE
-                || (mViewFlags & VISIBILITY_MASK) != VISIBLE
-                || (mViewFlags & ENABLED_MASK) != ENABLED) {
+        if (!canTakeFocus()) {
             return false;
         }
 
@@ -11156,10 +11170,21 @@
             return false;
         }
 
+        if (!isLayoutValid()) {
+            mPrivateFlags |= PFLAG_WANTS_FOCUS;
+        }
+
         handleFocusGainInternal(direction, previouslyFocusedRect);
         return true;
     }
 
+    void clearParentsWantFocus() {
+        if (mParent instanceof View) {
+            ((View) mParent).mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
+            ((View) mParent).clearParentsWantFocus();
+        }
+    }
+
     /**
      * Call this to try to give focus to a specific view or to one of its descendants. This is a
      * special variant of {@link #requestFocus() } that will allow views that are not focusable in
@@ -13531,6 +13556,13 @@
         mAttachInfo.mUnbufferedDispatchRequested = true;
     }
 
+    private boolean canTakeFocus() {
+        return ((mViewFlags & VISIBILITY_MASK) == VISIBLE)
+                && ((mViewFlags & FOCUSABLE) == FOCUSABLE)
+                && ((mViewFlags & ENABLED_MASK) == ENABLED)
+                && (sCanFocusZeroSized || !isLayoutValid() || (mBottom > mTop) && (mRight > mLeft));
+    }
+
     /**
      * Set flags controlling behavior of this view.
      *
@@ -13550,6 +13582,7 @@
             return;
         }
         int privateFlags = mPrivateFlags;
+        boolean shouldNotifyFocusableAvailable = false;
 
         // If focusable is auto, update the FOCUSABLE bit.
         int focusableChangedByAuto = 0;
@@ -13588,7 +13621,7 @@
                             || focusableChangedByAuto == 0
                             || viewRootImpl == null
                             || viewRootImpl.mThread == Thread.currentThread()) {
-                        mParent.focusableViewAvailable(this);
+                        shouldNotifyFocusableAvailable = true;
                     }
                 }
             }
@@ -13611,10 +13644,7 @@
                 // about in case nothing has focus.  even if this specific view
                 // isn't focusable, it may contain something that is, so let
                 // the root view try to give this focus if nothing else does.
-                if ((mParent != null) && ((mViewFlags & ENABLED_MASK) == ENABLED)
-                        && (mBottom > mTop) && (mRight > mLeft)) {
-                    mParent.focusableViewAvailable(this);
-                }
+                shouldNotifyFocusableAvailable = true;
             }
         }
 
@@ -13623,17 +13653,18 @@
                 // a view becoming enabled should notify the parent as long as the view is also
                 // visible and the parent wasn't already notified by becoming visible during this
                 // setFlags invocation.
-                if ((mViewFlags & VISIBILITY_MASK) == VISIBLE
-                        && ((changed & VISIBILITY_MASK) == 0)) {
-                    if ((mParent != null) && (mViewFlags & ENABLED_MASK) == ENABLED) {
-                        mParent.focusableViewAvailable(this);
-                    }
-                }
+                shouldNotifyFocusableAvailable = true;
             } else {
                 if (hasFocus()) clearFocus();
             }
         }
 
+        if (shouldNotifyFocusableAvailable) {
+            if (mParent != null && canTakeFocus()) {
+                mParent.focusableViewAvailable(this);
+            }
+        }
+
         /* Check if the GONE bit has changed */
         if ((changed & GONE) != 0) {
             needGlobalAttributesUpdate(false);
@@ -18936,7 +18967,7 @@
      *
      * @hide
      */
-    public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
+    public Bitmap createSnapshot(ViewDebug.CanvasProvider canvasProvider, boolean skipChildren) {
         int width = mRight - mLeft;
         int height = mBottom - mTop;
 
@@ -18945,71 +18976,48 @@
         width = (int) ((width * scale) + 0.5f);
         height = (int) ((height * scale) + 0.5f);
 
-        Bitmap bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
-                width > 0 ? width : 1, height > 0 ? height : 1, quality);
-        if (bitmap == null) {
-            throw new OutOfMemoryError();
-        }
+        Canvas oldCanvas = null;
+        try {
+            Canvas canvas = canvasProvider.getCanvas(this,
+                    width > 0 ? width : 1, height > 0 ? height : 1);
 
-        Resources resources = getResources();
-        if (resources != null) {
-            bitmap.setDensity(resources.getDisplayMetrics().densityDpi);
-        }
-
-        Canvas canvas;
-        if (attachInfo != null) {
-            canvas = attachInfo.mCanvas;
-            if (canvas == null) {
-                canvas = new Canvas();
+            if (attachInfo != null) {
+                oldCanvas = attachInfo.mCanvas;
+                // Temporarily clobber the cached Canvas in case one of our children
+                // is also using a drawing cache. Without this, the children would
+                // steal the canvas by attaching their own bitmap to it and bad, bad
+                // things would happen (invisible views, corrupted drawings, etc.)
+                attachInfo.mCanvas = null;
             }
-            canvas.setBitmap(bitmap);
-            // Temporarily clobber the cached Canvas in case one of our children
-            // is also using a drawing cache. Without this, the children would
-            // steal the canvas by attaching their own bitmap to it and bad, bad
-            // things would happen (invisible views, corrupted drawings, etc.)
-            attachInfo.mCanvas = null;
-        } else {
-            // This case should hopefully never or seldom happen
-            canvas = new Canvas(bitmap);
-        }
-        boolean enabledHwBitmapsInSwMode = canvas.isHwBitmapsInSwModeEnabled();
-        canvas.setHwBitmapsInSwModeEnabled(true);
-        if ((backgroundColor & 0xff000000) != 0) {
-            bitmap.eraseColor(backgroundColor);
-        }
 
-        computeScroll();
-        final int restoreCount = canvas.save();
-        canvas.scale(scale, scale);
-        canvas.translate(-mScrollX, -mScrollY);
+            computeScroll();
+            final int restoreCount = canvas.save();
+            canvas.scale(scale, scale);
+            canvas.translate(-mScrollX, -mScrollY);
 
-        // Temporarily remove the dirty mask
-        int flags = mPrivateFlags;
-        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+            // Temporarily remove the dirty mask
+            int flags = mPrivateFlags;
+            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
 
-        // Fast path for layouts with no backgrounds
-        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
-            dispatchDraw(canvas);
-            drawAutofilledHighlight(canvas);
-            if (mOverlay != null && !mOverlay.isEmpty()) {
-                mOverlay.getOverlayView().draw(canvas);
+            // Fast path for layouts with no backgrounds
+            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+                dispatchDraw(canvas);
+                drawAutofilledHighlight(canvas);
+                if (mOverlay != null && !mOverlay.isEmpty()) {
+                    mOverlay.getOverlayView().draw(canvas);
+                }
+            } else {
+                draw(canvas);
             }
-        } else {
-            draw(canvas);
+
+            mPrivateFlags = flags;
+            canvas.restoreToCount(restoreCount);
+            return canvasProvider.createBitmap();
+        } finally {
+            if (oldCanvas != null) {
+                attachInfo.mCanvas = oldCanvas;
+            }
         }
-
-        mPrivateFlags = flags;
-
-        canvas.restoreToCount(restoreCount);
-        canvas.setBitmap(null);
-        canvas.setHwBitmapsInSwModeEnabled(enabledHwBitmapsInSwMode);
-
-        if (attachInfo != null) {
-            // Restore the cached Canvas for our siblings
-            attachInfo.mCanvas = canvas;
-        }
-
-        return bitmap;
     }
 
     /**
@@ -20160,15 +20168,58 @@
             }
         }
 
+        final boolean wasLayoutValid = isLayoutValid();
+
         mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
         mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
 
+        if (!wasLayoutValid && isFocused()) {
+            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
+            if (canTakeFocus()) {
+                // We have a robust focus, so parents should no longer be wanting focus.
+                clearParentsWantFocus();
+            } else if (!getViewRootImpl().isInLayout()) {
+                // This is a weird case. Most-likely the user, rather than ViewRootImpl, called
+                // layout. In this case, there's no guarantee that parent layouts will be evaluated
+                // and thus the safest action is to clear focus here.
+                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
+                clearParentsWantFocus();
+            } else if (!hasParentWantsFocus()) {
+                // original requestFocus was likely on this view directly, so just clear focus
+                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
+            }
+            // otherwise, we let parents handle re-assigning focus during their layout passes.
+        } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
+            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
+            View focused = findFocus();
+            if (focused != null) {
+                // Try to restore focus as close as possible to our starting focus.
+                if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
+                    // Give up and clear focus once we've reached the top-most parent which wants
+                    // focus.
+                    focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
+                }
+            }
+        }
+
         if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
             mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
             notifyEnterOrExitForAutoFillIfNeeded(true);
         }
     }
 
+    private boolean hasParentWantsFocus() {
+        ViewParent parent = mParent;
+        while (parent instanceof ViewGroup) {
+            ViewGroup pv = (ViewGroup) parent;
+            if ((pv.mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
+                return true;
+            }
+            parent = pv.mParent;
+        }
+        return false;
+    }
+
     /**
      * Called from layout when this view should
      * assign a size and position to each of its children.
@@ -20275,6 +20326,23 @@
             mOverlay.getOverlayView().setRight(newWidth);
             mOverlay.getOverlayView().setBottom(newHeight);
         }
+        // If this isn't laid out yet, focus assignment will be handled during the "deferment/
+        // backtracking" of requestFocus during layout, so don't touch focus here.
+        if (!sCanFocusZeroSized && isLayoutValid()) {
+            if (newWidth <= 0 || newHeight <= 0) {
+                if (hasFocus()) {
+                    clearFocus();
+                    if (mParent instanceof ViewGroup) {
+                        ((ViewGroup) mParent).clearFocusedInCluster();
+                    }
+                }
+                clearAccessibilityFocus();
+            } else if (oldWidth <= 0 || oldHeight <= 0) {
+                if (mParent != null && canTakeFocus()) {
+                    mParent.focusableViewAvailable(this);
+                }
+            }
+        }
         rebuildOutline();
     }
 
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index afa9413..b09934e 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -21,6 +21,7 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.Handler;
@@ -773,16 +774,15 @@
             final CountDownLatch latch = new CountDownLatch(1);
             final Bitmap[] cache = new Bitmap[1];
 
-            captureView.post(new Runnable() {
-                public void run() {
-                    try {
-                        cache[0] = captureView.createSnapshot(
-                                Bitmap.Config.ARGB_8888, 0, skipChildren);
-                    } catch (OutOfMemoryError e) {
-                        Log.w("View", "Out of memory for bitmap");
-                    } finally {
-                        latch.countDown();
-                    }
+            captureView.post(() -> {
+                try {
+                    CanvasProvider provider = captureView.isHardwareAccelerated()
+                            ? new HardwareCanvasProvider() : new SoftwareCanvasProvider();
+                    cache[0] = captureView.createSnapshot(provider, skipChildren);
+                } catch (OutOfMemoryError e) {
+                    Log.w("View", "Out of memory for bitmap");
+                } finally {
+                    latch.countDown();
                 }
             });
 
@@ -1740,4 +1740,86 @@
             }
         });
     }
+
+    /**
+     * @hide
+     */
+    public static class SoftwareCanvasProvider implements CanvasProvider {
+
+        private Canvas mCanvas;
+        private Bitmap mBitmap;
+        private boolean mEnabledHwBitmapsInSwMode;
+
+        @Override
+        public Canvas getCanvas(View view, int width, int height) {
+            mBitmap = Bitmap.createBitmap(view.getResources().getDisplayMetrics(),
+                    width, height, Bitmap.Config.ARGB_8888);
+            if (mBitmap == null) {
+                throw new OutOfMemoryError();
+            }
+            mBitmap.setDensity(view.getResources().getDisplayMetrics().densityDpi);
+
+            if (view.mAttachInfo != null) {
+                mCanvas = view.mAttachInfo.mCanvas;
+            }
+            if (mCanvas == null) {
+                mCanvas = new Canvas();
+            }
+            mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled();
+            mCanvas.setBitmap(mBitmap);
+            return mCanvas;
+        }
+
+        @Override
+        public Bitmap createBitmap() {
+            mCanvas.setBitmap(null);
+            mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode);
+            return mBitmap;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static class HardwareCanvasProvider implements CanvasProvider {
+
+        private View mView;
+        private Point mSize;
+        private RenderNode mNode;
+        private DisplayListCanvas mCanvas;
+
+        @Override
+        public Canvas getCanvas(View view, int width, int height) {
+            mView = view;
+            mSize = new Point(width, height);
+            mNode = RenderNode.create("ViewDebug", mView);
+            mNode.setLeftTopRightBottom(0, 0, width, height);
+            mNode.setClipToBounds(false);
+            mCanvas = mNode.start(width, height);
+            return mCanvas;
+        }
+
+        @Override
+        public Bitmap createBitmap() {
+            mNode.end(mCanvas);
+            return ThreadedRenderer.createHardwareBitmap(mNode, mSize.x, mSize.y);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public interface CanvasProvider {
+
+        /**
+         * Returns a canvas which can be used to draw {@param view}
+         */
+        Canvas getCanvas(View view, int width, int height);
+
+        /**
+         * Creates a bitmap from previously returned canvas
+         * @return
+         */
+        Bitmap createBitmap();
+    }
 }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 122df93..703364f 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3215,22 +3215,31 @@
         }
         int descendantFocusability = getDescendantFocusability();
 
+        boolean result;
         switch (descendantFocusability) {
             case FOCUS_BLOCK_DESCENDANTS:
-                return super.requestFocus(direction, previouslyFocusedRect);
+                result = super.requestFocus(direction, previouslyFocusedRect);
+                break;
             case FOCUS_BEFORE_DESCENDANTS: {
                 final boolean took = super.requestFocus(direction, previouslyFocusedRect);
-                return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
+                result = took ? took : onRequestFocusInDescendants(direction,
+                        previouslyFocusedRect);
+                break;
             }
             case FOCUS_AFTER_DESCENDANTS: {
                 final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
-                return took ? took : super.requestFocus(direction, previouslyFocusedRect);
+                result = took ? took : super.requestFocus(direction, previouslyFocusedRect);
+                break;
             }
             default:
                 throw new IllegalStateException("descendant focusability must be "
                         + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
                         + "but is " + descendantFocusability);
         }
+        if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
+            mPrivateFlags |= PFLAG_WANTS_FOCUS;
+        }
+        return result;
     }
 
     /**
@@ -3854,7 +3863,7 @@
      * @hide
      */
     @Override
-    public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
+    public Bitmap createSnapshot(ViewDebug.CanvasProvider canvasProvider, boolean skipChildren) {
         int count = mChildrenCount;
         int[] visibilities = null;
 
@@ -3870,17 +3879,17 @@
             }
         }
 
-        Bitmap b = super.createSnapshot(quality, backgroundColor, skipChildren);
-
-        if (skipChildren) {
-            for (int i = 0; i < count; i++) {
-                View child = getChildAt(i);
-                child.mViewFlags = (child.mViewFlags & ~View.VISIBILITY_MASK)
-                        | (visibilities[i] & View.VISIBILITY_MASK);
+        try {
+            return super.createSnapshot(canvasProvider, skipChildren);
+        } finally {
+            if (skipChildren) {
+                for (int i = 0; i < count; i++) {
+                    View child = getChildAt(i);
+                    child.mViewFlags = (child.mViewFlags & ~View.VISIBILITY_MASK)
+                            | (visibilities[i] & View.VISIBILITY_MASK);
+                }
             }
         }
-
-        return b;
     }
 
     /** Return true if this ViewGroup is laying out using optical bounds. */
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index d665dde..1d94abe 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -26,6 +26,8 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.List;
 
 /**
@@ -204,6 +206,16 @@
     public abstract void setTextLines(int[] charOffsets, int[] baselines);
 
     /**
+     * Sets the identifier used to set the text associated with this view.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setTextIdEntry(@NonNull String entryName) {
+        Preconditions.checkNotNull(entryName);
+    }
+
+    /**
      * Set optional hint text associated with this view; this is for example the text that is
      * shown by an EditText when it is empty to indicate to the user the kind of text to input.
      */
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cbe012a..a65aba1 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -17,6 +17,32 @@
 package android.view;
 
 import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+import static android.view.WindowLayoutParamsProto.ALPHA;
+import static android.view.WindowLayoutParamsProto.BUTTON_BRIGHTNESS;
+import static android.view.WindowLayoutParamsProto.COLOR_MODE;
+import static android.view.WindowLayoutParamsProto.FLAGS;
+import static android.view.WindowLayoutParamsProto.FLAGS_EXTRA;
+import static android.view.WindowLayoutParamsProto.FORMAT;
+import static android.view.WindowLayoutParamsProto.GRAVITY;
+import static android.view.WindowLayoutParamsProto.HAS_SYSTEM_UI_LISTENERS;
+import static android.view.WindowLayoutParamsProto.HEIGHT;
+import static android.view.WindowLayoutParamsProto.HORIZONTAL_MARGIN;
+import static android.view.WindowLayoutParamsProto.INPUT_FEATURE_FLAGS;
+import static android.view.WindowLayoutParamsProto.NEEDS_MENU_KEY;
+import static android.view.WindowLayoutParamsProto.PREFERRED_REFRESH_RATE;
+import static android.view.WindowLayoutParamsProto.PRIVATE_FLAGS;
+import static android.view.WindowLayoutParamsProto.ROTATION_ANIMATION;
+import static android.view.WindowLayoutParamsProto.SCREEN_BRIGHTNESS;
+import static android.view.WindowLayoutParamsProto.SOFT_INPUT_MODE;
+import static android.view.WindowLayoutParamsProto.SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS;
+import static android.view.WindowLayoutParamsProto.SYSTEM_UI_VISIBILITY_FLAGS;
+import static android.view.WindowLayoutParamsProto.TYPE;
+import static android.view.WindowLayoutParamsProto.USER_ACTIVITY_TIMEOUT;
+import static android.view.WindowLayoutParamsProto.VERTICAL_MARGIN;
+import static android.view.WindowLayoutParamsProto.WIDTH;
+import static android.view.WindowLayoutParamsProto.WINDOW_ANIMATIONS;
+import static android.view.WindowLayoutParamsProto.X;
+import static android.view.WindowLayoutParamsProto.Y;
 
 import android.Manifest.permission;
 import android.annotation.IntDef;
@@ -2722,7 +2748,33 @@
          */
         public void writeToProto(ProtoOutputStream proto, long fieldId) {
             final long token = proto.start(fieldId);
-            proto.write(WindowLayoutParamsProto.TYPE, type);
+            proto.write(TYPE, type);
+            proto.write(X, x);
+            proto.write(Y, y);
+            proto.write(WIDTH, width);
+            proto.write(HEIGHT, height);
+            proto.write(HORIZONTAL_MARGIN, horizontalMargin);
+            proto.write(VERTICAL_MARGIN, verticalMargin);
+            proto.write(GRAVITY, gravity);
+            proto.write(SOFT_INPUT_MODE, softInputMode);
+            proto.write(FORMAT, format);
+            proto.write(WINDOW_ANIMATIONS, windowAnimations);
+            proto.write(ALPHA, alpha);
+            proto.write(SCREEN_BRIGHTNESS, screenBrightness);
+            proto.write(BUTTON_BRIGHTNESS, buttonBrightness);
+            proto.write(ROTATION_ANIMATION, rotationAnimation);
+            proto.write(PREFERRED_REFRESH_RATE, preferredRefreshRate);
+            proto.write(WindowLayoutParamsProto.PREFERRED_DISPLAY_MODE_ID, preferredDisplayModeId);
+            proto.write(HAS_SYSTEM_UI_LISTENERS, hasSystemUiListeners);
+            proto.write(INPUT_FEATURE_FLAGS, inputFeatures);
+            proto.write(USER_ACTIVITY_TIMEOUT, userActivityTimeout);
+            proto.write(NEEDS_MENU_KEY, needsMenuKey);
+            proto.write(COLOR_MODE, mColorMode);
+            proto.write(FLAGS, flags);
+            proto.write(FLAGS_EXTRA, flags2);
+            proto.write(PRIVATE_FLAGS, privateFlags);
+            proto.write(SYSTEM_UI_VISIBILITY_FLAGS, systemUiVisibility);
+            proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility);
             proto.end(token);
         }
 
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 21943bd..9c1c9e3 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -18,8 +18,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import android.annotation.SystemApi;
-
 /**
  * Constants for interfacing with WindowManagerService and WindowManagerPolicyInternal.
  * @hide
@@ -62,7 +60,6 @@
      * Set to {@code true} when intent was invoked from pressing the home key.
      * @hide
      */
-    @SystemApi
     String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY";
 
     // TODO: move this to a more appropriate place.
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 1d19a9f..6c2d349 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -38,19 +38,14 @@
  * <p>
  * An accessibility event is fired by an individual view which populates the event with
  * data for its state and requests from its parent to send the event to interested
- * parties. The parent can optionally add an {@link AccessibilityRecord} for itself before
- * dispatching a similar request to its parent. A parent can also choose not to respect the
- * request for sending an event. The accessibility event is sent by the topmost view in the
- * view tree. Therefore, an {@link android.accessibilityservice.AccessibilityService} can
- * explore all records in an accessibility event to obtain more information about the
- * context in which the event was fired.
+ * parties. The parent can optionally modify or even block the event based on its broader
+ * understanding of the user interface's context.
  * </p>
  * <p>
- * The main purpose of an accessibility event is to expose enough information for an
- * {@link android.accessibilityservice.AccessibilityService} to provide meaningful feedback
- * to the user. Sometimes however, an accessibility service may need more contextual
- * information then the one in the event pay-load. In such cases the service can obtain
- * the event source which is an {@link AccessibilityNodeInfo} (snapshot of a View state)
+ * The main purpose of an accessibility event is to communicate changes in the UI to an
+ * {@link android.accessibilityservice.AccessibilityService}. The service may then inspect,
+ * if needed the user interface by examining the View hierarchy, as represented by a tree of
+ * {@link AccessibilityNodeInfo}s (snapshot of a View state)
  * which can be used for exploring the window content. Note that the privilege for accessing
  * an event's source, thus the window content, has to be explicitly requested. For more
  * details refer to {@link android.accessibilityservice.AccessibilityService}. If an
@@ -85,21 +80,6 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source's sub-tree.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- *   <li>{@link #isPassword()} - Whether the source is password.</li>
- *   <li>{@link #isChecked()} - Whether the source is checked.</li>
- *   <li>{@link #getContentDescription()} - The content description of the source.</li>
- *   <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getItemCount()} - The total items of the source
- *       (for descendants of AdapterView).</li>
  * </ul>
  * </p>
  * <p>
@@ -113,21 +93,6 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source's sub-tree.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- *   <li>{@link #isPassword()} - Whether the source is password.</li>
- *   <li>{@link #isChecked()} - Whether the source is checked.</li>
- *   <li>{@link #getContentDescription()} - The content description of the source.</li>
- *   <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getItemCount()} - The total items of the source
- *       (for descendants of AdapterView).</li>
  * </ul>
  * </p>
  * <p>
@@ -141,23 +106,6 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source's sub-tree.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- *   <li>{@link #isPassword()} - Whether the source is password.</li>
- *   <li>{@link #isChecked()} - Whether the source is checked.</li>
- *   <li>{@link #getItemCount()} - The number of selectable items of the source.</li>
- *   <li>{@link #getCurrentItemIndex()} - The currently selected item index.</li>
- *   <li>{@link #getContentDescription()} - The content description of the source.</li>
- *   <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getItemCount()} - The total items of the source
- *       (for descendants of AdapterView).</li>
  * </ul>
  * </p>
  * <p>
@@ -171,23 +119,6 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source's sub-tree.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- *   <li>{@link #isPassword()} - Whether the source is password.</li>
- *   <li>{@link #isChecked()} - Whether the source is checked.</li>
- *   <li>{@link #getItemCount()} - The number of focusable items on the screen.</li>
- *   <li>{@link #getCurrentItemIndex()} - The currently focused item index.</li>
- *   <li>{@link #getContentDescription()} - The content description of the source.</li>
- *   <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getItemCount()} - The total items of the source
- *       (for descendants of AdapterView).</li>
  * </ul>
  * </p>
  * <p>
@@ -201,15 +132,11 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- *   <li>{@link #isPassword()} - Whether the source is password.</li>
- *   <li>{@link #isChecked()} - Whether the source is checked.</li>
+ *   <li>{@link #getText()} - The new text of the source.</li>
+ *   <li>{@link #getBeforeText()} - The text of the source before the change.</li>
  *   <li>{@link #getFromIndex()} - The text change start index.</li>
  *   <li>{@link #getAddedCount()} - The number of added characters.</li>
  *   <li>{@link #getRemovedCount()} - The number of removed characters.</li>
- *   <li>{@link #getBeforeText()} - The text of the source before the change.</li>
- *   <li>{@link #getContentDescription()} - The content description of the source.</li>
  * </ul>
  * </p>
  * <p>
@@ -223,13 +150,6 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source.</li>
- *   <li>{@link #isPassword()} - Whether the source is password.</li>
- *   <li>{@link #getFromIndex()} - The selection start index.</li>
- *   <li>{@link #getToIndex()} - The selection end index.</li>
- *   <li>{@link #getItemCount()} - The length of the source text.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- *   <li>{@link #getContentDescription()} - The content description of the source.</li>
  * </ul>
  * </p>
  * <b>View text traversed at movement granularity</b> - represents the event of traversing the
@@ -251,23 +171,11 @@
  *   <li>{@link #getToIndex()} - The end of the text that was skipped over in this movement.
  *       This is the ending point when moving forward through the text, but not when moving
  *       back.</li>
- *   <li>{@link #isPassword()} - Whether the source is password.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- *   <li>{@link #getContentDescription()} - The content description of the source.</li>
- *   <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text
- *       was traversed.</li>
  *   <li>{@link #getAction()} - Gets traversal action which specifies the direction.</li>
  * </ul>
  * </p>
  * <p>
- * <b>View scrolled</b> - represents the event of scrolling a view. If
- * the source is a descendant of {@link android.widget.AdapterView} the
- * scroll is reported in terms of visible items - the first visible item,
- * the last visible item, and the total items - because the the source
- * is unaware of its pixel size since its adapter is responsible for
- * creating views. In all other cases the scroll is reported as the current
- * scroll on the X and Y axis respectively plus the height of the source in
- * pixels.</br>
+ * <b>View scrolled</b> - represents the event of scrolling a view. </br>
  * <em>Type:</em> {@link #TYPE_VIEW_SCROLLED}</br>
  * <em>Properties:</em></br>
  * <ul>
@@ -276,29 +184,9 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source's sub-tree.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- *   <li>{@link #getContentDescription()} - The content description of the source.</li>
- *   <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getItemCount()} - The total items of the source
- *       (for descendants of AdapterView).</li>
+ *   <li>{@link #getScrollDeltaX()} - The difference in the horizontal position.</li>
+ *   <li>{@link #getScrollDeltaY()} - The difference in the vertical position.</li>
  * </ul>
- * <em>Note:</em> This event type is not dispatched to descendants though
- * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
- * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event
- * source {@link android.view.View} and the sub-tree rooted at it will not receive
- * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent)
- * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add
- * text content to such events is by setting the
- * {@link android.R.styleable#View_contentDescription contentDescription} of the source
- * view.</br>
  * </p>
  * <p>
  * <b>TRANSITION TYPES</b></br>
@@ -316,7 +204,6 @@
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
  *   <li>{@link #getText()} - The text of the source's sub-tree.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
  * </ul>
  * </p>
  * <p>
@@ -325,10 +212,6 @@
  * a view size, etc.</br>
  * </p>
  * <p>
- * <strong>Note:</strong> This event is fired only for the window source of the
- * last accessibility event different from {@link #TYPE_NOTIFICATION_STATE_CHANGED}
- * and its purpose is to notify clients that the content of the user interaction
- * window has changed.</br>
  * <em>Type:</em> {@link #TYPE_WINDOW_CONTENT_CHANGED}</br>
  * <em>Properties:</em></br>
  * <ul>
@@ -339,32 +222,26 @@
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
  * </ul>
- * <em>Note:</em> This event type is not dispatched to descendants though
- * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
- * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event
- * source {@link android.view.View} and the sub-tree rooted at it will not receive
- * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent)
- * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add
- * text content to such events is by setting the
- * {@link android.R.styleable#View_contentDescription contentDescription} of the source
- * view.</br>
  * </p>
  * <p>
- * <b>Windows changed</b> - represents the event of changes in the windows shown on
+ * <b>Windows changed</b> - represents a change in the windows shown on
  * the screen such as a window appeared, a window disappeared, a window size changed,
- * a window layer changed, etc.</br>
+ * a window layer changed, etc. These events should only come from the system, which is responsible
+ * for managing windows. The list of windows is available from
+ * {@link android.accessibilityservice.AccessibilityService#getWindows()}.
+ * For regions of the user interface that are presented as windows but are
+ * controlled by an app's process, use {@link #TYPE_WINDOW_STATE_CHANGED}.</br>
  * <em>Type:</em> {@link #TYPE_WINDOWS_CHANGED}</br>
  * <em>Properties:</em></br>
  * <ul>
  *   <li>{@link #getEventType()} - The type of the event.</li>
  *   <li>{@link #getEventTime()} - The event time.</li>
+ *   <li>{@link #getWindowChanges()}</li> - The specific change to the source window
  * </ul>
  * <em>Note:</em> You can retrieve the {@link AccessibilityWindowInfo} for the window
- * source of the event via {@link AccessibilityEvent#getSource()} to get the source
- * node on which then call {@link AccessibilityNodeInfo#getWindow()
- * AccessibilityNodeInfo.getWindow()} to get the window. Also all windows on the screen can
- * be retrieved by a call to {@link android.accessibilityservice.AccessibilityService#getWindows()
- * android.accessibilityservice.AccessibilityService.getWindows()}.
+ * source of the event by looking through the list returned by
+ * {@link android.accessibilityservice.AccessibilityService#getWindows()} for the window whose ID
+ * matches {@link #getWindowId()}.
  * </p>
  * <p>
  * <b>NOTIFICATION TYPES</b></br>
@@ -402,19 +279,6 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source's sub-tree.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- *   <li>{@link #getContentDescription()} - The content description of the source.</li>
- *   <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getItemCount()} - The total items of the source
- *       (for descendants of AdapterView).</li>
  * </ul>
  * </p>
  * <b>View hover exit</b> - represents the event of stopping to hover
@@ -428,19 +292,6 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source's sub-tree.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- *   <li>{@link #getContentDescription()} - The content description of the source.</li>
- *   <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- *       (without descendants of AdapterView).</li>
- *   <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- *       inclusive (for descendants of AdapterView).</li>
- *   <li>{@link #getItemCount()} - The total items of the source
- *       (for descendants of AdapterView).</li>
  * </ul>
  * </p>
  * <p>
@@ -513,10 +364,10 @@
  * <b>MISCELLANEOUS TYPES</b></br>
  * </p>
  * <p>
- * <b>Announcement</b> - represents the event of an application making an
- * announcement. Usually this announcement is related to some sort of a context
- * change for which none of the events representing UI transitions is a good fit.
- * For example, announcing a new page in a book.</br>
+ * <b>Announcement</b> - represents the event of an application requesting a screen reader to make
+ * an announcement. Because the event carries no semantic meaning, this event is appropriate only
+ * in exceptional situations where additional screen reader output is needed but other types of
+ * accessibility services do not need to be aware of the change.</br>
  * <em>Type:</em> {@link #TYPE_ANNOUNCEMENT}</br>
  * <em>Properties:</em></br>
  * <ul>
@@ -526,7 +377,6 @@
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
  *   <li>{@link #getText()} - The text of the announcement.</li>
- *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
  * </ul>
  * </p>
  *
@@ -674,7 +524,8 @@
     public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000;
 
     /**
-     * Represents the event change in the windows shown on the screen.
+     * Represents the event change in the system windows shown on the screen. This event type should
+     * only be dispatched by the system.
      */
     public static final int TYPE_WINDOWS_CHANGED = 0x00400000;
 
@@ -696,7 +547,8 @@
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
-     * A node in the subtree rooted at the source node was added or removed.
+     * One or more content changes occurred in the the subtree rooted at the source node,
+     * or the subtree's structure changed when a node was added or removed.
      */
     public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001;
 
@@ -712,6 +564,99 @@
      */
     public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
 
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window was added.
+     */
+    public static final int WINDOWS_CHANGE_ADDED = 0x00000001;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * A window was removed.
+     */
+    public static final int WINDOWS_CHANGE_REMOVED = 0x00000002;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's title changed.
+     */
+    public static final int WINDOWS_CHANGE_TITLE = 0x00000004;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's bounds changed.
+     */
+    public static final int WINDOWS_CHANGE_BOUNDS = 0x00000008;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's layer changed.
+     */
+    public static final int WINDOWS_CHANGE_LAYER = 0x00000010;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's {@link AccessibilityWindowInfo#isActive()} changed.
+     */
+    public static final int WINDOWS_CHANGE_ACTIVE = 0x00000020;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's {@link AccessibilityWindowInfo#isFocused()} changed.
+     */
+    public static final int WINDOWS_CHANGE_FOCUSED = 0x00000040;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's {@link AccessibilityWindowInfo#isAccessibilityFocused()} changed.
+     */
+    public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 0x00000080;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's parent changed.
+     */
+    public static final int WINDOWS_CHANGE_PARENT = 0x00000100;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window's children changed.
+     */
+    public static final int WINDOWS_CHANGE_CHILDREN = 0x00000200;
+
+    /**
+     * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+     * The window either entered or exited picture-in-picture mode.
+     */
+    public static final int WINDOWS_CHANGE_PIP = 0x00000400;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "WINDOWS_CHANGE_" }, value = {
+            WINDOWS_CHANGE_ADDED,
+            WINDOWS_CHANGE_REMOVED,
+            WINDOWS_CHANGE_TITLE,
+            WINDOWS_CHANGE_BOUNDS,
+            WINDOWS_CHANGE_LAYER,
+            WINDOWS_CHANGE_ACTIVE,
+            WINDOWS_CHANGE_FOCUSED,
+            WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED,
+            WINDOWS_CHANGE_PARENT,
+            WINDOWS_CHANGE_CHILDREN,
+            WINDOWS_CHANGE_PIP
+    })
+    public @interface WindowsChangeTypes {}
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "CONTENT_CHANGE_TYPE_" },
+            value = {
+                    CONTENT_CHANGE_TYPE_UNDEFINED,
+                    CONTENT_CHANGE_TYPE_SUBTREE,
+                    CONTENT_CHANGE_TYPE_TEXT,
+                    CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION
+            })
+    public @interface ContentChangeTypes {}
 
     /** @hide */
     @IntDef(flag = true, prefix = { "TYPE_" }, value = {
@@ -782,6 +727,7 @@
     int mMovementGranularity;
     int mAction;
     int mContentChangeTypes;
+    int mWindowChangeTypes;
 
     private ArrayList<AccessibilityRecord> mRecords;
 
@@ -802,6 +748,7 @@
         mMovementGranularity = event.mMovementGranularity;
         mAction = event.mAction;
         mContentChangeTypes = event.mContentChangeTypes;
+        mWindowChangeTypes = event.mWindowChangeTypes;
         mEventTime = event.mEventTime;
         mPackageName = event.mPackageName;
     }
@@ -885,6 +832,7 @@
      *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
      *         </ul>
      */
+    @ContentChangeTypes
     public int getContentChangeTypes() {
         return mContentChangeTypes;
     }
@@ -913,12 +861,49 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      * @see #getContentChangeTypes()
      */
-    public void setContentChangeTypes(int changeTypes) {
+    public void setContentChangeTypes(@ContentChangeTypes int changeTypes) {
         enforceNotSealed();
         mContentChangeTypes = changeTypes;
     }
 
     /**
+     * Get the bit mask of change types signaled by a {@link #TYPE_WINDOWS_CHANGED} event. A
+     * single event may represent multiple change types.
+     *
+     * @return The bit mask of change types.
+     */
+    @WindowsChangeTypes
+    public int getWindowChanges() {
+        return mWindowChangeTypes;
+    }
+
+    /** @hide  */
+    public void setWindowChanges(@WindowsChangeTypes int changes) {
+        mWindowChangeTypes = changes;
+    }
+
+    private static String windowChangeTypesToString(@WindowsChangeTypes int types) {
+        return BitUtils.flagsToString(types, AccessibilityEvent::singleWindowChangeTypeToString);
+    }
+
+    private static String singleWindowChangeTypeToString(int type) {
+        switch (type) {
+            case WINDOWS_CHANGE_ADDED: return "WINDOWS_CHANGE_ADDED";
+            case WINDOWS_CHANGE_REMOVED: return "WINDOWS_CHANGE_REMOVED";
+            case WINDOWS_CHANGE_TITLE: return "WINDOWS_CHANGE_TITLE";
+            case WINDOWS_CHANGE_BOUNDS: return "WINDOWS_CHANGE_BOUNDS";
+            case WINDOWS_CHANGE_LAYER: return "WINDOWS_CHANGE_LAYER";
+            case WINDOWS_CHANGE_ACTIVE: return "WINDOWS_CHANGE_ACTIVE";
+            case WINDOWS_CHANGE_FOCUSED: return "WINDOWS_CHANGE_FOCUSED";
+            case WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED:
+                return "WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED";
+            case WINDOWS_CHANGE_PARENT: return "WINDOWS_CHANGE_PARENT";
+            case WINDOWS_CHANGE_CHILDREN: return "WINDOWS_CHANGE_CHILDREN";
+            default: return Integer.toHexString(type);
+        }
+    }
+
+    /**
      * Sets the event type.
      *
      * @param eventType The event type.
@@ -1025,6 +1010,26 @@
     }
 
     /**
+     * Convenience method to obtain a {@link #TYPE_WINDOWS_CHANGED} event for a specific window and
+     * change set.
+     *
+     * @param windowId The ID of the window that changed
+     * @param windowChangeTypes The changes to populate
+     * @return An instance of a TYPE_WINDOWS_CHANGED, populated with the requested fields and with
+     *         importantForAccessibility set to {@code true}.
+     *
+     * @hide
+     */
+    public static AccessibilityEvent obtainWindowsChangedEvent(
+            int windowId, int windowChangeTypes) {
+        final AccessibilityEvent event = AccessibilityEvent.obtain(TYPE_WINDOWS_CHANGED);
+        event.setWindowId(windowId);
+        event.setWindowChanges(windowChangeTypes);
+        event.setImportantForAccessibility(true);
+        return event;
+    }
+
+    /**
      * Returns a cached instance if such is available or a new one is
      * instantiated with its type property set.
      *
@@ -1099,6 +1104,7 @@
         mMovementGranularity = 0;
         mAction = 0;
         mContentChangeTypes = 0;
+        mWindowChangeTypes = 0;
         mPackageName = null;
         mEventTime = 0;
         if (mRecords != null) {
@@ -1120,6 +1126,7 @@
         mMovementGranularity = parcel.readInt();
         mAction = parcel.readInt();
         mContentChangeTypes = parcel.readInt();
+        mWindowChangeTypes = parcel.readInt();
         mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         mEventTime = parcel.readLong();
         mConnectionId = parcel.readInt();
@@ -1178,6 +1185,7 @@
         parcel.writeInt(mMovementGranularity);
         parcel.writeInt(mAction);
         parcel.writeInt(mContentChangeTypes);
+        parcel.writeInt(mWindowChangeTypes);
         TextUtils.writeToParcel(mPackageName, parcel, 0);
         parcel.writeLong(mEventTime);
         parcel.writeInt(mConnectionId);
@@ -1238,11 +1246,13 @@
         builder.append("; PackageName: ").append(mPackageName);
         builder.append("; MovementGranularity: ").append(mMovementGranularity);
         builder.append("; Action: ").append(mAction);
+        builder.append("; ContentChangeTypes: ").append(
+                contentChangeTypesToString(mContentChangeTypes));
+        builder.append("; WindowChangeTypes: ").append(
+                windowChangeTypesToString(mWindowChangeTypes));
         builder.append(super.toString());
         if (DEBUG) {
             builder.append("\n");
-            builder.append("; ContentChangeTypes: ").append(
-                    contentChangeTypesToString(mContentChangeTypes));
             builder.append("; sourceWindowId: ").append(mSourceWindowId);
             builder.append("; mSourceNodeId: ").append(mSourceNodeId);
             for (int i = 0; i < getRecordCount(); i++) {
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index ef1a3f3..c1c9174 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -21,9 +21,12 @@
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.LongArray;
 import android.util.Pools.SynchronizedPool;
+import android.view.accessibility.AccessibilityEvent.WindowsChangeTypes;
 
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -575,7 +578,7 @@
         StringBuilder builder = new StringBuilder();
         builder.append("AccessibilityWindowInfo[");
         builder.append("title=").append(mTitle);
-        builder.append("id=").append(mId);
+        builder.append(", id=").append(mId);
         builder.append(", type=").append(typeToString(mType));
         builder.append(", layer=").append(mLayer);
         builder.append(", bounds=").append(mBoundsInScreen);
@@ -713,6 +716,60 @@
         return false;
     }
 
+    /**
+     * Reports how this window differs from a possibly different state of the same window. The
+     * argument must have the same id and type as neither of those properties may change.
+     *
+     * @param other The new state.
+     * @return A set of flags showing how the window has changes, or 0 if the two states are the
+     * same.
+     *
+     * @hide
+     */
+    @WindowsChangeTypes
+    public int differenceFrom(AccessibilityWindowInfo other) {
+        if (other.mId != mId) {
+            throw new IllegalArgumentException("Not same window.");
+        }
+        if (other.mType != mType) {
+            throw new IllegalArgumentException("Not same type.");
+        }
+        int changes = 0;
+        if (!TextUtils.equals(mTitle, other.mTitle)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_TITLE;
+        }
+
+        if (!mBoundsInScreen.equals(other.mBoundsInScreen)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
+        }
+        if (mLayer != other.mLayer) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_LAYER;
+        }
+        if (getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)
+                != other.getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_ACTIVE;
+        }
+        if (getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)
+                != other.getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
+        }
+        if (getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)
+                != other.getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
+        }
+        if (getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)
+                != other.getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_PIP;
+        }
+        if (mParentId != other.mParentId) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_PARENT;
+        }
+        if (!Objects.equals(mChildIds, other.mChildIds)) {
+            changes |= AccessibilityEvent.WINDOWS_CHANGE_CHILDREN;
+        }
+        return changes;
+    }
+
     public static final Parcelable.Creator<AccessibilityWindowInfo> CREATOR =
             new Creator<AccessibilityWindowInfo>() {
         @Override
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 2697454..78b41c6 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -53,6 +53,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -1027,7 +1028,9 @@
      * Gets the user data used for
      * <a href="AutofillService.html#FieldClassification">field classification</a>.
      *
-     * <p><b>Note:</b> This method should only be called by an app providing an autofill service.
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it's ignored if the caller currently doesn't have an enabled autofill service for
+     * the user.
      *
      * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was
      * reset or if the caller currently does not have an enabled autofill service for the user.
@@ -1079,6 +1082,49 @@
     }
 
     /**
+     * Gets the name of the default algorithm used for
+     * <a href="AutofillService.html#FieldClassification">field classification</a>.
+     *
+     * <p>The default algorithm is used when the algorithm on {@link UserData} is invalid or not
+     * set.
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it's ignored if the caller currently doesn't have an enabled autofill service for
+     * the user.
+     */
+    @Nullable
+    public String getDefaultFieldClassificationAlgorithm() {
+        try {
+            return mService.getDefaultFieldClassificationAlgorithm();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return null;
+        }
+    }
+
+    /**
+     * Gets the name of all algorithms currently available for
+     * <a href="AutofillService.html#FieldClassification">field classification</a>.
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it's ignored if the caller currently doesn't have an enabled autofill service for
+     * the user.
+     *
+     * @return list of all algorithms currently available, or an empty list if the caller currently
+     * does not have an enabled autofill service for the user.
+     */
+    @NonNull
+    public List<String> getAvailableFieldClassificationAlgorithms() {
+        try {
+            final List<String> names = mService.getAvailableFieldClassificationAlgorithms();
+            return names != null ? names : Collections.emptyList();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return null;
+        }
+    }
+
+    /**
      * Returns {@code true} if autofill is supported by the current device and
      * is supported for this user.
      *
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 38bb311..1afa35e 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -58,4 +58,6 @@
     void setUserData(in UserData userData);
     boolean isFieldClassificationEnabled();
     ComponentName getAutofillServiceComponentName();
+    List<String> getAvailableFieldClassificationAlgorithms();
+    String getDefaultFieldClassificationAlgorithm();
 }
diff --git a/core/java/android/webkit/FindAddress.java b/core/java/android/webkit/FindAddress.java
new file mode 100644
index 0000000..31b2427
--- /dev/null
+++ b/core/java/android/webkit/FindAddress.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import java.util.Locale;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Java implementation of legacy WebView.findAddress algorithm.
+ *
+ * @hide
+ */
+class FindAddress {
+    static class ZipRange {
+        int mLow;
+        int mHigh;
+        int mException1;
+        int mException2;
+        ZipRange(int low, int high, int exception1, int exception2) {
+            mLow = low;
+            mHigh = high;
+            mException1 = exception1;
+            mException2 = exception1;
+        }
+        boolean matches(String zipCode) {
+            int prefix = Integer.parseInt(zipCode.substring(0, 2));
+            return (mLow <= prefix && prefix <= mHigh) || prefix == mException1
+                    || prefix == mException2;
+        }
+    }
+
+    // Addresses consist of at least this many words, not including state and zip code.
+    private static final int MIN_ADDRESS_WORDS = 4;
+
+    // Adddresses consist of at most this many words, not including state and zip code.
+    private static final int MAX_ADDRESS_WORDS = 14;
+
+    // Addresses consist of at most this many lines.
+    private static final int MAX_ADDRESS_LINES = 5;
+
+    // No words in an address are longer than this many characters.
+    private static final int kMaxAddressNameWordLength = 25;
+
+    // Location name should be in the first MAX_LOCATION_NAME_DISTANCE words
+    private static final int MAX_LOCATION_NAME_DISTANCE = 5;
+
+    private static final ZipRange[] sStateZipCodeRanges = {
+            new ZipRange(99, 99, -1, -1), // AK Alaska.
+            new ZipRange(35, 36, -1, -1), // AL Alabama.
+            new ZipRange(71, 72, -1, -1), // AR Arkansas.
+            new ZipRange(96, 96, -1, -1), // AS American Samoa.
+            new ZipRange(85, 86, -1, -1), // AZ Arizona.
+            new ZipRange(90, 96, -1, -1), // CA California.
+            new ZipRange(80, 81, -1, -1), // CO Colorado.
+            new ZipRange(6, 6, -1, -1), // CT Connecticut.
+            new ZipRange(20, 20, -1, -1), // DC District of Columbia.
+            new ZipRange(19, 19, -1, -1), // DE Delaware.
+            new ZipRange(32, 34, -1, -1), // FL Florida.
+            new ZipRange(96, 96, -1, -1), // FM Federated States of Micronesia.
+            new ZipRange(30, 31, -1, -1), // GA Georgia.
+            new ZipRange(96, 96, -1, -1), // GU Guam.
+            new ZipRange(96, 96, -1, -1), // HI Hawaii.
+            new ZipRange(50, 52, -1, -1), // IA Iowa.
+            new ZipRange(83, 83, -1, -1), // ID Idaho.
+            new ZipRange(60, 62, -1, -1), // IL Illinois.
+            new ZipRange(46, 47, -1, -1), // IN Indiana.
+            new ZipRange(66, 67, 73, -1), // KS Kansas.
+            new ZipRange(40, 42, -1, -1), // KY Kentucky.
+            new ZipRange(70, 71, -1, -1), // LA Louisiana.
+            new ZipRange(1, 2, -1, -1), // MA Massachusetts.
+            new ZipRange(20, 21, -1, -1), // MD Maryland.
+            new ZipRange(3, 4, -1, -1), // ME Maine.
+            new ZipRange(96, 96, -1, -1), // MH Marshall Islands.
+            new ZipRange(48, 49, -1, -1), // MI Michigan.
+            new ZipRange(55, 56, -1, -1), // MN Minnesota.
+            new ZipRange(63, 65, -1, -1), // MO Missouri.
+            new ZipRange(96, 96, -1, -1), // MP Northern Mariana Islands.
+            new ZipRange(38, 39, -1, -1), // MS Mississippi.
+            new ZipRange(55, 56, -1, -1), // MT Montana.
+            new ZipRange(27, 28, -1, -1), // NC North Carolina.
+            new ZipRange(58, 58, -1, -1), // ND North Dakota.
+            new ZipRange(68, 69, -1, -1), // NE Nebraska.
+            new ZipRange(3, 4, -1, -1), // NH New Hampshire.
+            new ZipRange(7, 8, -1, -1), // NJ New Jersey.
+            new ZipRange(87, 88, 86, -1), // NM New Mexico.
+            new ZipRange(88, 89, 96, -1), // NV Nevada.
+            new ZipRange(10, 14, 0, 6), // NY New York.
+            new ZipRange(43, 45, -1, -1), // OH Ohio.
+            new ZipRange(73, 74, -1, -1), // OK Oklahoma.
+            new ZipRange(97, 97, -1, -1), // OR Oregon.
+            new ZipRange(15, 19, -1, -1), // PA Pennsylvania.
+            new ZipRange(6, 6, 0, 9), // PR Puerto Rico.
+            new ZipRange(96, 96, -1, -1), // PW Palau.
+            new ZipRange(2, 2, -1, -1), // RI Rhode Island.
+            new ZipRange(29, 29, -1, -1), // SC South Carolina.
+            new ZipRange(57, 57, -1, -1), // SD South Dakota.
+            new ZipRange(37, 38, -1, -1), // TN Tennessee.
+            new ZipRange(75, 79, 87, 88), // TX Texas.
+            new ZipRange(84, 84, -1, -1), // UT Utah.
+            new ZipRange(22, 24, 20, -1), // VA Virginia.
+            new ZipRange(6, 9, -1, -1), // VI Virgin Islands.
+            new ZipRange(5, 5, -1, -1), // VT Vermont.
+            new ZipRange(98, 99, -1, -1), // WA Washington.
+            new ZipRange(53, 54, -1, -1), // WI Wisconsin.
+            new ZipRange(24, 26, -1, -1), // WV West Virginia.
+            new ZipRange(82, 83, -1, -1) // WY Wyoming.
+    };
+
+    // Newlines
+    private static final String NL = "\n\u000B\u000C\r\u0085\u2028\u2029";
+
+    // Space characters
+    private static final String SP = "\u0009\u0020\u00A0\u1680\u2000\u2001"
+            + "\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F"
+            + "\u205F\u3000";
+
+    // Whitespace
+    private static final String WS = SP + NL;
+
+    // Characters that are considered word delimiters.
+    private static final String WORD_DELIM = ",*\u2022" + WS;
+
+    // Lookahead for word end.
+    private static final String WORD_END = "(?=[" + WORD_DELIM + "]|$)";
+
+    // Address words are a sequence of non-delimiter characters.
+    private static final Pattern sWordRe =
+            Pattern.compile("[^" + WORD_DELIM + "]+" + WORD_END, Pattern.CASE_INSENSITIVE);
+
+    // Characters that are considered suffix delimiters for house numbers.
+    private static final String HOUSE_POST_DELIM = ",\"'" + WS;
+
+    // Lookahead for house end.
+    private static final String HOUSE_END = "(?=[" + HOUSE_POST_DELIM + "]|$)";
+
+    // Characters that are considered prefix delimiters for house numbers.
+    private static final String HOUSE_PRE_DELIM = ":" + HOUSE_POST_DELIM;
+
+    // A house number component is "one" or a number, optionally
+    // followed by a single alphabetic character, or
+    private static final String HOUSE_COMPONENT = "(?:one|\\d+([a-z](?=[^a-z]|$)|st|nd|rd|th)?)";
+
+    // House numbers are a repetition of |HOUSE_COMPONENT|, separated by -, and followed by
+    // a delimiter character.
+    private static final Pattern sHouseNumberRe =
+            Pattern.compile(HOUSE_COMPONENT + "(?:-" + HOUSE_COMPONENT + ")*" + HOUSE_END,
+                    Pattern.CASE_INSENSITIVE);
+
+    // XXX: do we want to accept whitespace other than 0x20 in state names?
+    private static final Pattern sStateRe = Pattern.compile("(?:"
+                    + "(ak|alaska)|"
+                    + "(al|alabama)|"
+                    + "(ar|arkansas)|"
+                    + "(as|american[" + SP + "]+samoa)|"
+                    + "(az|arizona)|"
+                    + "(ca|california)|"
+                    + "(co|colorado)|"
+                    + "(ct|connecticut)|"
+                    + "(dc|district[" + SP + "]+of[" + SP + "]+columbia)|"
+                    + "(de|delaware)|"
+                    + "(fl|florida)|"
+                    + "(fm|federated[" + SP + "]+states[" + SP + "]+of[" + SP + "]+micronesia)|"
+                    + "(ga|georgia)|"
+                    + "(gu|guam)|"
+                    + "(hi|hawaii)|"
+                    + "(ia|iowa)|"
+                    + "(id|idaho)|"
+                    + "(il|illinois)|"
+                    + "(in|indiana)|"
+                    + "(ks|kansas)|"
+                    + "(ky|kentucky)|"
+                    + "(la|louisiana)|"
+                    + "(ma|massachusetts)|"
+                    + "(md|maryland)|"
+                    + "(me|maine)|"
+                    + "(mh|marshall[" + SP + "]+islands)|"
+                    + "(mi|michigan)|"
+                    + "(mn|minnesota)|"
+                    + "(mo|missouri)|"
+                    + "(mp|northern[" + SP + "]+mariana[" + SP + "]+islands)|"
+                    + "(ms|mississippi)|"
+                    + "(mt|montana)|"
+                    + "(nc|north[" + SP + "]+carolina)|"
+                    + "(nd|north[" + SP + "]+dakota)|"
+                    + "(ne|nebraska)|"
+                    + "(nh|new[" + SP + "]+hampshire)|"
+                    + "(nj|new[" + SP + "]+jersey)|"
+                    + "(nm|new[" + SP + "]+mexico)|"
+                    + "(nv|nevada)|"
+                    + "(ny|new[" + SP + "]+york)|"
+                    + "(oh|ohio)|"
+                    + "(ok|oklahoma)|"
+                    + "(or|oregon)|"
+                    + "(pa|pennsylvania)|"
+                    + "(pr|puerto[" + SP + "]+rico)|"
+                    + "(pw|palau)|"
+                    + "(ri|rhode[" + SP + "]+island)|"
+                    + "(sc|south[" + SP + "]+carolina)|"
+                    + "(sd|south[" + SP + "]+dakota)|"
+                    + "(tn|tennessee)|"
+                    + "(tx|texas)|"
+                    + "(ut|utah)|"
+                    + "(va|virginia)|"
+                    + "(vi|virgin[" + SP + "]+islands)|"
+                    + "(vt|vermont)|"
+                    + "(wa|washington)|"
+                    + "(wi|wisconsin)|"
+                    + "(wv|west[" + SP + "]+virginia)|"
+                    + "(wy|wyoming)"
+                    + ")" + WORD_END,
+            Pattern.CASE_INSENSITIVE);
+
+    private static final Pattern sLocationNameRe = Pattern.compile("(?:"
+                    + "alley|annex|arcade|ave[.]?|avenue|alameda|bayou|"
+                    + "beach|bend|bluffs?|bottom|boulevard|branch|bridge|"
+                    + "brooks?|burgs?|bypass|broadway|camino|camp|canyon|"
+                    + "cape|causeway|centers?|circles?|cliffs?|club|common|"
+                    + "corners?|course|courts?|coves?|creek|crescent|crest|"
+                    + "crossing|crossroad|curve|circulo|dale|dam|divide|"
+                    + "drives?|estates?|expressway|extensions?|falls?|ferry|"
+                    + "fields?|flats?|fords?|forest|forges?|forks?|fort|"
+                    + "freeway|gardens?|gateway|glens?|greens?|groves?|"
+                    + "harbors?|haven|heights|highway|hills?|hollow|inlet|"
+                    + "islands?|isle|junctions?|keys?|knolls?|lakes?|land|"
+                    + "landing|lane|lights?|loaf|locks?|lodge|loop|mall|"
+                    + "manors?|meadows?|mews|mills?|mission|motorway|mount|"
+                    + "mountains?|neck|orchard|oval|overpass|parks?|"
+                    + "parkways?|pass|passage|path|pike|pines?|plains?|"
+                    + "plaza|points?|ports?|prairie|privada|radial|ramp|"
+                    + "ranch|rapids?|rd[.]?|rest|ridges?|river|roads?|route|"
+                    + "row|rue|run|shoals?|shores?|skyway|springs?|spurs?|"
+                    + "squares?|station|stravenue|stream|st[.]?|streets?|"
+                    + "summit|speedway|terrace|throughway|trace|track|"
+                    + "trafficway|trail|tunnel|turnpike|underpass|unions?|"
+                    + "valleys?|viaduct|views?|villages?|ville|vista|walks?|"
+                    + "wall|ways?|wells?|xing|xrd)" + WORD_END,
+            Pattern.CASE_INSENSITIVE);
+
+    private static final Pattern sSuffixedNumberRe =
+            Pattern.compile("(\\d+)(st|nd|rd|th)", Pattern.CASE_INSENSITIVE);
+
+    private static final Pattern sZipCodeRe =
+            Pattern.compile("(?:\\d{5}(?:-\\d{4})?)" + WORD_END, Pattern.CASE_INSENSITIVE);
+
+    private static boolean checkHouseNumber(String houseNumber) {
+        // Make sure that there are at most 5 digits.
+        int digitCount = 0;
+        for (int i = 0; i < houseNumber.length(); ++i) {
+            if (Character.isDigit(houseNumber.charAt(i))) ++digitCount;
+        }
+        if (digitCount > 5) return false;
+
+        // Make sure that any ordinals are valid.
+        Matcher suffixMatcher = sSuffixedNumberRe.matcher(houseNumber);
+        while (suffixMatcher.find()) {
+            int num = Integer.parseInt(suffixMatcher.group(1));
+            if (num == 0) {
+                return false; // 0th is invalid.
+            }
+            String suffix = suffixMatcher.group(2).toLowerCase(Locale.getDefault());
+            switch (num % 10) {
+                case 1:
+                    return suffix.equals(num % 100 == 11 ? "th" : "st");
+                case 2:
+                    return suffix.equals(num % 100 == 12 ? "th" : "nd");
+                case 3:
+                    return suffix.equals(num % 100 == 13 ? "th" : "rd");
+                default:
+                    return suffix.equals("th");
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Attempt to match a house number beginnning at position offset
+     * in content.  The house number must be followed by a word
+     * delimiter or the end of the string, and if offset is non-zero,
+     * then it must also be preceded by a word delimiter.
+     *
+     * @return a MatchResult if a valid house number was found.
+     */
+    private static MatchResult matchHouseNumber(String content, int offset) {
+        if (offset > 0 && HOUSE_PRE_DELIM.indexOf(content.charAt(offset - 1)) == -1) return null;
+        Matcher matcher = sHouseNumberRe.matcher(content).region(offset, content.length());
+        if (matcher.lookingAt()) {
+            MatchResult matchResult = matcher.toMatchResult();
+            if (checkHouseNumber(matchResult.group(0))) return matchResult;
+        }
+        return null;
+    }
+
+    /**
+     * Attempt to match a US state beginnning at position offset in
+     * content.  The matching state must be followed by a word
+     * delimiter or the end of the string, and if offset is non-zero,
+     * then it must also be preceded by a word delimiter.
+     *
+     * @return a MatchResult if a valid US state (or two letter code)
+     * was found.
+     */
+    private static MatchResult matchState(String content, int offset) {
+        if (offset > 0 && WORD_DELIM.indexOf(content.charAt(offset - 1)) == -1) return null;
+        Matcher stateMatcher = sStateRe.matcher(content).region(offset, content.length());
+        return stateMatcher.lookingAt() ? stateMatcher.toMatchResult() : null;
+    }
+
+    /**
+     * Test whether zipCode matches the U.S. zip code format (ddddd or
+     * ddddd-dddd) and is within the expected range, given that
+     * stateMatch is a match of sStateRe.
+     *
+     * @return true if zipCode is a valid zip code, is legal for the
+     * matched state, and is followed by a word delimiter or the end
+     * of the string.
+     */
+    private static boolean isValidZipCode(String zipCode, MatchResult stateMatch) {
+        if (stateMatch == null) return false;
+        // Work out the index of the state, based on which group matched.
+        int stateIndex = stateMatch.groupCount();
+        while (stateIndex > 0) {
+            if (stateMatch.group(stateIndex--) != null) break;
+        }
+        return sZipCodeRe.matcher(zipCode).matches()
+                && sStateZipCodeRanges[stateIndex].matches(zipCode);
+    }
+
+    /**
+     * Test whether location is one of the valid locations.
+     *
+     * @return true if location starts with a valid location name
+     * followed by a word delimiter or the end of the string.
+     */
+    private static boolean isValidLocationName(String location) {
+        return sLocationNameRe.matcher(location).matches();
+    }
+
+    /**
+     * Attempt to match a complete address in content, starting with
+     * houseNumberMatch.
+     *
+     * @param content The string to search.
+     * @param houseNumberMatch A matching house number to start extending.
+     * @return +ve: the end of the match
+     *         +ve: the position to restart searching for house numbers, negated.
+     */
+    private static int attemptMatch(String content, MatchResult houseNumberMatch) {
+        int restartPos = -1;
+        int nonZipMatch = -1;
+        int it = houseNumberMatch.end();
+        int numLines = 1;
+        boolean consecutiveHouseNumbers = true;
+        boolean foundLocationName = false;
+        int wordCount = 1;
+        String lastWord = "";
+
+        Matcher matcher = sWordRe.matcher(content);
+
+        for (; it < content.length(); lastWord = matcher.group(0), it = matcher.end()) {
+            if (!matcher.find(it)) {
+                // No more words in the input sequence.
+                return -content.length();
+            }
+            if (matcher.end() - matcher.start() > kMaxAddressNameWordLength) {
+                // Word is too long to be part of an address. Fail.
+                return -matcher.end();
+            }
+
+            // Count the number of newlines we just consumed.
+            while (it < matcher.start()) {
+                if (NL.indexOf(content.charAt(it++)) != -1) ++numLines;
+            }
+
+            // Consumed too many lines. Fail.
+            if (numLines > MAX_ADDRESS_LINES) break;
+
+            // Consumed too many words. Fail.
+            if (++wordCount > MAX_ADDRESS_WORDS) break;
+
+            if (matchHouseNumber(content, it) != null) {
+                if (consecutiveHouseNumbers && numLines > 1) {
+                    // Last line ended with a number, and this this line starts with one.
+                    // Restart at this number.
+                    return -it;
+                }
+                // Remember the position of this match as the restart position.
+                if (restartPos == -1) restartPos = it;
+                continue;
+            }
+
+            consecutiveHouseNumbers = false;
+
+            if (isValidLocationName(matcher.group(0))) {
+                foundLocationName = true;
+                continue;
+            }
+
+            if (wordCount == MAX_LOCATION_NAME_DISTANCE && !foundLocationName) {
+                // Didn't find a location name in time. Fail.
+                it = matcher.end();
+                break;
+            }
+
+            if (foundLocationName && wordCount > MIN_ADDRESS_WORDS) {
+                // We can now attempt to match a state.
+                MatchResult stateMatch = matchState(content, it);
+                if (stateMatch != null) {
+                    if (lastWord.equals("et") && stateMatch.group(0).equals("al")) {
+                        // Reject "et al" as a false postitive.
+                        it = stateMatch.end();
+                        break;
+                    }
+
+                    // At this point we've matched a state; try to match a zip code after it.
+                    Matcher zipMatcher = sWordRe.matcher(content);
+                    if (zipMatcher.find(stateMatch.end())
+                            && isValidZipCode(zipMatcher.group(0), stateMatch)) {
+                        return zipMatcher.end();
+                    }
+                    // The content ends with a state but no zip
+                    // code. This is a legal match according to the
+                    // documentation. N.B. This differs from the
+                    // original c++ implementation, which only allowed
+                    // the zip code to be optional at the end of the
+                    // string, which presumably is a bug.  Now we
+                    // prefer to find a match with a zip code, but
+                    // remember non-zip matches and return them if
+                    // necessary.
+                    nonZipMatch = stateMatch.end();
+                }
+            }
+        }
+
+        if (nonZipMatch > 0) return nonZipMatch;
+
+        return -(restartPos > 0 ? restartPos : it);
+    }
+
+    /**
+     * Return the first matching address in content.
+     *
+     * @param content The string to search.
+     * @return The first valid address, or null if no address was matched.
+     */
+    static String findAddress(String content) {
+        Matcher houseNumberMatcher = sHouseNumberRe.matcher(content);
+        int start = 0;
+        while (houseNumberMatcher.find(start)) {
+            if (checkHouseNumber(houseNumberMatcher.group(0))) {
+                start = houseNumberMatcher.start();
+                int end = attemptMatch(content, houseNumberMatcher);
+                if (end > 0) {
+                    return content.substring(start, end);
+                }
+                start = -end;
+            } else {
+                start = houseNumberMatcher.end();
+            }
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 63519a6..03ff0ca 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,7 +34,7 @@
     private final UserInfo mUserInfo;
     private final PackageInfo mPackageInfo;
 
-    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1;
+    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.P;
 
     public UserPackage(UserInfo user, PackageInfo packageInfo) {
         this.mUserInfo = user;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index de5a822..deb94e81 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -129,8 +129,10 @@
  * String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
  * webview.loadData(summary, "text/html", null);
  * // ... although note that there are restrictions on what this HTML can do.
- * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
- * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
+ * // See {@link #loadData(String,String,String)} and {@link
+ * #loadDataWithBaseURL(String,String,String,String,String)} for more info.
+ * // Also see {@link #loadData(String,String,String)} for information on encoding special
+ * // characters.
  * </pre>
  *
  * <p>A WebView has several customization points where you can add your
@@ -989,13 +991,21 @@
      * #loadDataWithBaseURL(String,String,String,String,String)
      * loadDataWithBaseURL()} with an appropriate base URL.
      * <p>
-     * The encoding parameter specifies whether the data is base64 or URL
+     * The {@code encoding} parameter specifies whether the data is base64 or URL
      * encoded. If the data is base64 encoded, the value of the encoding
-     * parameter must be 'base64'. For all other values of the parameter,
-     * including {@code null}, it is assumed that the data uses ASCII encoding for
-     * octets inside the range of safe URL characters and use the standard %xx
-     * hex encoding of URLs for octets outside that range. For example, '#',
-     * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
+     * parameter must be 'base64'. HTML can be encoded with {@link
+     * android.util.Base64#encodeToString(byte[],int)} like so:
+     * <pre>
+     * String unencodedHtml =
+     *     "&lt;html&gt;&lt;body&gt;'%28' is the code for '('&lt;/body&gt;&lt;/html&gt;";
+     * String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING);
+     * webView.loadData(encodedHtml, "text/html", "base64");
+     * </pre>
+     * <p>
+     * For all other values of {@code encoding} (including {@code null}) it is assumed that the
+     * data uses ASCII encoding for octets inside the range of safe URL characters and use the
+     * standard %xx hex encoding of URLs for octets outside that range. See <a
+     * href="https://tools.ietf.org/html/rfc3986#section-2.2">RFC 3986</a> for more information.
      * <p>
      * The {@code mimeType} parameter specifies the format of the data.
      * If WebView can't handle the specified MIME type, it will download the data.
@@ -1821,9 +1831,10 @@
      */
     @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
-        return getFactory().getStatics().findAddress(addr);
+        if (addr == null) {
+            throw new NullPointerException("addr is null");
+        }
+        return FindAddress.findAddress(addr);
     }
 
     /**
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index e3efad0..b3522ec 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -47,7 +47,7 @@
     // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
     /** @hide */
     private static final String CHROMIUM_WEBVIEW_FACTORY =
-            "com.android.webview.chromium.WebViewChromiumFactoryProviderForOMR1";
+            "com.android.webview.chromium.WebViewChromiumFactoryProviderForP";
 
     private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
 
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 56c3e4a..336c20c 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -105,6 +105,11 @@
 
     @Override
     public Editable getText() {
+        CharSequence text = super.getText();
+        if (text instanceof Editable) {
+            return (Editable) super.getText();
+        }
+        super.setText(text, BufferType.EDITABLE);
         return (Editable) super.getText();
     }
 
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index bd48f45..26dfcc2 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -125,7 +125,7 @@
                 mView.getWidth() - mBitmap.getWidth()));
         final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
 
-        if (startX != mPrevStartCoordsInSurface.x || startY != mPrevStartCoordsInSurface.y) {
+        if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) {
             performPixelCopy(startX, startY);
 
             mPrevPosInView.x = xPosInView;
diff --git a/core/java/android/widget/MediaController2.java b/core/java/android/widget/MediaController2.java
new file mode 100644
index 0000000..9035137
--- /dev/null
+++ b/core/java/android/widget/MediaController2.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 android.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.media.session.MediaController;
+import android.media.update.ApiLoader;
+import android.media.update.MediaController2Provider;
+import android.media.update.ViewProvider;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+/**
+ * TODO PUBLIC API
+ * @hide
+ */
+public class MediaController2 extends FrameLayout {
+    private final MediaController2Provider mProvider;
+
+    public MediaController2(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public MediaController2(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public MediaController2(@NonNull Context context, @Nullable AttributeSet attrs,
+                            int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public MediaController2(@NonNull Context context, @Nullable AttributeSet attrs,
+                            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        mProvider = ApiLoader.getProvider(context)
+                .createMediaController2(this, new SuperProvider());
+    }
+
+    public void setController(MediaController controller) {
+        mProvider.setController_impl(controller);
+    }
+
+    public void setAnchorView(View view) {
+        mProvider.setAnchorView_impl(view);
+    }
+
+    public void show() {
+        mProvider.show_impl();
+    }
+
+    public void show(int timeout) {
+        mProvider.show_impl(timeout);
+    }
+
+    public boolean isShowing() {
+        return mProvider.isShowing_impl();
+    }
+
+    public void hide() {
+        mProvider.hide_impl();
+    }
+
+    public void setPrevNextListeners(OnClickListener next, OnClickListener prev) {
+        mProvider.setPrevNextListeners_impl(next, prev);
+    }
+
+    public void showCCButton() {
+        mProvider.showCCButton_impl();
+    }
+
+    public boolean isPlaying() {
+        return mProvider.isPlaying_impl();
+    }
+
+    public int getCurrentPosition() {
+        return mProvider.getCurrentPosition_impl();
+    }
+
+    public int getBufferPercentage() {
+        return mProvider.getBufferPercentage_impl();
+    }
+
+    public boolean canPause() {
+        return mProvider.canPause_impl();
+    }
+
+    public boolean canSeekBackward() {
+        return mProvider.canSeekBackward_impl();
+    }
+
+    public boolean canSeekForward() {
+        return mProvider.canSeekForward_impl();
+    }
+
+    public void showSubtitle() {
+        mProvider.showSubtitle_impl();
+    }
+
+    public void hideSubtitle() {
+        mProvider.hideSubtitle_impl();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        mProvider.onAttachedToWindow_impl();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        mProvider.onDetachedFromWindow_impl();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        mProvider.onLayout_impl(changed, left, top, right, bottom);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        mProvider.draw_impl(canvas);
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return mProvider.getAccessibilityClassName_impl();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return mProvider.onTouchEvent_impl(ev);
+    }
+
+    @Override
+    public boolean onTrackballEvent(MotionEvent ev) {
+        return mProvider.onTrackballEvent_impl(ev);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return mProvider.onKeyDown_impl(keyCode, event);
+    }
+
+    @Override
+    public void onFinishInflate() {
+        mProvider.onFinishInflate_impl();
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return mProvider.dispatchKeyEvent_impl(event);
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        mProvider.setEnabled_impl(enabled);
+    }
+
+    private class SuperProvider implements ViewProvider {
+        @Override
+        public void onAttachedToWindow_impl() {
+            MediaController2.super.onAttachedToWindow();
+        }
+
+        @Override
+        public void onDetachedFromWindow_impl() {
+            MediaController2.super.onDetachedFromWindow();
+        }
+
+        @Override
+        public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
+            MediaController2.super.onLayout(changed, left, top, right, bottom);
+        }
+
+        @Override
+        public void draw_impl(Canvas canvas) {
+            MediaController2.super.draw(canvas);
+        }
+
+        @Override
+        public CharSequence getAccessibilityClassName_impl() {
+            return MediaController2.super.getAccessibilityClassName();
+        }
+
+        @Override
+        public boolean onTouchEvent_impl(MotionEvent ev) {
+            return MediaController2.super.onTouchEvent(ev);
+        }
+
+        @Override
+        public boolean onTrackballEvent_impl(MotionEvent ev) {
+            return MediaController2.super.onTrackballEvent(ev);
+        }
+
+        @Override
+        public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
+            return MediaController2.super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public void onFinishInflate_impl() {
+            MediaController2.super.onFinishInflate();
+        }
+
+        @Override
+        public boolean dispatchKeyEvent_impl(KeyEvent event) {
+            return MediaController2.super.dispatchKeyEvent(event);
+        }
+
+        @Override
+        public void setEnabled_impl(boolean enabled) {
+            MediaController2.super.setEnabled(enabled);
+        }
+    }
+}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1e17f34..ff374fa 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -44,6 +44,7 @@
 import android.content.res.ColorStateList;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
+import android.content.res.ResourceId;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -295,6 +296,7 @@
  * @attr ref android.R.styleable#TextView_imeActionId
  * @attr ref android.R.styleable#TextView_editorExtras
  * @attr ref android.R.styleable#TextView_elegantTextHeight
+ * @attr ref android.R.styleable#TextView_fallbackLineSpacing
  * @attr ref android.R.styleable#TextView_letterSpacing
  * @attr ref android.R.styleable#TextView_fontFeatureSettings
  * @attr ref android.R.styleable#TextView_breakStrategy
@@ -309,7 +311,6 @@
 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
     static final String LOG_TAG = "TextView";
     static final boolean DEBUG_EXTRACT = false;
-    static final boolean DEBUG_AUTOFILL = false;
     private static final float[] TEMP_POSITION = new float[2];
 
     // Enum for the "typeface" XML parameter.
@@ -654,7 +655,7 @@
     // True if internationalized input should be used for numbers and date and time.
     private final boolean mUseInternationalizedInput;
     // True if fallback fonts that end up getting used should be allowed to affect line spacing.
-    /* package */ final boolean mUseFallbackLineSpacing;
+    /* package */ boolean mUseFallbackLineSpacing;
 
     @ViewDebug.ExportedProperty(category = "text")
     private int mGravity = Gravity.TOP | Gravity.START;
@@ -785,9 +786,11 @@
     // mAutoSizeStepGranularityInPx.
     private boolean mHasPresetAutoSizeValues = false;
 
-    // Indicates whether the text was set from resources or dynamically, so it can be used to
+    // Indicates whether the text was set statically or dynamically, so it can be used to
     // sanitize autofill requests.
-    private boolean mTextFromResource = false;
+    private boolean mTextSetFromXmlOrResourceId = false;
+    // Resource id used to set the text - used for autofill purposes.
+    private @StringRes int mTextId = ResourceId.ID_NULL;
 
     /**
      * Kick-start the font cache for the zygote process (to pay the cost of
@@ -926,7 +929,8 @@
 
         int n = a.getIndexCount();
 
-        boolean fromResourceId = false;
+        // Must set id in a temporary variable because it will be reset by setText()
+        boolean textIsSetFromXml = false;
         for (int i = 0; i < n; i++) {
             int attr = a.getIndex(i);
 
@@ -1068,7 +1072,8 @@
                     break;
 
                 case com.android.internal.R.styleable.TextView_text:
-                    fromResourceId = true;
+                    textIsSetFromXml = true;
+                    mTextId = a.getResourceId(attr, ResourceId.ID_NULL);
                     text = a.getText(attr);
                     break;
 
@@ -1460,8 +1465,8 @@
         }
 
         setText(text, bufferType);
-        if (fromResourceId) {
-            mTextFromResource = true;
+        if (textIsSetFromXml) {
+            mTextSetFromXmlOrResourceId = true;
         }
 
         if (hint != null) setHint(hint);
@@ -3250,6 +3255,8 @@
         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
         boolean mHasElegant = false;
         boolean mElegant = false;
+        boolean mHasFallbackLineSpacing = false;
+        boolean mFallbackLineSpacing = false;
         boolean mHasLetterSpacing = false;
         float mLetterSpacing = 0;
         String mFontFeatureSettings = null;
@@ -3274,6 +3281,8 @@
                     + "    mShadowRadius:" + mShadowRadius + "\n"
                     + "    mHasElegant:" + mHasElegant + "\n"
                     + "    mElegant:" + mElegant + "\n"
+                    + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
+                    + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
@@ -3312,6 +3321,8 @@
                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
+                com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
@@ -3402,6 +3413,11 @@
                     attributes.mHasElegant = true;
                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
                     break;
+                case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
+                    attributes.mHasFallbackLineSpacing = true;
+                    attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
+                            attributes.mFallbackLineSpacing);
+                    break;
                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
                     attributes.mHasLetterSpacing = true;
                     attributes.mLetterSpacing =
@@ -3455,6 +3471,10 @@
             setElegantTextHeight(attributes.mElegant);
         }
 
+        if (attributes.mHasFallbackLineSpacing) {
+            setFallbackLineSpacing(attributes.mFallbackLineSpacing);
+        }
+
         if (attributes.mHasLetterSpacing) {
             setLetterSpacing(attributes.mLetterSpacing);
         }
@@ -3736,7 +3756,7 @@
      *
      * @param elegant set the paint's elegant metrics flag.
      *
-     * @see Paint#isElegantTextHeight(boolean)
+     * @see Paint#isElegantTextHeight()
      *
      * @attr ref android.R.styleable#TextView_elegantTextHeight
      */
@@ -3752,6 +3772,43 @@
     }
 
     /**
+     * Set whether to respect the ascent and descent of the fallback fonts that are used in
+     * displaying the text (which is needed to avoid text from consecutive lines running into
+     * each other). If set, fallback fonts that end up getting used can increase the ascent
+     * and descent of the lines that they are used on.
+     * <p/>
+     * It is required to be true if text could be in languages like Burmese or Tibetan where text
+     * is typically much taller or deeper than Latin text.
+     *
+     * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
+     *
+     * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
+     *
+     * @attr ref android.R.styleable#TextView_fallbackLineSpacing
+     */
+    public void setFallbackLineSpacing(boolean enabled) {
+        if (mUseFallbackLineSpacing != enabled) {
+            mUseFallbackLineSpacing = enabled;
+            if (mLayout != null) {
+                nullLayouts();
+                requestLayout();
+                invalidate();
+            }
+        }
+    }
+
+    /**
+     * @return whether fallback line spacing is enabled, {@code true} by default
+     *
+     * @see #setFallbackLineSpacing(boolean)
+     *
+     * @attr ref android.R.styleable#TextView_fallbackLineSpacing
+     */
+    public boolean isFallbackLineSpacing() {
+        return mUseFallbackLineSpacing;
+    }
+
+    /**
      * Get the value of the TextView's elegant height metrics flag. This setting selects font
      * variants that have not been compacted to fit Latin-based vertical
      * metrics, and also increases top and bottom bounds to provide more space.
@@ -5278,7 +5335,7 @@
 
     private void setText(CharSequence text, BufferType type,
                          boolean notifyBefore, int oldlen) {
-        mTextFromResource = false;
+        mTextSetFromXmlOrResourceId = false;
         if (text == null) {
             text = "";
         }
@@ -5516,7 +5573,8 @@
     @android.view.RemotableViewMethod
     public final void setText(@StringRes int resid) {
         setText(getContext().getResources().getText(resid));
-        mTextFromResource = true;
+        mTextSetFromXmlOrResourceId = true;
+        mTextId = resid;
     }
 
     /**
@@ -5543,7 +5601,8 @@
      */
     public final void setText(@StringRes int resid, BufferType type) {
         setText(getContext().getResources().getText(resid), type);
-        mTextFromResource = true;
+        mTextSetFromXmlOrResourceId = true;
+        mTextId = resid;
     }
 
     /**
@@ -9456,7 +9515,7 @@
         }
         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
         if (afm != null) {
-            if (DEBUG_AUTOFILL) {
+            if (android.view.autofill.Helper.sVerbose) {
                 Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + mText);
             }
             afm.notifyValueChanged(TextView.this);
@@ -10234,7 +10293,17 @@
         final boolean isPassword = hasPasswordTransformationMethod()
                 || isPasswordInputType(getInputType());
         if (forAutofill) {
-            structure.setDataIsSensitive(!mTextFromResource);
+            structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
+            if (mTextId != ResourceId.ID_NULL) {
+                try {
+                    structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
+                } catch (Resources.NotFoundException e) {
+                    if (android.view.autofill.Helper.sVerbose) {
+                        Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
+                                + mTextId + ": " + e.getMessage());
+                    }
+                }
+            }
         }
 
         if (!isPassword || forAutofill) {
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index ab118ba..5d4bccc 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -81,7 +81,11 @@
     void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, String historyName,
             int type);
     void noteLongPartialWakelockStart(String name, String historyName, int uid);
+    void noteLongPartialWakelockStartFromSource(String name, String historyName,
+            in WorkSource workSource);
     void noteLongPartialWakelockFinish(String name, String historyName, int uid);
+    void noteLongPartialWakelockFinishFromSource(String name, String historyName,
+            in WorkSource workSource);
 
     void noteVibratorOn(int uid, long durationMillis);
     void noteVibratorOff(int uid);
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 77cfc2fc..96d3baf 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -411,6 +411,9 @@
             mContext.unbindService(mConnection);
             mConnection.destroy();
         }
+        if (mAfterCompute != null) {
+            mAfterCompute.afterCompute();
+        }
         if (DEBUG) {
             Log.d(TAG, "Unbinded Resolver Ranker.");
         }
@@ -573,7 +576,6 @@
             if (DEBUG) {
                 Log.d(TAG, "Has not found valid ResolverRankerService; Skip Prediction");
             }
-            return;
         } else {
             try {
                 mConnectSignal.await(CONNECTION_COST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index efc9c02..79f4c1b 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -153,7 +153,6 @@
     private int mNumActiveServices;
     private int mNumStartedServices;
 
-    private int mNumExcessiveWake;
     private int mNumExcessiveCpu;
 
     private int mNumCachedKill;
@@ -470,9 +469,23 @@
         }
     }
 
-    public void addPss(long pss, long uss, boolean always,
+    public void addPss(long pss, long uss, boolean always, int type, long duration,
             ArrayMap<String, ProcessStateHolder> pkgList) {
         ensureNotDead();
+        switch (type) {
+            case ProcessStats.ADD_PSS_INTERNAL:
+                mStats.mInternalPssCount++;
+                mStats.mInternalPssTime += duration;
+                break;
+            case ProcessStats.ADD_PSS_EXTERNAL:
+                mStats.mExternalPssCount++;
+                mStats.mExternalPssTime += duration;
+                break;
+            case ProcessStats.ADD_PSS_EXTERNAL_SLOW:
+                mStats.mExternalSlowPssCount++;
+                mStats.mExternalSlowPssTime += duration;
+                break;
+        }
         if (!always) {
             if (mLastPssState == mCurState && SystemClock.uptimeMillis()
                     < (mLastPssTime+(30*1000))) {
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 96ba2b0..35b2dd2 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -134,6 +134,10 @@
     public static final int FLAG_SHUTDOWN = 1<<1;
     public static final int FLAG_SYSPROPS = 1<<2;
 
+    public static final int ADD_PSS_INTERNAL = 0;
+    public static final int ADD_PSS_EXTERNAL = 1;
+    public static final int ADD_PSS_EXTERNAL_SLOW = 2;
+
     public static final int[] ALL_MEM_ADJ = new int[] { ADJ_MEM_FACTOR_NORMAL,
             ADJ_MEM_FACTOR_MODERATE, ADJ_MEM_FACTOR_LOW, ADJ_MEM_FACTOR_CRITICAL };
 
@@ -158,7 +162,7 @@
     };
 
     // Current version of the parcel format.
-    private static final int PARCEL_VERSION = 23;
+    private static final int PARCEL_VERSION = 24;
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
     private static final int MAGIC = 0x50535454;
 
@@ -183,6 +187,18 @@
 
     boolean mHasSwappedOutPss;
 
+    // Count and total time expended doing "quick" pss computations for internal use.
+    public long mInternalPssCount;
+    public long mInternalPssTime;
+
+    // Count and total time expended doing "quick" pss computations due to external requests.
+    public long mExternalPssCount;
+    public long mExternalPssTime;
+
+    // Count and total time expended doing full/slow pss computations due to external requests.
+    public long mExternalSlowPssCount;
+    public long mExternalSlowPssTime;
+
     public final SparseMappingTable mTableData = new SparseMappingTable();
 
     public final long[] mSysMemUsageArgs = new long[SYS_MEM_USAGE_COUNT];
@@ -302,6 +318,13 @@
         mTimePeriodEndRealtime += other.mTimePeriodEndRealtime - other.mTimePeriodStartRealtime;
         mTimePeriodEndUptime += other.mTimePeriodEndUptime - other.mTimePeriodStartUptime;
 
+        mInternalPssCount += other.mInternalPssCount;
+        mInternalPssTime += other.mInternalPssTime;
+        mExternalPssCount += other.mExternalPssCount;
+        mExternalPssTime += other.mExternalPssTime;
+        mExternalSlowPssCount += other.mExternalSlowPssCount;
+        mExternalSlowPssTime += other.mExternalSlowPssTime;
+
         mHasSwappedOutPss |= other.mHasSwappedOutPss;
     }
 
@@ -500,6 +523,12 @@
         buildTimePeriodStartClockStr();
         mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
         mTimePeriodStartUptime = mTimePeriodEndUptime = SystemClock.uptimeMillis();
+        mInternalPssCount = 0;
+        mInternalPssTime = 0;
+        mExternalPssCount = 0;
+        mExternalPssTime = 0;
+        mExternalSlowPssCount = 0;
+        mExternalSlowPssTime = 0;
         mTableData.reset();
         Arrays.fill(mMemFactorDurations, 0);
         mSysMemUsage.resetTable();
@@ -760,6 +789,12 @@
         out.writeLong(mTimePeriodEndRealtime);
         out.writeLong(mTimePeriodStartUptime);
         out.writeLong(mTimePeriodEndUptime);
+        out.writeLong(mInternalPssCount);
+        out.writeLong(mInternalPssTime);
+        out.writeLong(mExternalPssCount);
+        out.writeLong(mExternalPssTime);
+        out.writeLong(mExternalSlowPssCount);
+        out.writeLong(mExternalSlowPssTime);
         out.writeString(mRuntime);
         out.writeInt(mHasSwappedOutPss ? 1 : 0);
         out.writeInt(mFlags);
@@ -928,6 +963,12 @@
         mTimePeriodEndRealtime = in.readLong();
         mTimePeriodStartUptime = in.readLong();
         mTimePeriodEndUptime = in.readLong();
+        mInternalPssCount = in.readLong();
+        mInternalPssTime = in.readLong();
+        mExternalPssCount = in.readLong();
+        mExternalPssTime = in.readLong();
+        mExternalSlowPssCount = in.readLong();
+        mExternalSlowPssTime = in.readLong();
         mRuntime = in.readString();
         mHasSwappedOutPss = in.readInt() != 0;
         mFlags = in.readInt();
@@ -1484,9 +1525,31 @@
                 totalMem.processStateWeight[STATE_SERVICE_RESTARTING], totalMem.totalTime, totalPss,
                 totalMem.processStateSamples[STATE_SERVICE_RESTARTING]);
         pw.println();
+        pw.println("PSS collection stats:");
+        pw.print("  Internal: ");
+        pw.print(mInternalPssCount);
+        pw.print("x over ");
+        TimeUtils.formatDuration(mInternalPssTime, pw);
+        pw.println();
+        pw.print("  External: ");
+        pw.print(mExternalPssCount);
+        pw.print("x over ");
+        TimeUtils.formatDuration(mExternalPssTime, pw);
+        pw.println();
+        pw.print("  External Slow: ");
+        pw.print(mExternalSlowPssCount);
+        pw.print("x over ");
+        TimeUtils.formatDuration(mExternalSlowPssTime, pw);
+        pw.println();
+        pw.println();
         pw.print("          Start time: ");
         pw.print(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimePeriodStartClock));
         pw.println();
+        pw.print("        Total uptime: ");
+        TimeUtils.formatDuration(
+                (mRunning ? SystemClock.uptimeMillis() : mTimePeriodEndUptime)
+                        - mTimePeriodStartUptime, pw);
+        pw.println();
         pw.print("  Total elapsed time: ");
         TimeUtils.formatDuration(
                 (mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime)
diff --git a/core/java/com/android/internal/colorextraction/types/Tonal.java b/core/java/com/android/internal/colorextraction/types/Tonal.java
index 71baaf1..9b7383f 100644
--- a/core/java/com/android/internal/colorextraction/types/Tonal.java
+++ b/core/java/com/android/internal/colorextraction/types/Tonal.java
@@ -53,10 +53,8 @@
 
     public static final int THRESHOLD_COLOR_LIGHT = 0xffe0e0e0;
     public static final int MAIN_COLOR_LIGHT = 0xffe0e0e0;
-    public static final int SECONDARY_COLOR_LIGHT = 0xff9e9e9e;
     public static final int THRESHOLD_COLOR_DARK = 0xff212121;
     public static final int MAIN_COLOR_DARK = 0xff000000;
-    public static final int SECONDARY_COLOR_DARK = 0xff000000;
 
     private final TonalPalette mGreyPalette;
     private final ArrayList<TonalPalette> mTonalPalettes;
@@ -211,10 +209,8 @@
         }
 
         // Normal colors:
-        // best fit + a 2 colors offset
         outColorsNormal.setMainColor(mainColor);
-        int secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
-        outColorsNormal.setSecondaryColor(getColorInt(secondaryIndex, h, s, l));
+        outColorsNormal.setSecondaryColor(mainColor);
 
         // Dark colors:
         // Stops at 4th color, only lighter if dark text is supported
@@ -225,9 +221,9 @@
         } else {
             primaryIndex = Math.min(fitIndex, 3);
         }
-        secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
-        outColorsDark.setMainColor(getColorInt(primaryIndex, h, s, l));
-        outColorsDark.setSecondaryColor(getColorInt(secondaryIndex, h, s, l));
+        mainColor = getColorInt(primaryIndex, h, s, l);
+        outColorsDark.setMainColor(mainColor);
+        outColorsDark.setSecondaryColor(mainColor);
 
         // Extra Dark:
         // Stay close to dark colors until dark text is supported
@@ -238,9 +234,9 @@
         } else {
             primaryIndex = 2;
         }
-        secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
-        outColorsExtraDark.setMainColor(getColorInt(primaryIndex, h, s, l));
-        outColorsExtraDark.setSecondaryColor(getColorInt(secondaryIndex, h, s, l));
+        mainColor = getColorInt(primaryIndex, h, s, l);
+        outColorsExtraDark.setMainColor(mainColor);
+        outColorsExtraDark.setSecondaryColor(mainColor);
 
         outColorsNormal.setSupportsDarkText(supportsDarkText);
         outColorsDark.setSupportsDarkText(supportsDarkText);
@@ -273,11 +269,10 @@
         boolean light = inWallpaperColors != null
                 && (inWallpaperColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT)
                 != 0;
-        int innerColor = light ? MAIN_COLOR_LIGHT : MAIN_COLOR_DARK;
-        int outerColor = light ? SECONDARY_COLOR_LIGHT : SECONDARY_COLOR_DARK;
+        final int color = light ? MAIN_COLOR_LIGHT : MAIN_COLOR_DARK;
 
-        outGradientColors.setMainColor(innerColor);
-        outGradientColors.setSecondaryColor(outerColor);
+        outGradientColors.setMainColor(color);
+        outGradientColors.setSecondaryColor(color);
         outGradientColors.setSupportsDarkText(light);
     }
 
diff --git a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
index 7e88369..ee01a23 100644
--- a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
+++ b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
@@ -22,5 +22,6 @@
 interface INetworkWatchlistManager {
     boolean startWatchlistLogging();
     boolean stopWatchlistLogging();
+    void reloadWatchlist();
     void reportWatchlistIfNecessary();
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9ab16d8..b8ff9e4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -21,10 +21,13 @@
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.UidTraffic;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.NetworkStats;
+import android.net.Uri;
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.wifi.WifiManager;
 import android.os.BatteryManager;
@@ -45,6 +48,8 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
+import android.provider.Settings;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
@@ -53,6 +58,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.IntArray;
+import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.LogWriter;
 import android.util.LongSparseArray;
@@ -79,6 +85,7 @@
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
 
+import java.util.List;
 import libcore.util.EmptyArray;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -122,7 +129,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 172 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 173 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -287,12 +294,21 @@
     /**
      * Update per-freq cpu times for all the uids in {@link #mPendingUids}.
      */
-    public void updateProcStateCpuTimes() {
+    public void updateProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
         final SparseIntArray uidStates;
         synchronized (BatteryStatsImpl.this) {
+            if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
+                return;
+            }
             if(!initKernelSingleUidTimeReaderLocked()) {
                 return;
             }
+            // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
+            // compute deltas since it might result in mis-attributing cpu times to wrong states.
+            if (mKernelSingleUidTimeReader.hasStaleData()) {
+                mPendingUids.clear();
+                return;
+            }
 
             if (mPendingUids.size() == 0) {
                 return;
@@ -305,7 +321,6 @@
             final int procState = uidStates.valueAt(i);
             final int[] isolatedUids;
             final Uid u;
-            final boolean onBattery;
             synchronized (BatteryStatsImpl.this) {
                 // It's possible that uid no longer exists and any internal references have
                 // already been deleted, so using {@link #getAvailableUidStatsLocked} to avoid
@@ -322,7 +337,6 @@
                         isolatedUids[j] = u.mChildUids.get(j);
                     }
                 }
-                onBattery = mOnBatteryInternal;
             }
             long[] cpuTimesMs = mKernelSingleUidTimeReader.readDeltaMs(uid);
             if (isolatedUids != null) {
@@ -333,27 +347,45 @@
             }
             if (onBattery && cpuTimesMs != null) {
                 synchronized (BatteryStatsImpl.this) {
-                    u.addProcStateTimesMs(procState, cpuTimesMs);
-                    u.addProcStateScreenOffTimesMs(procState, cpuTimesMs);
+                    u.addProcStateTimesMs(procState, cpuTimesMs, onBattery);
+                    u.addProcStateScreenOffTimesMs(procState, cpuTimesMs, onBatteryScreenOff);
                 }
             }
         }
     }
 
+    public void copyFromAllUidsCpuTimes() {
+        synchronized (BatteryStatsImpl.this) {
+            copyFromAllUidsCpuTimes(
+                    mOnBatteryTimeBase.isRunning(), mOnBatteryScreenOffTimeBase.isRunning());
+        }
+    }
+
     /**
      * When the battery/screen state changes, we don't attribute the cpu times to any process
      * but we still need to snapshots of all uids to get correct deltas later on. Since we
      * already read this data for updating per-freq cpu times, we can use the same data for
      * per-procstate cpu times.
      */
-    public void copyFromAllUidsCpuTimes() {
+    public void copyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
         synchronized (BatteryStatsImpl.this) {
+            if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
+                return;
+            }
             if(!initKernelSingleUidTimeReaderLocked()) {
                 return;
             }
 
             final SparseArray<long[]> allUidCpuFreqTimesMs =
                     mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
+            // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
+            // compute deltas since it might result in mis-attributing cpu times to wrong states.
+            if (mKernelSingleUidTimeReader.hasStaleData()) {
+                mKernelSingleUidTimeReader.setAllUidsCpuTimesMs(allUidCpuFreqTimesMs);
+                mKernelSingleUidTimeReader.markDataAsStale(false);
+                mPendingUids.clear();
+                return;
+            }
             for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
                 final int uid = allUidCpuFreqTimesMs.keyAt(i);
                 final Uid u = getAvailableUidStatsLocked(mapUid(uid));
@@ -366,7 +398,7 @@
                 }
                 final long[] deltaTimesMs = mKernelSingleUidTimeReader.computeDelta(
                         uid, cpuTimesMs.clone());
-                if (mOnBatteryInternal && deltaTimesMs != null) {
+                if (onBattery && deltaTimesMs != null) {
                     final int procState;
                     final int idx = mPendingUids.indexOfKey(uid);
                     if (idx >= 0) {
@@ -376,8 +408,8 @@
                         procState = u.mProcessState;
                     }
                     if (procState >= 0 && procState < Uid.NUM_PROCESS_STATE) {
-                        u.addProcStateTimesMs(procState, deltaTimesMs);
-                        u.addProcStateScreenOffTimesMs(procState, deltaTimesMs);
+                        u.addProcStateTimesMs(procState, deltaTimesMs, onBattery);
+                        u.addProcStateScreenOffTimesMs(procState, deltaTimesMs, onBatteryScreenOff);
                     }
                 }
             }
@@ -441,8 +473,9 @@
 
         Future<?> scheduleSync(String reason, int flags);
         Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
-        Future<?> scheduleReadProcStateCpuTimes();
-        Future<?> scheduleCopyFromAllUidsCpuTimes();
+        Future<?> scheduleReadProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff);
+        Future<?> scheduleCopyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff);
+        Future<?> scheduleCpuSyncDueToSettingChange();
     }
 
     public Handler mHandler;
@@ -504,8 +537,8 @@
     final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
 
     long mHistoryBaseTime;
-    boolean mHaveBatteryLevel = false;
-    boolean mRecordingHistory = false;
+    protected boolean mHaveBatteryLevel = false;
+    protected boolean mRecordingHistory = false;
     int mNumHistoryItems;
 
     final Parcel mHistoryBuffer = Parcel.obtain();
@@ -650,6 +683,14 @@
             new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
 
     /**
+     * The WiFi Overall wakelock timer
+     * This timer tracks the actual aggregate time for which MC wakelocks are enabled
+     * since addition of per UID timers would not result in an accurate value due to overlapp of
+     * per uid wakelock timers
+     */
+    StopwatchTimer mWifiMulticastWakelockTimer;
+
+    /**
      * The WiFi controller activity (time in tx, rx, idle, and power consumed) for the device.
      */
     ControllerActivityCounterImpl mWifiActivity;
@@ -797,6 +838,9 @@
     @VisibleForTesting
     protected PowerProfile mPowerProfile;
 
+    @GuardedBy("this")
+    private final Constants mConstants;
+
     /*
      * Holds a SamplingTimer associated with each Resource Power Manager state and voter,
      * recording their times when on-battery (regardless of screen state).
@@ -885,6 +929,7 @@
         mHandler = null;
         mPlatformIdleStateCallback = null;
         mUserInfoProvider = null;
+        mConstants = new Constants(mHandler);
         clearHistoryLocked();
     }
 
@@ -1221,12 +1266,10 @@
         public long[] mCounts;
         public long[] mLoadedCounts;
         public long[] mUnpluggedCounts;
-        public long[] mPluggedCounts;
 
         private LongSamplingCounterArray(TimeBase timeBase, Parcel in) {
             mTimeBase = timeBase;
-            mPluggedCounts = in.createLongArray();
-            mCounts = copyArray(mPluggedCounts, mCounts);
+            mCounts = in.createLongArray();
             mLoadedCounts = in.createLongArray();
             mUnpluggedCounts = in.createLongArray();
             timeBase.add(this);
@@ -1245,17 +1288,16 @@
 
         @Override
         public void onTimeStarted(long elapsedRealTime, long baseUptime, long baseRealtime) {
-            mUnpluggedCounts = copyArray(mPluggedCounts, mUnpluggedCounts);
+            mUnpluggedCounts = copyArray(mCounts, mUnpluggedCounts);
         }
 
         @Override
         public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
-            mPluggedCounts = copyArray(mCounts, mPluggedCounts);
         }
 
         @Override
         public long[] getCountsLocked(int which) {
-            long[] val = copyArray(mTimeBase.isRunning() ? mCounts : mPluggedCounts, null);
+            long[] val = copyArray(mCounts, null);
             if (which == STATS_SINCE_UNPLUGGED) {
                 subtract(val, mUnpluggedCounts);
             } else if (which != STATS_SINCE_CHARGED) {
@@ -1268,15 +1310,18 @@
         public void logState(Printer pw, String prefix) {
             pw.println(prefix + "mCounts=" + Arrays.toString(mCounts)
                     + " mLoadedCounts=" + Arrays.toString(mLoadedCounts)
-                    + " mUnpluggedCounts=" + Arrays.toString(mUnpluggedCounts)
-                    + " mPluggedCounts=" + Arrays.toString(mPluggedCounts));
+                    + " mUnpluggedCounts=" + Arrays.toString(mUnpluggedCounts));
         }
 
         public void addCountLocked(long[] counts) {
+            addCountLocked(counts, mTimeBase.isRunning());
+        }
+
+        public void addCountLocked(long[] counts, boolean isRunning) {
             if (counts == null) {
                 return;
             }
-            if (mTimeBase.isRunning()) {
+            if (isRunning) {
                 if (mCounts == null) {
                     mCounts = new long[counts.length];
                 }
@@ -1296,7 +1341,6 @@
         public void reset(boolean detachIfReset) {
             fillArray(mCounts, 0);
             fillArray(mLoadedCounts, 0);
-            fillArray(mPluggedCounts, 0);
             fillArray(mUnpluggedCounts, 0);
             if (detachIfReset) {
                 detach();
@@ -1315,7 +1359,6 @@
             mCounts = in.createLongArray();
             mLoadedCounts = copyArray(mCounts, mLoadedCounts);
             mUnpluggedCounts = copyArray(mCounts, mUnpluggedCounts);
-            mPluggedCounts = copyArray(mCounts, mPluggedCounts);
         }
 
         public static void writeToParcel(Parcel out, LongSamplingCounterArray counterArray) {
@@ -3773,7 +3816,8 @@
                         + " and battery is " + (unplugged ? "on" : "off"));
             }
             updateCpuTimeLocked();
-            mExternalSync.scheduleCopyFromAllUidsCpuTimes();
+            mExternalSync.scheduleCopyFromAllUidsCpuTimes(mOnBatteryTimeBase.isRunning(),
+                    mOnBatteryScreenOffTimeBase.isRunning());
 
             mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
             if (updateOnBatteryTimeBase) {
@@ -3973,30 +4017,88 @@
         addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_FINISH, name, uid);
     }
 
-    public void noteAlarmStartLocked(String name, int uid) {
-        if (!mRecordAllHistory) {
-            return;
-        }
-        uid = mapUid(uid);
-        final long elapsedRealtime = mClocks.elapsedRealtime();
-        final long uptime = mClocks.uptimeMillis();
-        if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_START, name, uid, 0)) {
-            return;
-        }
-        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_START, name, uid);
+    public void noteAlarmStartLocked(String name, WorkSource workSource, int uid) {
+        noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_START, name, workSource, uid);
     }
 
-    public void noteAlarmFinishLocked(String name, int uid) {
+    public void noteAlarmFinishLocked(String name, WorkSource workSource, int uid) {
+        noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_FINISH, name, workSource, uid);
+    }
+
+    private void noteAlarmStartOrFinishLocked(int historyItem, String name, WorkSource workSource,
+            int uid) {
         if (!mRecordAllHistory) {
             return;
         }
-        uid = mapUid(uid);
+
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
-        if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_FINISH, name, uid, 0)) {
-            return;
+
+        if (workSource != null) {
+            for (int i = 0; i < workSource.size(); ++i) {
+                uid = mapUid(workSource.get(i));
+                if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
+                    addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid);
+                }
+            }
+
+            List<WorkChain> workChains = workSource.getWorkChains();
+            if (workChains != null) {
+                for (int i = 0; i < workChains.size(); ++i) {
+                    uid = mapUid(workChains.get(i).getAttributionUid());
+                    if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
+                        addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid);
+                    }
+                }
+            }
+        } else {
+            uid = mapUid(uid);
+
+            if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
+                addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid);
+            }
         }
-        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_FINISH, name, uid);
+    }
+
+    public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource,
+            String tag) {
+
+        if (workSource != null) {
+            for (int i = 0; i < workSource.size(); ++i) {
+                uid = workSource.get(i);
+                final String workSourceName = workSource.getName(i);
+
+                if (isOnBattery()) {
+                    BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid,
+                            workSourceName != null ? workSourceName : packageName);
+                    pkg.noteWakeupAlarmLocked(tag);
+                }
+
+                StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+            }
+
+            ArrayList<WorkChain> workChains = workSource.getWorkChains();
+            if (workChains != null) {
+                for (int i = 0; i < workChains.size(); ++i) {
+                    final WorkChain wc = workChains.get(i);
+                    uid = wc.getAttributionUid();
+
+                    if (isOnBattery()) {
+                        BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
+                        pkg.noteWakeupAlarmLocked(tag);
+                    }
+
+                    // TODO(statsd): Log the full attribution chain here once it's available
+                    StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+                }
+            }
+        } else {
+            if (isOnBattery()) {
+                BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
+                pkg.noteWakeupAlarmLocked(tag);
+            }
+            StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+        }
     }
 
     private void requestWakelockCpuUpdate() {
@@ -4064,8 +4166,8 @@
     private String mInitialAcquireWakeName;
     private int mInitialAcquireWakeUid = -1;
 
-    public void noteStartWakeLocked(int uid, int pid, String name, String historyName, int type,
-            boolean unimportantForLogging, long elapsedRealtime, long uptime) {
+    public void noteStartWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
+        int type, boolean unimportantForLogging, long elapsedRealtime, long uptime) {
         uid = mapUid(uid);
         if (type == WAKE_TYPE_PARTIAL) {
             // Only care about partial wake locks, since full wake locks
@@ -4113,12 +4215,18 @@
                 }
                 requestWakelockCpuUpdate();
             }
+
             getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime);
+
+            if (wc != null) {
+                StatsLog.write(
+                        StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 1);
+            }
         }
     }
 
-    public void noteStopWakeLocked(int uid, int pid, String name, String historyName, int type,
-            long elapsedRealtime, long uptime) {
+    public void noteStopWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
+            int type, long elapsedRealtime, long uptime) {
         uid = mapUid(uid);
         if (type == WAKE_TYPE_PARTIAL) {
             mWakeLockNesting--;
@@ -4148,7 +4256,12 @@
                 }
                 requestWakelockCpuUpdate();
             }
+
             getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime);
+            if (wc != null) {
+                StatsLog.write(
+                        StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 0);
+            }
         }
     }
 
@@ -4158,8 +4271,17 @@
         final long uptime = mClocks.uptimeMillis();
         final int N = ws.size();
         for (int i=0; i<N; i++) {
-            noteStartWakeLocked(ws.get(i), pid, name, historyName, type, unimportantForLogging,
-                    elapsedRealtime, uptime);
+            noteStartWakeLocked(ws.get(i), pid, null, name, historyName, type,
+                    unimportantForLogging, elapsedRealtime, uptime);
+        }
+
+        List<WorkChain> wcs = ws.getWorkChains();
+        if (wcs != null) {
+            for (int i = 0; i < wcs.size(); ++i) {
+                final WorkChain wc = wcs.get(i);
+                noteStartWakeLocked(wc.getAttributionUid(), pid, wc, name, historyName, type,
+                        unimportantForLogging, elapsedRealtime, uptime);
+            }
         }
     }
 
@@ -4168,17 +4290,46 @@
             String newHistoryName, int newType, boolean newUnimportantForLogging) {
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
+
+        List<WorkChain>[] wcs = WorkSource.diffChains(ws, newWs);
+
         // For correct semantics, we start the need worksources first, so that we won't
         // make inappropriate history items as if all wake locks went away and new ones
         // appeared.  This is okay because tracking of wake locks allows nesting.
+        //
+        // First the starts :
         final int NN = newWs.size();
         for (int i=0; i<NN; i++) {
-            noteStartWakeLocked(newWs.get(i), newPid, newName, newHistoryName, newType,
+            noteStartWakeLocked(newWs.get(i), newPid, null, newName, newHistoryName, newType,
                     newUnimportantForLogging, elapsedRealtime, uptime);
         }
+        if (wcs != null) {
+            List<WorkChain> newChains = wcs[0];
+            if (newChains != null) {
+                for (int i = 0; i < newChains.size(); ++i) {
+                    final WorkChain newChain = newChains.get(i);
+                    noteStartWakeLocked(newChain.getAttributionUid(), newPid, newChain, newName,
+                        newHistoryName, newType, newUnimportantForLogging, elapsedRealtime,
+                        uptime);
+                }
+            }
+        }
+
+        // Then the stops :
         final int NO = ws.size();
         for (int i=0; i<NO; i++) {
-            noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime);
+            noteStopWakeLocked(ws.get(i), pid, null, name, historyName, type, elapsedRealtime,
+                    uptime);
+        }
+        if (wcs != null) {
+            List<WorkChain> goneChains = wcs[1];
+            if (goneChains != null) {
+                for (int i = 0; i < goneChains.size(); ++i) {
+                    final WorkChain goneChain = goneChains.get(i);
+                    noteStopWakeLocked(goneChain.getAttributionUid(), pid, goneChain, name,
+                            historyName, type, elapsedRealtime, uptime);
+                }
+            }
         }
     }
 
@@ -4188,12 +4339,50 @@
         final long uptime = mClocks.uptimeMillis();
         final int N = ws.size();
         for (int i=0; i<N; i++) {
-            noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime);
+            noteStopWakeLocked(ws.get(i), pid, null, name, historyName, type, elapsedRealtime,
+                    uptime);
+        }
+
+        List<WorkChain> wcs = ws.getWorkChains();
+        if (wcs != null) {
+            for (int i = 0; i < wcs.size(); ++i) {
+                final WorkChain wc = wcs.get(i);
+                noteStopWakeLocked(wc.getAttributionUid(), pid, wc, name, historyName, type,
+                        elapsedRealtime, uptime);
+            }
         }
     }
 
     public void noteLongPartialWakelockStart(String name, String historyName, int uid) {
         uid = mapUid(uid);
+        noteLongPartialWakeLockStartInternal(name, historyName, uid);
+        StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 1);
+    }
+
+    public void noteLongPartialWakelockStartFromSource(String name, String historyName,
+            WorkSource workSource) {
+        final int N = workSource.size();
+        for (int i = 0; i < N; ++i) {
+            final int uid = mapUid(workSource.get(i));
+            noteLongPartialWakeLockStartInternal(name, historyName, uid);
+            StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 1);
+        }
+
+        final ArrayList<WorkChain> workChains = workSource.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                final WorkChain workChain = workChains.get(i);
+                final int uid = workChain.getAttributionUid();
+                noteLongPartialWakeLockStartInternal(name, historyName, uid);
+
+                // TODO(statsd): the Log WorkChain to statds, and not just the uid.
+                StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName,
+                        1);
+            }
+        }
+    }
+
+    private void noteLongPartialWakeLockStartInternal(String name, String historyName, int uid) {
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         if (historyName == null) {
@@ -4205,11 +4394,39 @@
         }
         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) {
         uid = mapUid(uid);
+        noteLongPartialWakeLockFinishInternal(name, historyName, uid);
+        StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 0);
+    }
+
+    public void noteLongPartialWakelockFinishFromSource(String name, String historyName,
+            WorkSource workSource) {
+        final int N = workSource.size();
+        for (int i = 0; i < N; ++i) {
+            final int uid = mapUid(workSource.get(i));
+            noteLongPartialWakeLockFinishInternal(name, historyName, uid);
+            StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 0);
+        }
+
+        final ArrayList<WorkChain> workChains = workSource.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                final WorkChain workChain = workChains.get(i);
+                final int uid = workChain.getAttributionUid();
+
+                noteLongPartialWakeLockFinishInternal(name, historyName, uid);
+
+                // TODO(statsd): the Log WorkChain to statds, and not just the uid.
+                StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName,
+                        0);
+            }
+        }
+    }
+
+    private void noteLongPartialWakeLockFinishInternal(String name, String historyName, int uid) {
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         if (historyName == null) {
@@ -4221,7 +4438,6 @@
         }
         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) {
@@ -4432,10 +4648,10 @@
                 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,
+                noteStartWakeLocked(-1, -1, null, "screen", null, WAKE_TYPE_PARTIAL, false,
                         elapsedRealtime, uptime);
             } else if (isScreenOn(oldState)) {
-                noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL,
+                noteStopWakeLocked(-1, -1, null, "screen", "screen", WAKE_TYPE_PARTIAL,
                         elapsedRealtime, uptime);
                 updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
                         mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
@@ -5158,8 +5374,9 @@
         }
     }
 
-    private void noteBluetoothScanStartedLocked(int uid, boolean isUnoptimized) {
-        uid = mapUid(uid);
+    private void noteBluetoothScanStartedLocked(WorkChain workChain, int uid,
+            boolean isUnoptimized) {
+        uid = getAttributionUid(uid, workChain);
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         if (mBluetoothScanNesting == 0) {
@@ -5170,18 +5387,36 @@
             mBluetoothScanTimer.startRunningLocked(elapsedRealtime);
         }
         mBluetoothScanNesting++;
+
+        // TODO(statsd): Log WorkChain here if it's non-null.
+        StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uid, 1);
+        if (isUnoptimized) {
+            StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, 1);
+        }
+
         getUidStatsLocked(uid).noteBluetoothScanStartedLocked(elapsedRealtime, isUnoptimized);
+        if (workChain != null) {
+            getUidStatsLocked(uid).addBluetoothWorkChain(workChain, isUnoptimized);
+        }
     }
 
     public void noteBluetoothScanStartedFromSourceLocked(WorkSource ws, boolean isUnoptimized) {
         final int N = ws.size();
         for (int i = 0; i < N; i++) {
-            noteBluetoothScanStartedLocked(ws.get(i), isUnoptimized);
+            noteBluetoothScanStartedLocked(null, ws.get(i), isUnoptimized);
+        }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                noteBluetoothScanStartedLocked(workChains.get(i), -1, isUnoptimized);
+            }
         }
     }
 
-    private void noteBluetoothScanStoppedLocked(int uid, boolean isUnoptimized) {
-        uid = mapUid(uid);
+    private void noteBluetoothScanStoppedLocked(WorkChain workChain, int uid,
+            boolean isUnoptimized) {
+        uid = getAttributionUid(uid, workChain);
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         mBluetoothScanNesting--;
@@ -5192,13 +5427,38 @@
             addHistoryRecordLocked(elapsedRealtime, uptime);
             mBluetoothScanTimer.stopRunningLocked(elapsedRealtime);
         }
+
+        // TODO(statsd): Log WorkChain here if it's non-null.
+        StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uid, 0);
+        if (isUnoptimized) {
+            StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, 0);
+        }
+
         getUidStatsLocked(uid).noteBluetoothScanStoppedLocked(elapsedRealtime, isUnoptimized);
+        if (workChain != null) {
+            getUidStatsLocked(uid).removeBluetoothWorkChain(workChain, isUnoptimized);
+        }
+    }
+
+    private int getAttributionUid(int uid, WorkChain workChain) {
+        if (workChain != null) {
+            return mapUid(workChain.getAttributionUid());
+        }
+
+        return mapUid(uid);
     }
 
     public void noteBluetoothScanStoppedFromSourceLocked(WorkSource ws, boolean isUnoptimized) {
         final int N = ws.size();
         for (int i = 0; i < N; i++) {
-            noteBluetoothScanStoppedLocked(ws.get(i), isUnoptimized);
+            noteBluetoothScanStoppedLocked(null, ws.get(i), isUnoptimized);
+        }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                noteBluetoothScanStoppedLocked(workChains.get(i), -1, isUnoptimized);
+            }
         }
     }
 
@@ -5212,9 +5472,35 @@
                     + Integer.toHexString(mHistoryCur.states2));
             addHistoryRecordLocked(elapsedRealtime, uptime);
             mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtime);
+
+
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
                 uid.noteResetBluetoothScanLocked(elapsedRealtime);
+
+                StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uid.getUid(), 0);
+
+                List<WorkChain> allWorkChains = uid.getAllBluetoothWorkChains();
+                if (allWorkChains != null) {
+                    for (int j = 0; j < allWorkChains.size(); ++j) {
+                        // TODO(statsd) : Log the entire workchain here.
+                        StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED,
+                                allWorkChains.get(j).getAttributionUid(), 0);
+                    }
+
+                    allWorkChains.clear();
+                }
+
+                List<WorkChain> unoptimizedWorkChains = uid.getUnoptimizedBluetoothWorkChains();
+                if (unoptimizedWorkChains != null) {
+                    for (int j = 0; j < unoptimizedWorkChains.size(); ++j) {
+                        // TODO(statsd) : Log the entire workchain here.
+                        StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED,
+                                unoptimizedWorkChains.get(j).getAttributionUid(), 0);
+                    }
+
+                    unoptimizedWorkChains.clear();
+                }
             }
         }
     }
@@ -5224,6 +5510,18 @@
         for (int i = 0; i < N; i++) {
             int uid = mapUid(ws.get(i));
             getUidStatsLocked(uid).noteBluetoothScanResultsLocked(numNewResults);
+            StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uid, numNewResults);
+        }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                final WorkChain wc = workChains.get(i);
+                int uid = mapUid(wc.getAttributionUid());
+                getUidStatsLocked(uid).noteBluetoothScanResultsLocked(numNewResults);
+                // TODO(statsd): Log the entire WorkChain here.
+                StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uid, numNewResults);
+            }
         }
     }
 
@@ -5273,6 +5571,15 @@
                 int uid = mapUid(ws.get(i));
                 getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
             }
+
+            List<WorkChain> workChains = ws.getWorkChains();
+            if (workChains != null) {
+                for (int i = 0; i < workChains.size(); ++i) {
+                    int uid = mapUid(workChains.get(i).getAttributionUid());
+                    getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
+                }
+            }
+
             scheduleSyncExternalStatsLocked("wifi-running", ExternalStatsSync.UPDATE_WIFI);
         } else {
             Log.w(TAG, "noteWifiRunningLocked -- called while WIFI running");
@@ -5287,11 +5594,28 @@
                 int uid = mapUid(oldWs.get(i));
                 getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
             }
+
+            List<WorkChain> workChains = oldWs.getWorkChains();
+            if (workChains != null) {
+                for (int i = 0; i < workChains.size(); ++i) {
+                    int uid = mapUid(workChains.get(i).getAttributionUid());
+                    getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
+                }
+            }
+
             N = newWs.size();
             for (int i=0; i<N; i++) {
                 int uid = mapUid(newWs.get(i));
                 getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
             }
+
+            workChains = newWs.getWorkChains();
+            if (workChains != null) {
+                for (int i = 0; i < workChains.size(); ++i) {
+                    int uid = mapUid(workChains.get(i).getAttributionUid());
+                    getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
+                }
+            }
         } else {
             Log.w(TAG, "noteWifiRunningChangedLocked -- called while WIFI not running");
         }
@@ -5312,6 +5636,15 @@
                 int uid = mapUid(ws.get(i));
                 getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
             }
+
+            List<WorkChain> workChains = ws.getWorkChains();
+            if (workChains != null) {
+                for (int i = 0; i < workChains.size(); ++i) {
+                    int uid = mapUid(workChains.get(i).getAttributionUid());
+                    getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
+                }
+            }
+
             scheduleSyncExternalStatsLocked("wifi-stopped", ExternalStatsSync.UPDATE_WIFI);
         } else {
             Log.w(TAG, "noteWifiStoppedLocked -- called while WIFI not running");
@@ -5393,7 +5726,6 @@
     int mWifiFullLockNesting = 0;
 
     public void noteFullWifiLockAcquiredLocked(int uid) {
-        uid = mapUid(uid);
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         if (mWifiFullLockNesting == 0) {
@@ -5407,7 +5739,6 @@
     }
 
     public void noteFullWifiLockReleasedLocked(int uid) {
-        uid = mapUid(uid);
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         mWifiFullLockNesting--;
@@ -5423,7 +5754,6 @@
     int mWifiScanNesting = 0;
 
     public void noteWifiScanStartedLocked(int uid) {
-        uid = mapUid(uid);
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         if (mWifiScanNesting == 0) {
@@ -5437,7 +5767,6 @@
     }
 
     public void noteWifiScanStoppedLocked(int uid) {
-        uid = mapUid(uid);
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         mWifiScanNesting--;
@@ -5473,6 +5802,12 @@
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
+
+            // Start Wifi Multicast overall timer
+            if (!mWifiMulticastWakelockTimer.isRunningLocked()) {
+                if (DEBUG_HISTORY) Slog.v(TAG, "WiFi Multicast Overall Timer Started");
+                mWifiMulticastWakelockTimer.startRunningLocked(elapsedRealtime);
+            }
         }
         mWifiMulticastNesting++;
         getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime);
@@ -5488,6 +5823,12 @@
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
+
+            // Stop Wifi Multicast overall timer
+            if (mWifiMulticastWakelockTimer.isRunningLocked()) {
+                if (DEBUG_HISTORY) Slog.v(TAG, "Multicast Overall Timer Stopped");
+                mWifiMulticastWakelockTimer.stopRunningLocked(elapsedRealtime);
+            }
         }
         getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime);
     }
@@ -5495,28 +5836,95 @@
     public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) {
         int N = ws.size();
         for (int i=0; i<N; i++) {
-            noteFullWifiLockAcquiredLocked(ws.get(i));
+            final int uid = mapUid(ws.get(i));
+            noteFullWifiLockAcquiredLocked(uid);
+            StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 1);
+        }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                final WorkChain workChain = workChains.get(i);
+                final int uid = mapUid(workChain.getAttributionUid());
+                noteFullWifiLockAcquiredLocked(uid);
+
+                // TODO(statsd): Log workChain instead of uid here.
+                if (DEBUG) {
+                    Slog.v(TAG, "noteFullWifiLockAcquiredFromSourceLocked: " + workChain);
+                }
+                StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 1);
+            }
         }
     }
 
     public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) {
         int N = ws.size();
         for (int i=0; i<N; i++) {
-            noteFullWifiLockReleasedLocked(ws.get(i));
+            final int uid = mapUid(ws.get(i));
+            noteFullWifiLockReleasedLocked(uid);
+            StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 0);
+        }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                final WorkChain workChain = workChains.get(i);
+                final int uid = mapUid(workChain.getAttributionUid());
+                noteFullWifiLockReleasedLocked(uid);
+
+                // TODO(statsd): Log workChain instead of uid here.
+                if (DEBUG) {
+                    Slog.v(TAG, "noteFullWifiLockReleasedFromSourceLocked: " + workChain);
+                }
+                StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 0);
+            }
         }
     }
 
     public void noteWifiScanStartedFromSourceLocked(WorkSource ws) {
         int N = ws.size();
         for (int i=0; i<N; i++) {
-            noteWifiScanStartedLocked(ws.get(i));
+            final int uid = mapUid(ws.get(i));
+            noteWifiScanStartedLocked(uid);
+            StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 1);
+        }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                final WorkChain workChain = workChains.get(i);
+                final int uid = mapUid(workChain.getAttributionUid());
+                noteWifiScanStartedLocked(uid);
+
+                // TODO(statsd): Log workChain instead of uid here.
+                if (DEBUG) {
+                    Slog.v(TAG, "noteWifiScanStartedFromSourceLocked: " + workChain);
+                }
+                StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 1);
+            }
         }
     }
 
     public void noteWifiScanStoppedFromSourceLocked(WorkSource ws) {
         int N = ws.size();
         for (int i=0; i<N; i++) {
-            noteWifiScanStoppedLocked(ws.get(i));
+            final int uid = mapUid(ws.get(i));
+            noteWifiScanStoppedLocked(uid);
+            StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 0);
+        }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                final WorkChain workChain = workChains.get(i);
+                final int uid = mapUid(workChain.getAttributionUid());
+                noteWifiScanStoppedLocked(uid);
+
+                if (DEBUG) {
+                    Slog.v(TAG, "noteWifiScanStoppedFromSourceLocked: " + workChain);
+                }
+                StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 0);
+            }
         }
     }
 
@@ -5525,6 +5933,13 @@
         for (int i=0; i<N; i++) {
             noteWifiBatchedScanStartedLocked(ws.get(i), csph);
         }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                noteWifiBatchedScanStartedLocked(workChains.get(i).getAttributionUid(), csph);
+            }
+        }
     }
 
     public void noteWifiBatchedScanStoppedFromSourceLocked(WorkSource ws) {
@@ -5532,6 +5947,13 @@
         for (int i=0; i<N; i++) {
             noteWifiBatchedScanStoppedLocked(ws.get(i));
         }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                noteWifiBatchedScanStoppedLocked(workChains.get(i).getAttributionUid());
+            }
+        }
     }
 
     public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) {
@@ -5539,6 +5961,13 @@
         for (int i=0; i<N; i++) {
             noteWifiMulticastEnabledLocked(ws.get(i));
         }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                noteWifiMulticastEnabledLocked(workChains.get(i).getAttributionUid());
+            }
+        }
     }
 
     public void noteWifiMulticastDisabledFromSourceLocked(WorkSource ws) {
@@ -5546,6 +5975,13 @@
         for (int i=0; i<N; i++) {
             noteWifiMulticastDisabledLocked(ws.get(i));
         }
+
+        final List<WorkChain> workChains = ws.getWorkChains();
+        if (workChains != null) {
+            for (int i = 0; i < workChains.size(); ++i) {
+                noteWifiMulticastDisabledLocked(workChains.get(i).getAttributionUid());
+            }
+        }
     }
 
     private static String[] includeInStringArray(String[] array, String str) {
@@ -5774,6 +6210,16 @@
         return (int)mMobileRadioActiveUnknownCount.getCountLocked(which);
     }
 
+    @Override public long getWifiMulticastWakelockTime(
+            long elapsedRealtimeUs, int which) {
+        return mWifiMulticastWakelockTimer.getTotalTimeLocked(
+                elapsedRealtimeUs, which);
+    }
+
+    @Override public int getWifiMulticastWakelockCount(int which) {
+        return mWifiMulticastWakelockTimer.getCountLocked(which);
+    }
+
     @Override public long getWifiOnTime(long elapsedRealtimeUs, int which) {
         return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
@@ -6103,6 +6549,15 @@
          */
         final SparseArray<Pid> mPids = new SparseArray<>();
 
+        /**
+         * The list of WorkChains associated with active bluetooth scans.
+         *
+         * NOTE: This is a hack and it only needs to exist because there's a "reset" API that is
+         * supposed to stop and log all WorkChains that were currently active.
+         */
+        ArrayList<WorkChain> mAllBluetoothChains = null;
+        ArrayList<WorkChain> mUnoptimizedBluetoothChains = null;
+
         public Uid(BatteryStatsImpl bsi, int uid) {
             mBsi = bsi;
             mUid = uid;
@@ -6227,7 +6682,7 @@
             return null;
         }
 
-        private void addProcStateTimesMs(int procState, long[] cpuTimesMs) {
+        private void addProcStateTimesMs(int procState, long[] cpuTimesMs, boolean onBattery) {
             if (mProcStateTimeMs == null) {
                 mProcStateTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
             }
@@ -6236,10 +6691,11 @@
                 mProcStateTimeMs[procState] = new LongSamplingCounterArray(
                         mBsi.mOnBatteryTimeBase);
             }
-            mProcStateTimeMs[procState].addCountLocked(cpuTimesMs);
+            mProcStateTimeMs[procState].addCountLocked(cpuTimesMs, onBattery);
         }
 
-        private void addProcStateScreenOffTimesMs(int procState, long[] cpuTimesMs) {
+        private void addProcStateScreenOffTimesMs(int procState, long[] cpuTimesMs,
+                boolean onBatteryScreenOff) {
             if (mProcStateScreenOffTimeMs == null) {
                 mProcStateScreenOffTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
             }
@@ -6248,7 +6704,7 @@
                 mProcStateScreenOffTimeMs[procState] = new LongSamplingCounterArray(
                         mBsi.mOnBatteryScreenOffTimeBase);
             }
-            mProcStateScreenOffTimeMs[procState].addCountLocked(cpuTimesMs);
+            mProcStateScreenOffTimeMs[procState].addCountLocked(cpuTimesMs, onBatteryScreenOff);
         }
 
         @Override
@@ -6330,8 +6786,6 @@
                             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);
             }
         }
 
@@ -6340,10 +6794,6 @@
             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);
-                }
             }
         }
 
@@ -6357,8 +6807,6 @@
                             mOnBatteryBackgroundTimeBase);
                 }
                 mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
-                // TODO(statsd): Possibly use a worksource instead of a uid.
-                StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 1);
             }
         }
 
@@ -6367,10 +6815,6 @@
             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);
-                }
             }
         }
 
@@ -6634,44 +7078,63 @@
             return mBluetoothUnoptimizedScanTimer;
         }
 
-        public void noteBluetoothScanStartedLocked(long elapsedRealtimeMs, boolean isUnoptimized) {
+        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 addBluetoothWorkChain(WorkChain workChain, boolean isUnoptimized) {
+            if (mAllBluetoothChains == null) {
+                mAllBluetoothChains = new ArrayList<WorkChain>(4);
+            }
+
+            if (isUnoptimized && mUnoptimizedBluetoothChains == null) {
+                mUnoptimizedBluetoothChains = new ArrayList<WorkChain>(4);
+            }
+
+            mAllBluetoothChains.add(workChain);
+            if (isUnoptimized) {
+                mUnoptimizedBluetoothChains.add(workChain);
+            }
+        }
+
+        public void removeBluetoothWorkChain(WorkChain workChain, boolean isUnoptimized) {
+            if (mAllBluetoothChains != null) {
+                mAllBluetoothChains.remove(workChain);
+            }
+
+            if (isUnoptimized && mUnoptimizedBluetoothChains != null) {
+                mUnoptimizedBluetoothChains.remove(workChain);
+            }
+        }
+
+        public List<WorkChain> getAllBluetoothWorkChains() {
+            return mAllBluetoothChains;
+        }
+
+        public List<WorkChain> getUnoptimizedBluetoothWorkChains() {
+            return mUnoptimizedBluetoothChains;
+        }
+
+
         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);
             }
         }
 
@@ -6693,9 +7156,6 @@
             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
@@ -8981,9 +9441,11 @@
                 if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
                     mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
 
-                    if (mBsi.mPerProcStateCpuTimesAvailable) {
+                    if (mBsi.trackPerProcStateCpuTimes()) {
                         if (mBsi.mPendingUids.size() == 0) {
-                            mBsi.mExternalSync.scheduleReadProcStateCpuTimes();
+                            mBsi.mExternalSync.scheduleReadProcStateCpuTimes(
+                                    mBsi.mOnBatteryTimeBase.isRunning(),
+                                    mBsi.mOnBatteryScreenOffTimeBase.isRunning());
                         }
                         if (mBsi.mPendingUids.indexOfKey(mUid) < 0
                                 || ArrayUtils.contains(CRITICAL_PROC_STATES, mProcessState)) {
@@ -9227,8 +9689,6 @@
             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);
@@ -9247,8 +9707,6 @@
                 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) {
@@ -9337,6 +9795,7 @@
         mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
         mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
         mHandler = new MyHandler(handler.getLooper());
+        mConstants = new Constants(mHandler);
         mStartCount++;
         mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
         mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
@@ -9378,6 +9837,8 @@
         mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase);
         mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase);
         mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase);
+        mWifiMulticastWakelockTimer = new StopwatchTimer(mClocks, null,
+                WIFI_AGGREGATE_MULTICAST_ENABLED, null, mOnBatteryTimeBase);
         mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase);
         mGlobalWifiRunningTimer = new StopwatchTimer(mClocks, null, -5, null, mOnBatteryTimeBase);
         for (int i=0; i<NUM_WIFI_STATES; i++) {
@@ -9430,6 +9891,7 @@
         mDailyFile = null;
         mHandler = null;
         mExternalSync = null;
+        mConstants = new Constants(mHandler);
         clearHistoryLocked();
         readFromParcel(p);
         mPlatformIdleStateCallback = null;
@@ -10079,6 +10541,7 @@
         for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
             mWifiSignalStrengthsTimer[i].reset(false);
         }
+        mWifiMulticastWakelockTimer.reset(false);
         mWifiActivity.reset(false);
         mBluetoothActivity.reset(false);
         mModemActivity.reset(false);
@@ -12182,6 +12645,78 @@
         mShuttingDown = true;
     }
 
+    public boolean trackPerProcStateCpuTimes() {
+        return mConstants.TRACK_CPU_TIMES_BY_PROC_STATE && mPerProcStateCpuTimesAvailable;
+    }
+
+    public void systemServicesReady(Context context) {
+        mConstants.startObserving(context.getContentResolver());
+    }
+
+    @VisibleForTesting
+    public final class Constants extends ContentObserver {
+        public static final String KEY_TRACK_CPU_TIMES_BY_PROC_STATE
+                = "track_cpu_times_by_proc_state";
+
+        private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = true;
+
+        public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
+
+        private ContentResolver mResolver;
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+        public Constants(Handler handler) {
+            super(handler);
+        }
+
+        public void startObserving(ContentResolver resolver) {
+            mResolver = resolver;
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.BATTERY_STATS_CONSTANTS),
+                    false /* notifyForDescendants */, this);
+            updateConstants();
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateConstants();
+        }
+
+        private void updateConstants() {
+            synchronized (BatteryStatsImpl.this) {
+                try {
+                    mParser.setString(Settings.Global.getString(mResolver,
+                            Settings.Global.BATTERY_STATS_CONSTANTS));
+                } catch (IllegalArgumentException e) {
+                    // Failed to parse the settings string, log this and move on
+                    // with defaults.
+                    Slog.e(TAG, "Bad batterystats settings", e);
+                }
+
+                updateTrackCpuTimesByProcStateLocked(TRACK_CPU_TIMES_BY_PROC_STATE,
+                        mParser.getBoolean(KEY_TRACK_CPU_TIMES_BY_PROC_STATE,
+                                DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE));
+            }
+        }
+
+        private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) {
+            TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled;
+            if (isEnabled && !wasEnabled) {
+                mKernelSingleUidTimeReader.markDataAsStale(true);
+                mExternalSync.scheduleCpuSyncDueToSettingChange();
+            }
+        }
+
+        public void dumpLocked(PrintWriter pw) {
+            pw.print(KEY_TRACK_CPU_TIMES_BY_PROC_STATE); pw.print("=");
+            pw.println(TRACK_CPU_TIMES_BY_PROC_STATE);
+        }
+    }
+
+    public void dumpConstantsLocked(PrintWriter pw) {
+        mConstants.dumpLocked(pw);
+    }
+
     Parcel mPendingWrite = null;
     final ReentrantLock mWriteLock = new ReentrantLock();
 
@@ -12525,6 +13060,7 @@
         mMobileRadioActiveAdjustedTime.readSummaryFromParcelLocked(in);
         mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in);
         mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in);
+        mWifiMulticastWakelockTimer.readSummaryFromParcelLocked(in);
         mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
         mWifiOn = false;
         mWifiOnTimer.readSummaryFromParcelLocked(in);
@@ -12965,6 +13501,7 @@
         mMobileRadioActiveAdjustedTime.writeSummaryFromParcelLocked(out);
         mMobileRadioActiveUnknownTime.writeSummaryFromParcelLocked(out);
         mMobileRadioActiveUnknownCount.writeSummaryFromParcelLocked(out);
+        mWifiMulticastWakelockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         for (int i=0; i<NUM_WIFI_STATES; i++) {
@@ -13428,6 +13965,8 @@
         mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
         mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
         mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mWifiMulticastWakelockTimer = new StopwatchTimer(mClocks, null, -4, null,
+                mOnBatteryTimeBase, in);
         mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
         mWifiOn = false;
         mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase, in);
@@ -13634,6 +14173,7 @@
         mMobileRadioActiveAdjustedTime.writeToParcel(out);
         mMobileRadioActiveUnknownTime.writeToParcel(out);
         mMobileRadioActiveUnknownCount.writeToParcel(out);
+        mWifiMulticastWakelockTimer.writeToParcel(out, uSecRealtime);
         mWifiOnTimer.writeToParcel(out, uSecRealtime);
         mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime);
         for (int i=0; i<NUM_WIFI_STATES; i++) {
@@ -13820,6 +14360,8 @@
             mMobileRadioActiveTimer.logState(pr, "  ");
             pr.println("*** Mobile network active adjusted timer:");
             mMobileRadioActiveAdjustedTime.logState(pr, "  ");
+            pr.println("*** Wifi Multicast WakeLock Timer:");
+            mWifiMulticastWakelockTimer.logState(pr, "  ");
             pr.println("*** mWifiRadioPowerState=" + mWifiRadioPowerState);
             pr.println("*** Wifi timer:");
             mWifiOnTimer.logState(pr, "  ");
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index ca635a4..ebeb24c4 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -46,12 +46,14 @@
     private final int mCpuFreqsCount;
 
     @GuardedBy("this")
-    private final SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
+    private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
 
     @GuardedBy("this")
     private int mReadErrorCounter;
     @GuardedBy("this")
     private boolean mSingleUidCpuTimesAvailable = true;
+    @GuardedBy("this")
+    private boolean mHasStaleData;
 
     private final Injector mInjector;
 
@@ -166,6 +168,30 @@
         return deltaTimesMs;
     }
 
+    public void markDataAsStale(boolean hasStaleData) {
+        synchronized (this) {
+            mHasStaleData = hasStaleData;
+        }
+    }
+
+    public boolean hasStaleData() {
+        synchronized (this) {
+            return mHasStaleData;
+        }
+    }
+
+    public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) {
+        synchronized (this) {
+            mLastUidCpuTimeMs.clear();
+            for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) {
+                final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i);
+                if (cpuTimesMs != null) {
+                    mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone());
+                }
+            }
+        }
+    }
+
     public void removeUid(int uid) {
         synchronized (this) {
             mLastUidCpuTimeMs.delete(uid);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2be6212..c5fe4cb 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -650,7 +650,7 @@
         String args[] = {
             "--setuid=1000",
             "--setgid=1000",
-            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1032,3001,3002,3003,3006,3007,3009,3010",
+            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1032,1065,3001,3002,3003,3006,3007,3009,3010",
             "--capabilities=" + capabilities + "," + capabilities,
             "--nice-name=system_server",
             "--runtime-args",
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
index 9a6e542..8fc4c30 100644
--- a/core/java/com/android/internal/util/RingBuffer.java
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -67,16 +67,21 @@
      */
     public T getNextSlot() {
         final int nextSlotIdx = indexOf(mCursor++);
-        T item = mBuffer[nextSlotIdx];
-        if (item == null) {
-            try {
-                item = (T) mBuffer.getClass().getComponentType().newInstance();
-            } catch (IllegalAccessException | InstantiationException e) {
-                return null;
-            }
-            mBuffer[nextSlotIdx] = item;
+        if (mBuffer[nextSlotIdx] == null) {
+            mBuffer[nextSlotIdx] = createNewItem();
         }
-        return item;
+        return mBuffer[nextSlotIdx];
+    }
+
+    /**
+     * @return a new object of type <T> or null if a new object could not be created.
+     */
+    protected T createNewItem() {
+        try {
+            return (T) mBuffer.getClass().getComponentType().newInstance();
+        } catch (IllegalAccessException | InstantiationException e) {
+            return null;
+        }
     }
 
     public T[] toArray() {
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambda.java b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
index 17b140d..87c25e9 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambda.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
@@ -59,6 +59,21 @@
  * You can fill the 'missing argument' spot with {@link #__()}
  * (which is the factory function for {@link ArgumentPlaceholder})
  *
+ * NOTE: It is highly recommended to <b>only</b> use {@code ClassName::methodName}
+ * (aka unbounded method references) as the 1st argument for any of the
+ * factories ({@code obtain*(...)}) to avoid unwanted allocations.
+ * This means <b>not</b> using:
+ * <ul>
+ *     <li>{@code someVar::methodName} or {@code this::methodName} as it captures the reference
+ *     on the left of {@code ::}, resulting in an allocation on each evaluation of such
+ *     bounded method references</li>
+ *
+ *     <li>A lambda expression, e.g. {@code () -> toString()} due to how easy it is to accidentally
+ *     capture state from outside. In the above lambda expression for example, no variable from
+ *     outer scope is explicitly mentioned, yet one is still captured due to {@code toString()}
+ *     being an equivalent of {@code this.toString()}</li>
+ * </ul>
+ *
  * @hide
  */
 @SuppressWarnings({"unchecked", "unused", "WeakerAccess"})
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 82affe2..ab8be33 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -30,9 +30,6 @@
  * orientation when it can't fit its child views horizontally.
  */
 public class ButtonBarLayout extends LinearLayout {
-    /** Minimum screen height required for button stacking. */
-    private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
-
     /** Amount of the second button to "peek" above the fold when stacked. */
     private static final int PEEK_BUTTON_DP = 16;
 
@@ -46,12 +43,8 @@
     public ButtonBarLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        final boolean allowStackingDefault =
-                context.getResources().getConfiguration().screenHeightDp
-                        >= ALLOW_STACKING_MIN_HEIGHT_DP;
         final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
-        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
-                allowStackingDefault);
+        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true);
         ta.recycle();
     }
 
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 43536a5..4e7df28 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -63,21 +63,22 @@
     // RecoverableKeyStoreLoader methods.
     // {@code ServiceSpecificException} may be thrown to signal an error, which caller can
     // convert to  {@code RecoverableKeyStoreLoader}.
-    void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList,
-            int userId);
-    KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId);
-    void setSnapshotCreatedPendingIntent(in PendingIntent intent, int userId);
-    Map getRecoverySnapshotVersions(int userId);
-    void setServerParameters(long serverParameters, int userId);
-    void setRecoveryStatus(in String packageName, in String[] aliases, int status, int userId);
-    Map getRecoveryStatus(in String packageName, int userId);
-    void setRecoverySecretTypes(in int[] secretTypes, int userId);
-    int[] getRecoverySecretTypes(int userId);
-    int[] getPendingRecoverySecretTypes(int userId);
-    void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret, int userId);
+    void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList);
+    KeyStoreRecoveryData getRecoveryData(in byte[] account);
+    byte[] generateAndStoreKey(String alias);
+    void removeKey(String alias);
+    void setSnapshotCreatedPendingIntent(in PendingIntent intent);
+    Map getRecoverySnapshotVersions();
+    void setServerParameters(long serverParameters);
+    void setRecoveryStatus(in String packageName, in String[] aliases, int status);
+    Map getRecoveryStatus(in String packageName);
+    void setRecoverySecretTypes(in int[] secretTypes);
+    int[] getRecoverySecretTypes();
+    int[] getPendingRecoverySecretTypes();
+    void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret);
     byte[] startRecoverySession(in String sessionId,
             in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge,
-            in List<KeyStoreRecoveryMetadata> secrets, int userId);
-    void recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
-            in List<KeyEntryRecoveryData> applicationKeys, int userId);
+            in List<KeyStoreRecoveryMetadata> secrets);
+    Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
+            in List<KeyEntryRecoveryData> applicationKeys);
 }
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 7abc76a..408a4e9 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -9556,7 +9556,7 @@
             if (vScroll == 0 && hScroll == 0) {
                 return false;
             }
-            mRecyclerView.scrollBy(hScroll, vScroll);
+            mRecyclerView.smoothScrollBy(hScroll, vScroll);
             return true;
         }
 
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index b7a6719..c5af897 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -633,6 +633,11 @@
             addFeature(PackageManager.FEATURE_SECURELY_REMOVES_USERS, 0);
         }
 
+        // Help legacy devices that may not have updated their static config
+        if (StorageManager.hasAdoptable()) {
+            addFeature(PackageManager.FEATURE_ADOPTABLE_STORAGE, 0);
+        }
+
         if (ActivityManager.isLowRamDeviceStatic()) {
             addFeature(PackageManager.FEATURE_RAM_LOW, 0);
         } else {
diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
index 0f46433..4257c98 100644
--- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
@@ -1,5 +1,6 @@
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "SkData.h"
+#include "SkMalloc.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
 #include "SkTypes.h"
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 52c84a4..8fee7ba 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -18,6 +18,8 @@
 
 //#define LOG_NDEBUG 0
 
+#include <inttypes.h>
+
 #include <nativehelper/JNIHelp.h>
 
 #include <android_runtime/AndroidRuntime.h>
@@ -78,8 +80,8 @@
 
     void setFdEvents(int events);
 
-    const char* getInputChannelName() {
-        return mInputConsumer.getChannel()->getName().c_str();
+    const std::string getInputChannelName() {
+        return mInputConsumer.getChannel()->getName();
     }
 
     virtual int handleEvent(int receiveFd, int events, void* data);
@@ -93,7 +95,7 @@
         mInputConsumer(inputChannel), mMessageQueue(messageQueue),
         mBatchedInputEventPending(false), mFdEvents(0) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName());
+        ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName().c_str());
     }
 }
 
@@ -109,7 +111,7 @@
 
 void NativeInputEventReceiver::dispose() {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Disposing input event receiver.", getInputChannelName());
+        ALOGD("channel '%s' ~ Disposing input event receiver.", getInputChannelName().c_str());
     }
 
     setFdEvents(0);
@@ -117,7 +119,7 @@
 
 status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Finished input event.", getInputChannelName());
+        ALOGD("channel '%s' ~ Finished input event.", getInputChannelName().c_str());
     }
 
     status_t status = mInputConsumer.sendFinishedSignal(seq, handled);
@@ -125,7 +127,7 @@
         if (status == WOULD_BLOCK) {
             if (kDebugDispatchCycle) {
                 ALOGD("channel '%s' ~ Could not send finished signal immediately.  "
-                        "Enqueued for later.", getInputChannelName());
+                        "Enqueued for later.", getInputChannelName().c_str());
             }
             Finish finish;
             finish.seq = seq;
@@ -137,7 +139,7 @@
             return OK;
         }
         ALOGW("Failed to send finished signal on channel '%s'.  status=%d",
-                getInputChannelName(), status);
+                getInputChannelName().c_str(), status);
     }
     return status;
 }
@@ -161,7 +163,7 @@
         // the consumer will soon be disposed as well.
         if (kDebugDispatchCycle) {
             ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred.  "
-                    "events=0x%x", getInputChannelName(), events);
+                    "events=0x%x", getInputChannelName().c_str(), events);
         }
         return 0; // remove the callback
     }
@@ -183,13 +185,13 @@
                 if (status == WOULD_BLOCK) {
                     if (kDebugDispatchCycle) {
                         ALOGD("channel '%s' ~ Sent %zu queued finish events; %zu left.",
-                                getInputChannelName(), i, mFinishQueue.size());
+                                getInputChannelName().c_str(), i, mFinishQueue.size());
                     }
                     return 1; // keep the callback, try again later
                 }
 
                 ALOGW("Failed to send finished signal on channel '%s'.  status=%d",
-                        getInputChannelName(), status);
+                        getInputChannelName().c_str(), status);
                 if (status != DEAD_OBJECT) {
                     JNIEnv* env = AndroidRuntime::getJNIEnv();
                     String8 message;
@@ -202,7 +204,7 @@
         }
         if (kDebugDispatchCycle) {
             ALOGD("channel '%s' ~ Sent %zu queued finish events; none left.",
-                    getInputChannelName(), mFinishQueue.size());
+                    getInputChannelName().c_str(), mFinishQueue.size());
         }
         mFinishQueue.clear();
         setFdEvents(ALOOPER_EVENT_INPUT);
@@ -210,15 +212,16 @@
     }
 
     ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
-            "events=0x%x", getInputChannelName(), events);
+            "events=0x%x", getInputChannelName().c_str(), events);
     return 1;
 }
 
 status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
         bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%lld.",
-                getInputChannelName(), consumeBatches ? "true" : "false", (long long)frameTime);
+        ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64,
+                getInputChannelName().c_str(),
+                consumeBatches ? "true" : "false", frameTime);
     }
 
     if (consumeBatches) {
@@ -245,7 +248,7 @@
                         receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
                         if (!receiverObj.get()) {
                             ALOGW("channel '%s' ~ Receiver object was finalized "
-                                    "without being disposed.", getInputChannelName());
+                                    "without being disposed.", getInputChannelName().c_str());
                             return DEAD_OBJECT;
                         }
                     }
@@ -253,7 +256,7 @@
                     mBatchedInputEventPending = true;
                     if (kDebugDispatchCycle) {
                         ALOGD("channel '%s' ~ Dispatching batched input event pending notification.",
-                                getInputChannelName());
+                                getInputChannelName().c_str());
                     }
                     env->CallVoidMethod(receiverObj.get(),
                             gInputEventReceiverClassInfo.dispatchBatchedInputEventPending);
@@ -265,7 +268,7 @@
                 return OK;
             }
             ALOGE("channel '%s' ~ Failed to consume input event.  status=%d",
-                    getInputChannelName(), status);
+                    getInputChannelName().c_str(), status);
             return status;
         }
         assert(inputEvent);
@@ -275,7 +278,7 @@
                 receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
                 if (!receiverObj.get()) {
                     ALOGW("channel '%s' ~ Receiver object was finalized "
-                            "without being disposed.", getInputChannelName());
+                            "without being disposed.", getInputChannelName().c_str());
                     return DEAD_OBJECT;
                 }
             }
@@ -284,7 +287,7 @@
             switch (inputEvent->getType()) {
             case AINPUT_EVENT_TYPE_KEY:
                 if (kDebugDispatchCycle) {
-                    ALOGD("channel '%s' ~ Received key event.", getInputChannelName());
+                    ALOGD("channel '%s' ~ Received key event.", getInputChannelName().c_str());
                 }
                 inputEventObj = android_view_KeyEvent_fromNative(env,
                         static_cast<KeyEvent*>(inputEvent));
@@ -292,7 +295,7 @@
 
             case AINPUT_EVENT_TYPE_MOTION: {
                 if (kDebugDispatchCycle) {
-                    ALOGD("channel '%s' ~ Received motion event.", getInputChannelName());
+                    ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str());
                 }
                 MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
                 if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
@@ -309,7 +312,7 @@
 
             if (inputEventObj) {
                 if (kDebugDispatchCycle) {
-                    ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName());
+                    ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str());
                 }
                 env->CallVoidMethod(receiverObj.get(),
                         gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj,
@@ -320,7 +323,8 @@
                 }
                 env->DeleteLocalRef(inputEventObj);
             } else {
-                ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName());
+                ALOGW("channel '%s' ~ Failed to obtain event object.",
+                        getInputChannelName().c_str());
                 skipCallbacks = true;
             }
         }
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index f87abac..effeed6 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -70,8 +70,8 @@
     KeyedVector<uint32_t, uint32_t> mPublishedSeqMap;
     uint32_t mNextPublishedSeq;
 
-    const char* getInputChannelName() {
-        return mInputPublisher.getChannel()->getName().c_str();
+    const std::string getInputChannelName() {
+        return mInputPublisher.getChannel()->getName();
     }
 
     virtual int handleEvent(int receiveFd, int events, void* data);
@@ -86,7 +86,7 @@
         mInputPublisher(inputChannel), mMessageQueue(messageQueue),
         mNextPublishedSeq(1) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Initializing input event sender.", getInputChannelName());
+        ALOGD("channel '%s' ~ Initializing input event sender.", getInputChannelName().c_str());
     }
 }
 
@@ -103,7 +103,7 @@
 
 void NativeInputEventSender::dispose() {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Disposing input event sender.", getInputChannelName());
+        ALOGD("channel '%s' ~ Disposing input event sender.", getInputChannelName().c_str());
     }
 
     mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
@@ -111,7 +111,7 @@
 
 status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Sending key event, seq=%u.", getInputChannelName(), seq);
+        ALOGD("channel '%s' ~ Sending key event, seq=%u.", getInputChannelName().c_str(), seq);
     }
 
     uint32_t publishedSeq = mNextPublishedSeq++;
@@ -121,7 +121,7 @@
             event->getRepeatCount(), event->getDownTime(), event->getEventTime());
     if (status) {
         ALOGW("Failed to send key event on channel '%s'.  status=%d",
-                getInputChannelName(), status);
+                getInputChannelName().c_str(), status);
         return status;
     }
     mPublishedSeqMap.add(publishedSeq, seq);
@@ -130,7 +130,7 @@
 
 status_t NativeInputEventSender::sendMotionEvent(uint32_t seq, const MotionEvent* event) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Sending motion event, seq=%u.", getInputChannelName(), seq);
+        ALOGD("channel '%s' ~ Sending motion event, seq=%u.", getInputChannelName().c_str(), seq);
     }
 
     uint32_t publishedSeq;
@@ -148,7 +148,7 @@
                 event->getHistoricalRawPointerCoords(0, i));
         if (status) {
             ALOGW("Failed to send motion event sample on channel '%s'.  status=%d",
-                    getInputChannelName(), status);
+                    getInputChannelName().c_str(), status);
             return status;
         }
     }
@@ -163,7 +163,7 @@
         // soon be disposed as well.
         if (kDebugDispatchCycle) {
             ALOGD("channel '%s' ~ Consumer closed input channel or an error occurred.  "
-                    "events=0x%x", getInputChannelName(), events);
+                    "events=0x%x", getInputChannelName().c_str(), events);
         }
 
         return 0; // remove the callback
@@ -171,7 +171,7 @@
 
     if (!(events & ALOOPER_EVENT_INPUT)) {
         ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
-                "events=0x%x", getInputChannelName(), events);
+                "events=0x%x", getInputChannelName().c_str(), events);
         return 1;
     }
 
@@ -183,7 +183,7 @@
 
 status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName());
+        ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName().c_str());
     }
 
     ScopedLocalRef<jobject> senderObj(env, NULL);
@@ -197,7 +197,7 @@
                 return OK;
             }
             ALOGE("channel '%s' ~ Failed to consume finished signals.  status=%d",
-                    getInputChannelName(), status);
+                    getInputChannelName().c_str(), status);
             return status;
         }
 
@@ -209,7 +209,7 @@
             if (kDebugDispatchCycle) {
                 ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, "
                         "pendingEvents=%zu.",
-                        getInputChannelName(), seq, handled ? "true" : "false",
+                        getInputChannelName().c_str(), seq, handled ? "true" : "false",
                         mPublishedSeqMap.size());
             }
 
@@ -218,7 +218,7 @@
                     senderObj.reset(jniGetReferent(env, mSenderWeakGlobal));
                     if (!senderObj.get()) {
                         ALOGW("channel '%s' ~ Sender object was finalized "
-                                "without being disposed.", getInputChannelName());
+                                "without being disposed.", getInputChannelName().c_str());
                         return DEAD_OBJECT;
                     }
                 }
diff --git a/core/proto/android/app/activitymanager.proto b/core/proto/android/app/activitymanager.proto
index 7385050..3412a32 100644
--- a/core/proto/android/app/activitymanager.proto
+++ b/core/proto/android/app/activitymanager.proto
@@ -38,29 +38,29 @@
   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_STATE_IMPORTANT_FOREGROUND = 500;
   // Process is important to the user, but not something they are aware of.
-  PROCESS_STATE_IMPORTANT_BACKGROUND = 700;
+  PROCESS_STATE_IMPORTANT_BACKGROUND = 600;
   // Process is in the background transient so we will try to keep running.
-  PROCESS_STATE_TRANSIENT_BACKGROUND = 800;
+  PROCESS_STATE_TRANSIENT_BACKGROUND = 700;
   // 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_STATE_BACKUP = 800;
   // 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_STATE_SERVICE = 900;
   // 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_STATE_RECEIVER = 1000;
+  // Same as PROCESS_STATE_TOP but while device is sleeping.
+  PROCESS_STATE_TOP_SLEEPING = 1100;
+  // 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 = 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.
@@ -70,9 +70,12 @@
   // 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 has an activity that corresponds
+  // to an existing recent task.
+  PROCESS_STATE_CACHED_RECENT = 1700;
   // Process is being cached for later use and is empty.
-  PROCESS_STATE_CACHED_EMPTY = 1700;
+  PROCESS_STATE_CACHED_EMPTY = 1800;
   // Process does not exist.
-  PROCESS_STATE_NONEXISTENT = 1800;
+  PROCESS_STATE_NONEXISTENT = 1900;
 }
 
diff --git a/core/proto/android/content/clipdata.proto b/core/proto/android/content/clipdata.proto
new file mode 100644
index 0000000..6967b69
--- /dev/null
+++ b/core/proto/android/content/clipdata.proto
@@ -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.
+ */
+
+syntax = "proto2";
+package android.content;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/content/clipdescription.proto";
+import "frameworks/base/core/proto/android/content/intent.proto";
+
+// An android.content.ClipData object.
+message ClipDataProto {
+    optional android.content.ClipDescriptionProto description = 1;
+
+    // Custom dump of an android.graphics.Bitmap object.
+    message Icon {
+        optional int32 width = 1;
+        optional int32 height = 2;
+    }
+    optional Icon icon = 2;
+
+    // An android.content.ClipData.Item object.
+    message Item {
+        oneof data {
+            string html_text = 1;
+            string text = 2;
+            string uri = 3;
+            android.content.IntentProto intent = 4;
+            bool nothing = 5;
+        }
+    }
+    repeated Item items = 3;
+}
diff --git a/core/java/android/service/autofill/Scorer.java b/core/proto/android/content/clipdescription.proto
similarity index 60%
copy from core/java/android/service/autofill/Scorer.java
copy to core/proto/android/content/clipdescription.proto
index c401855..40f4ad3 100644
--- a/core/java/android/service/autofill/Scorer.java
+++ b/core/proto/android/content/clipdescription.proto
@@ -13,16 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.service.autofill;
 
-/**
- * Helper class used to calculate a score.
- *
- * <p>Typically used to calculate the
- * <a href="AutofillService.html#FieldClassification">field classification</a> score between an
- * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value
- * predicted by an autofill service.
- */
-public interface Scorer {
+syntax = "proto2";
+package android.content;
 
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/os/persistablebundle.proto";
+
+// An android.content.ClipDescription object.
+message ClipDescriptionProto {
+    repeated string mime_types = 1;
+    optional string label = 2;
+    optional android.os.PersistableBundleProto extras = 3;
+    optional int64 timestamp_ms = 4;
 }
diff --git a/core/proto/android/graphics/pixelformat.proto b/core/proto/android/graphics/pixelformat.proto
new file mode 100644
index 0000000..4e42c92
--- /dev/null
+++ b/core/proto/android/graphics/pixelformat.proto
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.graphics;
+option java_multiple_files = true;
+
+message PixelFormatProto {
+  enum Format {
+      UNKNOWN      = 0;
+      TRANSLUCENT  = -3;
+      TRANSPARENT  = -2;
+      OPAQUE       = -1;
+      RGBA_8888    = 1;
+      RGBX_8888    = 2;
+      RGB_888      = 3;
+      RGB_565      = 4;
+      RGBA_F16     = 0x16;
+      RGBA_1010102 = 0x2B;
+  }
+}
diff --git a/core/java/android/service/autofill/Scorer.java b/core/proto/android/net/network.proto
similarity index 62%
copy from core/java/android/service/autofill/Scorer.java
copy to core/proto/android/net/network.proto
index c401855..9c7ea5d 100644
--- a/core/java/android/service/autofill/Scorer.java
+++ b/core/proto/android/net/network.proto
@@ -13,16 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.service.autofill;
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.net;
 
 /**
- * Helper class used to calculate a score.
- *
- * <p>Typically used to calculate the
- * <a href="AutofillService.html#FieldClassification">field classification</a> score between an
- * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value
- * predicted by an autofill service.
+ * An android.net.Network object.
  */
-public interface Scorer {
-
+message NetworkProto {
+    optional int32 net_id = 1;
 }
diff --git a/core/proto/android/net/networkcapabilities.proto b/core/proto/android/net/networkcapabilities.proto
new file mode 100644
index 0000000..e1c2af1
--- /dev/null
+++ b/core/proto/android/net/networkcapabilities.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+package android.net;
+
+option java_multiple_files = true;
+
+/**
+ * An android.net.NetworkCapabilities object.
+ */
+message NetworkCapabilitiesProto {
+    enum Transport {
+        // Indicates this network uses a Cellular transport.
+        TRANSPORT_CELLULAR = 0;
+        // Indicates this network uses a Wi-Fi transport.
+        TRANSPORT_WIFI = 1;
+        // Indicates this network uses a Bluetooth transport.
+        TRANSPORT_BLUETOOTH = 2;
+        // Indicates this network uses an Ethernet transport.
+        TRANSPORT_ETHERNET = 3;
+        // Indicates this network uses a VPN transport.
+        TRANSPORT_VPN = 4;
+        // Indicates this network uses a Wi-Fi Aware transport.
+        TRANSPORT_WIFI_AWARE = 5;
+        // Indicates this network uses a LoWPAN transport.
+        TRANSPORT_LOWPAN = 6;
+    }
+    repeated Transport transports = 1;
+
+    enum NetCapability {
+        // Indicates this is a network that has the ability to reach the
+        // carrier's MMSC for sending and receiving MMS messages.
+        NET_CAPABILITY_MMS = 0;
+        // Indicates this is a network that has the ability to reach the
+        // carrier's SUPL server, used to retrieve GPS information.
+        NET_CAPABILITY_SUPL = 1;
+        // Indicates this is a network that has the ability to reach the
+        // carrier's DUN or tethering gateway.
+        NET_CAPABILITY_DUN = 2;
+        // Indicates this is a network that has the ability to reach the
+        // carrier's FOTA portal, used for over the air updates.
+        NET_CAPABILITY_FOTA = 3;
+        // Indicates this is a network that has the ability to reach the
+        // carrier's IMS servers, used for network registration and signaling.
+        NET_CAPABILITY_IMS = 4;
+        // Indicates this is a network that has the ability to reach the
+        // carrier's CBS servers, used for carrier specific services.
+        NET_CAPABILITY_CBS = 5;
+        // Indicates this is a network that has the ability to reach a Wi-Fi
+        // direct peer.
+        NET_CAPABILITY_WIFI_P2P = 6;
+        // Indicates this is a network that has the ability to reach a carrier's
+        // Initial Attach servers.
+        NET_CAPABILITY_IA = 7;
+        // Indicates this is a network that has the ability to reach a carrier's
+        // RCS servers, used for Rich Communication Services.
+        NET_CAPABILITY_RCS = 8;
+        // Indicates this is a network that has the ability to reach a carrier's
+        // XCAP servers, used for configuration and control.
+        NET_CAPABILITY_XCAP = 9;
+        // Indicates this is a network that has the ability to reach a carrier's
+        // Emergency IMS servers or other services, used for network signaling
+        // during emergency calls.
+        NET_CAPABILITY_EIMS = 10;
+        // Indicates that this network is unmetered.
+        NET_CAPABILITY_NOT_METERED = 11;
+        // Indicates that this network should be able to reach the internet.
+        NET_CAPABILITY_INTERNET = 12;
+        // Indicates that this network is available for general use. If this is
+        // not set applications should not attempt to communicate on this
+        // network. Note that this is simply informative and not enforcement -
+        // enforcement is handled via other means. Set by default.
+        NET_CAPABILITY_NOT_RESTRICTED = 13;
+        // Indicates that the user has indicated implicit trust of this network.
+        // This generally means it's a sim-selected carrier, a plugged in
+        // ethernet, a paired BT device or a wifi the user asked to connect to.
+        // Untrusted networks are probably limited to unknown wifi AP. Set by
+        // default.
+        NET_CAPABILITY_TRUSTED = 14;
+        // Indicates that this network is not a VPN.  This capability is set by
+        // default and should be explicitly cleared for VPN networks.
+        NET_CAPABILITY_NOT_VPN = 15;
+        // Indicates that connectivity on this network was successfully
+        // validated. For example, for a network with NET_CAPABILITY_INTERNET,
+        // it means that Internet connectivity was successfully detected.
+        NET_CAPABILITY_VALIDATED = 16;
+        // Indicates that this network was found to have a captive portal in
+        // place last time it was probed.
+        NET_CAPABILITY_CAPTIVE_PORTAL = 17;
+        // Indicates that this network is not roaming.
+        NET_CAPABILITY_NOT_ROAMING = 18;
+        // Indicates that this network is available for use by apps, and not a
+        // network that is being kept up in the background to facilitate fast
+        // network switching.
+        NET_CAPABILITY_FOREGROUND = 19;
+    }
+    repeated NetCapability capabilities = 2;
+
+    // Passive link bandwidth. This is a rough guide of the expected peak
+    // bandwidth for the first hop on the given transport.  It is not measured,
+    // but may take into account link parameters (Radio technology, allocated
+    // channels, etc).
+    optional int32 link_up_bandwidth_kbps = 3;
+    optional int32 link_down_bandwidth_kbps = 4;
+
+    optional string network_specifier = 5;
+
+    // True if this object specifies a signal strength.
+    optional bool can_report_signal_strength = 6;
+    // This is a signed integer, and higher values indicate better signal. The
+    // exact units are bearer-dependent. For example, Wi-Fi uses RSSI.
+    // Only valid if can_report_signal_strength is true.
+    optional sint32 signal_strength = 7;
+}
diff --git a/core/proto/android/net/networkrequest.proto b/core/proto/android/net/networkrequest.proto
new file mode 100644
index 0000000..9884464
--- /dev/null
+++ b/core/proto/android/net/networkrequest.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+package android.net;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
+
+/**
+ * An android.net.NetworkRequest object.
+ */
+message NetworkRequestProto {
+    enum Type {
+        TYPE_UNKNOWN = 0;
+        // Only used by applications. When an application creates a
+        // NetworkRequest, it does not have a type; the type is set by the
+        // system depending on the method used to file the request
+        // (requestNetwork, registerNetworkCallback, etc.).
+        TYPE_NONE = 1;
+        // The framework will issue callbacks about any and all networks that
+        // match the specified NetworkCapabilities.
+        TYPE_LISTEN = 2;
+        // A hybrid of the two designed such that the framework will issue
+        // callbacks for the single, highest scoring current network (if any)
+        // that matches the capabilities of the default Internet request
+        // (mDefaultRequest), but which cannot cause the framework to either
+        // create or retain the existence of any specific network. Note that
+        // from the point of view of the request matching code, TRACK_DEFAULT is
+        // identical to REQUEST: its special behaviour is not due to different
+        // semantics, but to the fact that the system will only ever create a
+        // TRACK_DEFAULT with capabilities that are identical to the default
+        // request's capabilities, thus causing it to share fate in every way
+        // with the default request.
+        TYPE_TRACK_DEFAULT = 3;
+        // Capable of causing a specific network to be created first (e.g. a
+        // telephony DUN request), the framework will issue callbacks about the
+        // single, highest scoring current network (if any) that matches the
+        // specified NetworkCapabilities.
+        TYPE_REQUEST = 4;
+        // Like REQUEST but does not cause any networks to retain the
+        // NET_CAPABILITY_FOREGROUND capability. A network with no foreground
+        // requests is in the background. A network that has one or more
+        // background requests and loses its last foreground request to a
+        // higher-scoring network will not go into the background immediately,
+        // but will linger and go into the background after the linger timeout.
+        TYPE_BACKGROUND_REQUEST = 5;
+    }
+    // The type of the request. This is only used by the system and is always
+    // NONE elsewhere.
+    optional Type type = 1;
+    // Identifies the request.
+    optional int32 request_id = 2;
+    // Set for legacy requests and the default.
+    optional int32 legacy_type = 3;
+    optional NetworkCapabilitiesProto network_capabilities = 4;
+}
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 0fa428e..9999b4e 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -537,7 +537,24 @@
       // Screen-off CPU time in milliseconds.
       optional int64 screen_off_duration_ms = 3;
     }
+    // CPU times accumulated across all process states.
     repeated ByFrequency by_frequency = 3;
+
+    enum ProcessState {
+      TOP = 0;
+      FOREGROUND_SERVICE = 1;
+      FOREGROUND = 2;
+      BACKGROUND = 3;
+      TOP_SLEEPING = 4;
+      HEAVY_WEIGHT = 5;
+      CACHED = 6;
+    }
+    // CPU times at different process states.
+    message ByProcessState {
+      optional ProcessState process_state = 1;
+      repeated ByFrequency by_frequency = 2;
+    }
+    repeated ByProcessState by_process_state = 4;
   }
   optional Cpu cpu = 7;
 
@@ -655,7 +672,7 @@
     // 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
+    // top > foreground service > foreground > background > top sleeping > heavy weight > cache
     enum State {
       // Time this uid has any processes in the top state (or above such as
       // persistent).
@@ -663,20 +680,26 @@
       // 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;
+      PROCESS_STATE_FOREGROUND = 2;
       // Time this uid has any process in an active background state, but none
       // in the "foreground" or better state.
-      PROCESS_STATE_BACKGROUND = 4;
+      PROCESS_STATE_BACKGROUND = 3;
+      // Time this uid has any process that is top while the device is sleeping,
+      // but not active for any other reason. We consider is a kind of cached
+      // process for execution restrictions. 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 = 4;
+      // Time this uid has any process that is in the background but it has an
+      // activity marked as "can't save state".  This is essentially a cached
+      // process, though the system will try much harder than normal to avoid
+      // killing it.
+      PROCESS_STATE_HEAVY_WEIGHT = 5;
       // Time this uid has any processes that are sitting around cached, not in
       // one of the other active states.
-      PROCESS_STATE_CACHED = 5;
+      PROCESS_STATE_CACHED = 6;
     }
     optional State state = 1;
     optional int64 duration_ms = 2;
diff --git a/core/java/android/service/autofill/Scorer.java b/core/proto/android/os/batterytype.proto
similarity index 61%
copy from core/java/android/service/autofill/Scorer.java
copy to core/proto/android/os/batterytype.proto
index c401855..75d0dd3 100644
--- a/core/java/android/service/autofill/Scorer.java
+++ b/core/proto/android/os/batterytype.proto
@@ -13,16 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.service.autofill;
 
-/**
- * Helper class used to calculate a score.
- *
- * <p>Typically used to calculate the
- * <a href="AutofillService.html#FieldClassification">field classification</a> score between an
- * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value
- * predicted by an autofill service.
- */
-public interface Scorer {
+syntax = "proto2";
 
+package android.os;
+
+option java_multiple_files = true;
+
+message BatteryTypeProto {
+   optional string type = 1;
 }
diff --git a/core/java/android/service/autofill/Scorer.java b/core/proto/android/os/bundle.proto
similarity index 61%
copy from core/java/android/service/autofill/Scorer.java
copy to core/proto/android/os/bundle.proto
index c401855..6990281 100644
--- a/core/java/android/service/autofill/Scorer.java
+++ b/core/proto/android/os/bundle.proto
@@ -13,16 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.service.autofill;
 
-/**
- * Helper class used to calculate a score.
- *
- * <p>Typically used to calculate the
- * <a href="AutofillService.html#FieldClassification">field classification</a> score between an
- * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value
- * predicted by an autofill service.
- */
-public interface Scorer {
+syntax = "proto2";
+package android.os;
 
+option java_multiple_files = true;
+
+// An android.os.Bundle object.
+message BundleProto {
+    oneof data {
+        int32 parcelled_data_size = 1;
+        string map_data = 2;
+    }
 }
diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto
index 522ff24..cd151e2 100644
--- a/core/proto/android/os/cpuinfo.proto
+++ b/core/proto/android/os/cpuinfo.proto
@@ -81,7 +81,9 @@
         optional string virt = 8;   // virtual memory size, i.e. 14.0G, 13.5M
         optional string res = 9;    // Resident size, i.e. 0, 3.1G
 
-        // How Android memory manager will treat the task
+        // How Android memory manager will treat the task.
+        // TODO: use PsDumpProto.Process.Policy instead once we extern variables
+        // and are able to include the same .h file in two files.
         enum Policy {
             POLICY_UNKNOWN = 0;
             POLICY_fg = 1;  // foreground, the name is lower case for parsing the value
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index ac8f26d..2752a7e 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -18,17 +18,20 @@
 option java_multiple_files = true;
 option java_outer_classname = "IncidentProtoMetadata";
 
+import "frameworks/base/core/proto/android/os/batterytype.proto";
 import "frameworks/base/core/proto/android/os/cpufreq.proto";
 import "frameworks/base/core/proto/android/os/cpuinfo.proto";
 import "frameworks/base/core/proto/android/os/incidentheader.proto";
 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/os/ps.proto";
 import "frameworks/base/core/proto/android/os/system_properties.proto";
 import "frameworks/base/core/proto/android/providers/settings.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/jobscheduler.proto";
 import "frameworks/base/core/proto/android/server/powermanagerservice.proto";
 import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
 import "frameworks/base/core/proto/android/service/appwidget.proto";
@@ -41,6 +44,8 @@
 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";
+import "frameworks/base/core/proto/android/util/event_log_tags.proto";
+import "frameworks/base/core/proto/android/util/log.proto";
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
 import "frameworks/base/libs/incident/proto/android/section.proto";
 
@@ -57,6 +62,8 @@
 // the sections are able to be controlled and configured by section ids.
 // Instead privacy field options need to be configured in each section proto message.
 message IncidentProto {
+    reserved 1001;
+
     // Incident header from callers
     repeated IncidentHeaderProto header = 1;
     // Internal metadata of incidentd
@@ -65,7 +72,53 @@
     // Device information
     optional SystemPropertiesProto system_properties = 1000 [
         (section).type = SECTION_COMMAND,
-        (section).args = "/system/bin/getprop"
+        (section).args = "getprop"
+    ];
+
+    // Device Logs
+    optional android.util.EventLogTagMapProto event_log_tag_map = 1100 [
+        (section).type = SECTION_FILE,
+        (section).args = "/system/etc/event-log-tags"
+    ];
+
+    optional android.util.LogProto main_logs = 1101 [
+        (section).type = SECTION_LOG,
+        (section).args = "LOG_ID_MAIN"
+    ];
+
+    optional android.util.LogProto radio_logs = 1102 [
+        (section).type = SECTION_LOG,
+        (section).args = "LOG_ID_RADIO"
+    ];
+
+    optional android.util.LogProto events_logs = 1103 [
+        (section).type = SECTION_LOG,
+        (section).args = "LOG_ID_EVENTS"
+    ];
+
+    optional android.util.LogProto system_logs = 1104 [
+        (section).type = SECTION_LOG,
+        (section).args = "LOG_ID_SYSTEM"
+    ];
+
+    optional android.util.LogProto crash_logs = 1105 [
+        (section).type = SECTION_LOG,
+        (section).args = "LOG_ID_CRASH"
+    ];
+
+    optional android.util.LogProto stats_logs = 1106 [
+        (section).type = SECTION_LOG,
+        (section).args = "LOG_ID_STATS"
+    ];
+
+    optional android.util.LogProto security_logs = 1107 [
+        (section).type = SECTION_LOG,
+        (section).args = "LOG_ID_SECURITY"
+    ];
+
+    optional android.util.LogProto kernel_logs = 1108 [
+        (section).type = SECTION_LOG,
+        (section).args = "LOG_ID_KERNEL"
     ];
 
     // Linux services
@@ -86,7 +139,7 @@
 
     optional CpuInfo cpu_info = 2003 [
         (section).type = SECTION_COMMAND,
-        (section).args = "/system/bin/top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
+        (section).args = "top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
     ];
 
     optional CpuFreq cpu_freq = 2004 [
@@ -94,6 +147,16 @@
         (section).args = "/sys/devices/system/cpu/cpufreq/all_time_in_state"
     ];
 
+    optional PsDumpProto processes_and_threads = 2005 [
+        (section).type = SECTION_COMMAND,
+        (section).args = "ps -A -T -Z -O pri,nice,rtprio,sched,pcy,time"
+    ];
+
+    optional BatteryTypeProto battery_type = 2006 [
+        (section).type = SECTION_FILE,
+        (section).args = "/sys/class/power_supply/bms/battery_type"
+    ];
+
     // System Services
     optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
         (section).type = SECTION_DUMPSYS,
@@ -184,4 +247,9 @@
         (section).type = SECTION_DUMPSYS,
         (section).args = "graphicsstats --proto"
     ];
+
+    optional com.android.server.job.JobSchedulerServiceDumpProto jobscheduler = 3020 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "jobscheduler --proto"
+    ];
 }
diff --git a/core/java/android/service/autofill/Scorer.java b/core/proto/android/os/persistablebundle.proto
similarity index 61%
copy from core/java/android/service/autofill/Scorer.java
copy to core/proto/android/os/persistablebundle.proto
index c401855..75ff787 100644
--- a/core/java/android/service/autofill/Scorer.java
+++ b/core/proto/android/os/persistablebundle.proto
@@ -13,16 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.service.autofill;
 
-/**
- * Helper class used to calculate a score.
- *
- * <p>Typically used to calculate the
- * <a href="AutofillService.html#FieldClassification">field classification</a> score between an
- * actual {@link android.view.autofill.AutofillValue} filled by the user and the expected value
- * predicted by an autofill service.
- */
-public interface Scorer {
+syntax = "proto2";
+package android.os;
 
+option java_multiple_files = true;
+
+// An android.os.PersistableBundle object.
+message PersistableBundleProto {
+    oneof data {
+        int32 parcelled_data_size = 1;
+        string map_data = 2;
+    }
 }
diff --git a/core/proto/android/os/ps.proto b/core/proto/android/os/ps.proto
new file mode 100644
index 0000000..88c6609
--- /dev/null
+++ b/core/proto/android/os/ps.proto
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 PsDumpProto {
+    message Process {
+        // Security label, most commonly used for SELinux context data.
+        optional string label = 1;
+        optional string user = 2;
+        // Process ID number.
+        optional int32 pid = 3;
+        // The unique number representing a dispatchable entity (alias lwp,
+        // spid).  This value may also appear as: a process ID (pid); a process
+        // group ID (pgrp); a session ID for the session leader (sid); a thread
+        // group ID for the thread group leader (tgid); and a tty process group
+        // ID for the process group leader (tpgid).
+        optional int32 tid = 4;
+        // Parent process ID.
+        optional int32 ppid = 5;
+        // Virtual set size (memory size) of the process, in KiB.
+        optional int32 vsz = 6;
+        // Resident set size. How many physical pages are associated with the
+        // process; real memory usage, in KiB.
+        optional int32 rss = 7;
+        // Name of the kernel function in which the process is sleeping, a "-"
+        // if the process is running, or a "*" if the process is multi-threaded
+        // and ps is not displaying threads.
+        optional string wchan = 8;
+        // Memory address of the process.
+        optional string addr = 9;
+
+        enum ProcessStateCode {
+            STATE_UNKNOWN = 0;
+            // Uninterruptible sleep (usually IO).
+            STATE_D = 1;
+            // Running or runnable (on run queue).
+            STATE_R = 2;
+            // Interruptible sleep (waiting for an event to complete).
+            STATE_S = 3;
+            // Stopped by job control signal.
+            STATE_T = 4;
+            // Stopped by debugger during the tracing.
+            STATE_TRACING = 5;
+            // Dead (should never be seen).
+            STATE_X = 6;
+            // Defunct ("zombie") process. Terminated but not reaped by its
+            // parent.
+            STATE_Z = 7;
+        }
+        // Minimal state display
+        optional ProcessStateCode s = 10;
+        // Priority of the process. Higher number means lower priority.
+        optional int32 pri = 11;
+        // Nice value. This ranges from 19 (nicest) to -20 (not nice to others).
+        optional sint32 ni = 12;
+        // Realtime priority.
+        optional string rtprio = 13; // Number or -
+
+        enum SchedulingPolicy {
+            option allow_alias = true;
+
+            // Regular names conflict with macros defined in
+            // bionic/libc/kernel/uapi/linux/sched.h.
+            SCH_OTHER = 0;
+            SCH_NORMAL = 0;
+
+            SCH_FIFO = 1;
+            SCH_RR = 2;
+            SCH_BATCH = 3;
+            SCH_ISO = 4;
+            SCH_IDLE = 5;
+        }
+        // Scheduling policy of the process.
+        optional SchedulingPolicy sch = 14;
+
+        // How Android memory manager will treat the task
+        enum Policy {
+            POLICY_UNKNOWN = 0;
+            // Foreground.
+            POLICY_FG = 1;
+            // Background.
+            POLICY_BG = 2;
+            POLICY_TA = 3;  // TODO: figure out what this value is
+        }
+        optional Policy pcy = 15;
+        // Total CPU time, "[DD-]HH:MM:SS" format.
+        optional string time = 16;
+        // Command with all its arguments as a string.
+        optional string cmd = 17;
+    }
+    repeated Process processes = 1;
+}
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index a51253f..59582ec 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -327,7 +327,7 @@
         optional string crypto_state = 21;
         optional string crypto_type = 22;
         optional string dalvik_vm_native_bridge = 23;
-        optional int32  debuggable = 24;
+        optional bool  debuggable = 24;
         optional string frp_pst = 25;
         optional string gfx_driver_0 = 26;
 
@@ -413,19 +413,29 @@
         optional string revision = 35;
         optional int32  sf_lcd_density = 36;
         optional bool   storage_manager_enabled = 37;
-        optional bool   telephony_call_ring_multiple = 38;
-        optional int32  telephony_default_cdma_sub = 39;
-        optional int32  telephony_default_network = 40;
-        optional string url_legal = 41;
-        optional string url_legal_android_privacy = 42;
-        optional string vendor_build_date = 43;
-        optional int32  vendor_build_date_utc = 44;
-        optional string vendor_build_fingerprint = 45;
-        optional string vndk_version = 46;
-        optional int32  vts_coverage = 47;
-        optional string zygote = 48;
 
-        // Next Tag: 49
+        message Telephony {
+            optional bool  call_ring_multiple = 1;
+            optional int32 default_cdma_sub = 2;
+            optional int32 default_network = 3;
+        }
+        optional Telephony telephony = 38;
+
+        optional string url_legal = 39;
+        optional string url_legal_android_privacy = 40;
+
+        message Vendor {
+            optional string build_date = 1;
+            optional int32  build_date_utc = 2;
+            optional string build_fingerprint = 3;
+        }
+        optional Vendor vendor = 41;
+
+        optional string vndk_version = 42;
+        optional int32  vts_coverage = 43;
+        optional string zygote = 44;
+
+        // Next Tag: 45
     }
     optional Ro ro = 21;
 
diff --git a/core/proto/android/os/worksource.proto b/core/proto/android/os/worksource.proto
index c60edfc..2f8b2fb 100644
--- a/core/proto/android/os/worksource.proto
+++ b/core/proto/android/os/worksource.proto
@@ -25,5 +25,10 @@
         optional string name = 2;
     }
 
+    message WorkChain {
+      repeated WorkSourceContentProto nodes = 1;
+    }
+
     repeated WorkSourceContentProto work_source_contents = 1;
-}
\ No newline at end of file
+    repeated WorkChain work_chains = 2;
+}
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index fb0ebed..f5d098c 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -582,7 +582,7 @@
     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;
+    reserved 165; // Removed demo_user_setup_complete
     optional SettingProto instant_apps_enabled = 166;
     optional SettingProto device_paired = 167;
     optional SettingProto package_verifier_state = 191;
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
new file mode 100644
index 0000000..f72ca62
--- /dev/null
+++ b/core/proto/android/server/jobscheduler.proto
@@ -0,0 +1,561 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.job;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/app/jobparameters.proto";
+import "frameworks/base/core/proto/android/content/clipdata.proto";
+import "frameworks/base/core/proto/android/content/component_name.proto";
+import "frameworks/base/core/proto/android/content/intent.proto";
+import "frameworks/base/core/proto/android/net/network.proto";
+import "frameworks/base/core/proto/android/net/networkrequest.proto";
+import "frameworks/base/core/proto/android/os/bundle.proto";
+import "frameworks/base/core/proto/android/os/persistablebundle.proto";
+import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto";
+
+message JobSchedulerServiceDumpProto {
+    optional ConstantsProto settings = 1;
+
+    repeated int32 started_users = 2;
+
+    message RegisteredJob {
+        optional JobStatusShortInfoProto info = 1;
+        optional JobStatusDumpProto dump = 2;
+
+        // A job is ready to be executed if:
+        // is_job_ready && is_user_started && !is_job_pending &&
+        // !is_job_currently_active && !is_uid_backing_up &&
+        // is_component_present.
+        optional bool is_job_ready = 3;
+        optional bool is_user_started = 4;
+        optional bool is_job_pending = 5;
+        optional bool is_job_currently_active = 6;
+        optional bool is_uid_backing_up = 7;
+        optional bool is_component_present = 8;
+    }
+    repeated RegisteredJob registered_jobs = 3;
+
+    repeated StateControllerProto controllers = 4;
+
+    // Which uids are currently in the foreground.
+    message PriorityOverride {
+        optional int32 uid = 1;
+        // Use sint32 instead of an enum since priorities can technically be
+        // negative.
+        optional sint32 override_value = 2;
+    }
+    repeated PriorityOverride priority_overrides = 5;
+
+    // UIDs that are currently performing backups, so their jobs won't be
+    // allowed to run.
+    repeated int32 backing_up_uids = 6;
+
+    optional JobPackageHistoryProto history = 7;
+    optional JobPackageTrackerDumpProto package_tracker = 8;
+
+    message PendingJob {
+        optional JobStatusShortInfoProto info = 1;
+        optional JobStatusDumpProto dump = 2;
+        optional sint32 evaluated_priority = 3;
+        // How long this job has been pending.
+        optional int64 enqueued_duration_ms = 4;
+    }
+    repeated PendingJob pending_jobs = 9;
+
+    // From a service that has currently active or pending jobs.
+    message ActiveJob {
+        message InactiveJob {
+            optional int64 time_since_stopped_ms = 1;
+            // This is not always provided.
+            optional string stopped_reason = 2;
+        }
+        message RunningJob {
+            optional JobStatusShortInfoProto info = 1;
+            // How long this job has been running for.
+            optional int64 running_duration_ms = 2;
+            optional int64 time_until_timeout_ms = 3;
+
+            optional JobStatusDumpProto dump = 4;
+
+            optional sint32 evaluated_priority = 5;
+
+            optional int64 time_since_made_active_ms = 6;
+            // How long this job has been pending.
+            optional int64 pending_duration_ms = 7;
+        }
+        oneof job {
+            InactiveJob inactive = 1;
+            RunningJob running = 2;
+        }
+    }
+    repeated ActiveJob active_jobs = 10;
+
+    // True when JobScheduler is allowed to run third party apps.
+    optional bool is_ready_to_rock = 11;
+    // What was last reported to DeviceIdleController about whether the device
+    // is active.
+    optional bool reported_active = 12;
+    // The current limit on the number of concurrent JobServiceContext entries
+    // we want to keep actively running a job.
+    optional int32 max_active_jobs = 13;
+}
+
+// A com.android.server.job.JobSchedulerService.Constants object.
+message ConstantsProto {
+    // Minimum # of idle jobs that must be ready in order to force the JMS to
+    // schedule things early.
+    optional int32 min_idle_count = 1;
+    // Minimum # of charging jobs that must be ready in order to force the JMS
+    // to schedule things early.
+    optional int32 min_charging_count = 2;
+    // Minimum # of "battery not low" jobs that must be ready in order to force
+    // the JMS to schedule things early.
+    optional int32 min_battery_not_low_count = 3;
+    // Minimum # of "storage not low" jobs that must be ready in order to force
+    // the JMS to schedule things early.
+    optional int32 min_storage_not_low_count = 4;
+    // Minimum # of connectivity jobs that must be ready in order to force the
+    // JMS to schedule things early. 1 == Run connectivity jobs as soon as
+    // ready.
+    optional int32 min_connectivity_count = 5;
+    // Minimum # of content trigger jobs that must be ready in order to force
+    // the JMS to schedule things early.
+    optional int32 min_content_count = 6;
+    // Minimum # of jobs (with no particular constraints) for which the JMS will
+    // be happy running some work early. This (and thus the other min counts)
+    // is now set to 1, to prevent any batching at this level. Since we now do
+    // batching through doze, that is a much better mechanism.
+    optional int32 min_ready_jobs_count = 7;
+    // This is the job execution factor that is considered to be heavy use of
+    // the system.
+    optional double heavy_use_factor = 8;
+    // This is the job execution factor that is considered to be moderate use of
+    // the system.
+    optional double moderate_use_factor = 9;
+    // The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app.
+    optional int32 fg_job_count = 10;
+    // The maximum number of background jobs we allow when the system is in a
+    // normal memory state.
+    optional int32 bg_normal_job_count = 11;
+    // The maximum number of background jobs we allow when the system is in a
+    // moderate memory state.
+    optional int32 bg_moderate_job_count = 12;
+    // The maximum number of background jobs we allow when the system is in a
+    // low memory state.
+    optional int32 bg_low_job_count = 13;
+    // The maximum number of background jobs we allow when the system is in a
+    // critical memory state.
+    optional int32 bg_critical_job_count = 14;
+    // The maximum number of times we allow a job to have itself rescheduled
+    // before giving up on it, for standard jobs.
+    optional int32 max_standard_reschedule_count = 15;
+    // The maximum number of times we allow a job to have itself rescheduled
+    // before giving up on it, for jobs that are executing work.
+    optional int32 max_work_reschedule_count = 16;
+    // The minimum backoff time to allow for linear backoff.
+    optional int64 min_linear_backoff_time_ms = 17;
+    // The minimum backoff time to allow for exponential backoff.
+    optional int64 min_exp_backoff_time_ms = 18;
+    // How often we recalculate runnability based on apps' standby bucket
+    // assignment. This should be prime relative to common time interval lengths
+    // such as a quarter-hour or day, so that the heartbeat drifts relative to
+    // wall-clock milestones.
+    optional int64 standby_heartbeat_time_ms = 19;
+    // Mapping: standby bucket -> number of heartbeats between each sweep of
+    // that bucket's jobs.
+    // Bucket assignments as recorded in the JobStatus objects are normalized to
+    // be indices into this array, rather than the raw constants used by
+    // AppIdleHistory.
+    repeated int32 standby_beats = 20;
+}
+
+message StateControllerProto {
+    message AppIdleController {
+        optional bool is_parole_on = 1;
+
+        message TrackedJob {
+            optional JobStatusShortInfoProto info = 1;
+            optional int32 source_uid = 2;
+            optional string source_package_name = 3;
+            // If the constraints are satisfied, then the controller will mark
+            // the job as RUNNABLE, otherwise, it will be WAITING.
+            optional bool are_constraints_satisfied = 4;
+        }
+        repeated TrackedJob tracked_jobs = 2;
+    }
+    message BackgroundJobsController {
+        optional com.android.server.ForceAppStandbyTrackerProto force_app_standby_tracker = 1;
+
+        message TrackedJob {
+            optional JobStatusShortInfoProto info = 1;
+            optional int32 source_uid = 2;
+            optional string source_package_name = 3;
+            optional bool is_in_foreground = 4;
+            optional bool is_whitelisted = 5;
+            optional bool can_run_any_in_background = 6;
+            // If the constraints are satisfied, then the controller will mark
+            // the job as RUNNABLE, otherwise, it will be WAITING.
+            optional bool are_constraints_satisfied = 7;
+        }
+        repeated TrackedJob tracked_jobs = 2;
+    }
+    message BatteryController {
+        optional bool is_on_stable_power = 1;
+        optional bool is_battery_not_low = 2;
+
+        // Whether or not the controller is monitoring battery changes.
+        optional bool is_monitoring = 3;
+        // Only valid if is_monitoring is true.
+        optional int32 last_broadcast_sequence_number = 4;
+
+        message TrackedJob {
+            optional JobStatusShortInfoProto info = 1;
+            optional int32 source_uid = 2;
+        }
+        repeated TrackedJob tracked_jobs = 5;
+    }
+    message ConnectivityController {
+        optional bool is_connected = 1;
+
+        message TrackedJob {
+            optional JobStatusShortInfoProto info = 1;
+            optional int32 source_uid = 2;
+            optional .android.net.NetworkRequestProto required_network = 3;
+        }
+        repeated TrackedJob tracked_jobs = 2;
+    }
+    message ContentObserverController {
+        message TrackedJob {
+            optional JobStatusShortInfoProto info = 1;
+            optional int32 source_uid = 2;
+        }
+        repeated TrackedJob tracked_jobs = 1;
+
+        message Observer {
+            optional int32 user_id = 1;
+
+            message TriggerContentData {
+                optional string uri = 1;
+                optional int32 flags = 2;
+
+                // A
+                // com.android.server.job.controllers.ContentObserverController.JobInstance
+                // object.
+                message JobInstance {
+                    optional JobStatusShortInfoProto info = 1;
+                    optional int32 source_uid = 2;
+
+                    optional int64 trigger_content_update_delay_ms = 3;
+                    optional int64 trigger_content_max_delay_ms = 4;
+
+                    repeated string changed_authorities = 5;
+                    repeated string changed_uris = 6;
+                }
+                repeated JobInstance jobs = 3;
+            }
+            repeated TriggerContentData triggers = 2;
+        }
+        repeated Observer observers = 2;
+    }
+    message DeviceIdleJobsController {
+        // True when in device idle mode.
+        optional bool is_device_idle_mode = 1;
+
+        message TrackedJob {
+            optional JobStatusShortInfoProto info = 1;
+            optional int32 source_uid = 2;
+            optional string source_package_name = 3;
+            // If the constraints are satisfied, then the controller will mark
+            // the job as RUNNABLE, otherwise, it will be WAITING.
+            optional bool are_constraints_satisfied = 4;
+            optional bool is_doze_whitelisted = 5;
+            // A job that is exempted from Doze when the app is temp whitelisted
+            // or in the foreground.
+            optional bool is_allowed_in_doze = 6;
+        }
+        repeated TrackedJob tracked_jobs = 2;
+    }
+    message IdleController {
+        optional bool is_idle = 1;
+
+        message TrackedJob {
+            optional JobStatusShortInfoProto info = 1;
+            optional int32 source_uid = 2;
+        }
+        repeated TrackedJob tracked_jobs = 2;
+    }
+    message StorageController {
+        optional bool is_storage_not_low = 1;
+        optional int32 last_broadcast_sequence_number = 2;
+
+        message TrackedJob {
+            optional JobStatusShortInfoProto info = 1;
+            optional int32 source_uid = 2;
+        }
+        repeated TrackedJob tracked_jobs = 3;
+    }
+    message TimeController {
+        optional int64 now_elapsed_realtime = 1;
+        optional int64 time_until_next_delay_alarm_ms = 2;
+        optional int64 time_until_next_deadline_alarm_ms = 3;
+
+        message TrackedJob {
+            optional JobStatusShortInfoProto info = 1;
+            optional int32 source_uid = 2;
+
+            optional bool has_timing_delay_constraint = 3;
+            // Only valid if has_timing_delay_constraint is true. Can be
+            // negative if the delay is in the past.
+            optional int64 delay_time_remaining_ms = 4;
+
+            optional bool has_deadline_constraint = 5;
+            // Only valid if has_timing_delay_constraint is true. Can be
+            // negative in certain situations.
+            optional int64 time_remaining_until_deadline_ms = 6;
+        }
+        repeated TrackedJob tracked_jobs = 4;
+    }
+    oneof controller {
+        AppIdleController app_idle = 1;
+        BackgroundJobsController background = 2;
+        BatteryController battery = 3;
+        ConnectivityController connectivity = 4;
+        ContentObserverController content_observer = 5;
+        DeviceIdleJobsController device_idle = 6;
+        IdleController idle = 7;
+        StorageController storage = 8;
+        TimeController time = 9;
+    }
+}
+
+// A com.android.server.job.JobPackageTracker.DataSet object.
+message DataSetProto {
+    optional int64 start_clock_time_ms = 1;
+    // How much time has elapsed since the DataSet was instantiated.
+    optional int64 elapsed_time_ms = 2;
+    optional int64 period_ms = 3;
+
+    // Represents a com.android.server.job.JobPackageTracker.PackageEntry
+    // object, but with some extra data.
+    message PackageEntryProto {
+        optional int32 uid = 1;
+        optional string package_name = 2;
+
+        message State {
+            optional int64 duration_ms = 1;
+            optional int32 count = 2;
+        }
+        optional State pending_state = 3;
+        optional State active_state = 4;
+        optional State active_top_state = 5;
+
+        // True if the PackageEntry is currently pending or has been pending in
+        // the past.
+        optional bool pending = 6;
+        // True if the PackageEntry is currently active or has been active in
+        // the past.
+        optional bool active = 7;
+        // True if the PackageEntry is currently active top or has been active
+        // top in the past.
+        optional bool active_top = 8;
+
+        message StopReasonCount {
+            optional .android.app.JobParametersProto.CancelReason reason = 1;
+            optional int32 count = 2;
+        }
+        repeated StopReasonCount stop_reasons = 9;
+    }
+    repeated PackageEntryProto package_entries = 4;
+
+    optional int32 max_concurrency = 5;
+    optional int32 max_foreground_concurrency = 6;
+}
+
+// Dump from com.android.server.job.GrantedUriPermissions.
+message GrantedUriPermissionsDumpProto {
+    optional int32 flags = 1;
+    optional int32 source_user_id = 2;
+    optional string tag = 3;
+    optional string permission_owner = 4;
+    repeated string uris = 5;
+}
+
+message JobPackageTrackerDumpProto {
+    repeated DataSetProto historical_stats = 1;
+    optional DataSetProto current_stats = 2;
+}
+
+message JobPackageHistoryProto {
+    enum Event {
+        UNKNOWN = 0;
+        START_JOB = 1;
+        STOP_JOB = 2;
+        START_PERIODIC_JOB = 3;
+        STOP_PERIODIC_JOB = 4;
+    }
+    message HistoryEvent {
+        optional Event event = 1;
+        optional int64 time_since_event_ms = 2;
+        optional int32 uid = 3;
+        // Job IDs can technically be negative.
+        optional int32 job_id = 4;
+        optional string tag = 5;
+        // Only valid for STOP_JOB or STOP_PERIODIC_JOB Events.
+        optional .android.app.JobParametersProto.CancelReason stop_reason = 6;
+    }
+    repeated HistoryEvent history_event = 1;
+}
+
+message JobStatusShortInfoProto {
+    optional int32 calling_uid = 1;
+    // Job IDs can technically be negative.
+    optional int32 job_id = 2;
+    optional string battery_name = 3;
+}
+
+// Dump from a com.android.server.job.controllers.JobStatus object.
+message JobStatusDumpProto {
+    // The UID that scheduled the job.
+    optional int32 calling_uid = 1;
+    optional string tag = 2;
+
+    // The UID for which the job is being run.
+    optional int32 source_uid = 3;
+    optional int32 source_user_id = 4;
+    // The package for which the job is being run.
+    optional string source_package_name = 5;
+
+    // Custom dump of android.app.job.JobInfo object.
+    message JobInfo {
+        optional .android.content.ComponentNameProto service = 1;
+
+        optional bool is_periodic = 2;
+        // Only valid if is_periodic is true.
+        optional int64 period_interval_ms = 3;
+        // Only valid if is_periodic is true.
+        optional int64 period_flex_ms = 4;
+
+        optional bool is_persisted = 5;
+        optional sint32 priority = 6;
+        optional int32 flags = 7;
+
+        optional bool requires_charging = 8;
+        optional bool requires_battery_not_low = 9;
+        optional bool requires_device_idle = 10;
+
+        message TriggerContentUri {
+            optional int32 flags = 1;
+            optional string uri = 2;
+        }
+        repeated TriggerContentUri trigger_content_uris = 11;
+        optional int64 trigger_content_update_delay_ms = 12;
+        optional int64 trigger_content_max_delay_ms = 13;
+
+        optional .android.os.PersistableBundleProto extras = 14;
+        optional .android.os.BundleProto transient_extras = 15;
+        optional .android.content.ClipDataProto clip_data = 16;
+
+        optional GrantedUriPermissionsDumpProto granted_uri_permissions = 17;
+
+        optional .android.net.NetworkRequestProto required_network = 18;
+
+        optional int64 total_network_bytes = 19;
+
+        optional int64 min_latency_ms = 20;
+        optional int64 max_execution_delay_ms = 21;
+
+        message Backoff {
+            enum Policy {
+                BACKOFF_POLICY_LINEAR = 0;
+                BACKOFF_POLICY_EXPONENTIAL = 1;
+            }
+            optional Policy policy = 1;
+            optional int64 initial_backoff_ms = 2;
+        }
+        optional Backoff backoff_policy = 22;
+
+        optional bool has_early_constraint = 23;
+        optional bool has_late_constraint = 24;
+    }
+    optional JobInfo job_info = 6;
+
+    enum Constraint {
+        CONSTRAINT_CHARGING = 1;
+        CONSTRAINT_BATTERY_NOT_LOW = 2;
+        CONSTRAINT_STORAGE_NOT_LOW = 3;
+        CONSTRAINT_TIMING_DELAY = 4;
+        CONSTRAINT_DEADLINE = 5;
+        CONSTRAINT_IDLE = 6;
+        CONSTRAINT_CONNECTIVITY = 7;
+        CONSTRAINT_APP_NOT_IDLE = 8;
+        CONSTRAINT_CONTENT_TRIGGER = 9;
+        CONSTRAINT_DEVICE_NOT_DOZING = 10;
+    }
+    repeated Constraint required_constraints = 7;
+    repeated Constraint satisfied_constraints = 8;
+    repeated Constraint unsatisfied_constraints = 9;
+    optional bool is_doze_whitelisted = 10;
+
+    enum TrackingController {
+        TRACKING_BATTERY = 0;
+        TRACKING_CONNECTIVITY = 1;
+        TRACKING_CONTENT = 2;
+        TRACKING_IDLE = 3;
+        TRACKING_STORAGE = 4;
+        TRACKING_TIME = 5;
+    }
+    // Controllers that are currently tracking the job.
+    repeated TrackingController tracking_controllers = 11;
+
+    repeated string changed_authorities = 12;
+    repeated string changed_uris = 13;
+
+    optional .android.net.NetworkProto network = 14;
+
+    // Only the desired data from an android.app.job.JobWorkItem object.
+    message JobWorkItem {
+        optional int32 work_id = 1;
+        optional int32 delivery_count = 2;
+        optional .android.content.IntentProto intent = 3;
+        optional GrantedUriPermissionsDumpProto uri_grants = 4;
+    }
+    repeated JobWorkItem pending_work = 15;
+    repeated JobWorkItem executing_work = 16;
+
+    enum Bucket {
+        ACTIVE = 0;
+        WORKING_SET = 1;
+        FREQUENT = 2;
+        RARE = 3;
+        NEVER = 4;
+    }
+    optional Bucket standby_bucket = 17;
+
+    optional int64 enqueue_duration_ms = 18;
+    // Can be negative if the earliest runtime deadline has passed.
+    optional sint64 time_until_earliest_runtime_ms = 19;
+    // Can be negative if the latest runtime deadline has passed.
+    optional sint64 time_until_latest_runtime_ms = 20;
+
+    optional int32 num_failures = 21;
+
+    optional int64 last_successful_run_time = 22;
+    optional int64 last_failed_run_time = 23;
+}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index ef7b6b0..12aca78 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -18,6 +18,7 @@
 
 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/displaycutout.proto";
 import "frameworks/base/core/proto/android/view/displayinfo.proto";
 import "frameworks/base/core/proto/android/view/windowlayoutparams.proto";
 
@@ -172,6 +173,27 @@
   repeated WindowStateProto child_windows = 15;
   optional .android.graphics.RectProto surface_position = 16;
   optional .android.graphics.RectProto shown_position = 17;
+  optional int32 requested_width = 18;
+  optional int32 requested_height = 19;
+  optional int32 view_visibility = 20;
+  optional int32 system_ui_visibility = 21;
+  optional bool has_surface = 22;
+  optional bool is_ready_for_display = 23;
+  optional .android.graphics.RectProto display_frame = 24;
+  optional .android.graphics.RectProto overscan_frame = 25;
+  optional .android.graphics.RectProto visible_frame = 26;
+  optional .android.graphics.RectProto decor_frame = 27;
+  optional .android.graphics.RectProto outset_frame = 28;
+  optional .android.graphics.RectProto overscan_insets = 29;
+  optional .android.graphics.RectProto visible_insets = 30;
+  optional .android.graphics.RectProto stable_insets = 31;
+  optional .android.graphics.RectProto outsets = 32;
+  optional .android.view.DisplayCutoutProto cutout = 33;
+  optional bool remove_on_exit = 34;
+  optional bool destroying = 35;
+  optional bool removed = 36;
+  optional bool is_on_screen = 37;
+  optional bool is_visible = 38;
 }
 
 message IdentifierProto {
@@ -184,6 +206,15 @@
 message WindowStateAnimatorProto {
   optional .android.graphics.RectProto last_clip_rect = 1;
   optional WindowSurfaceControllerProto surface = 2;
+  enum DrawState {
+    NO_SURFACE = 0;
+    DRAW_PENDING = 1;
+    COMMIT_DRAW_PENDING = 2;
+    READY_TO_SHOW = 3;
+    HAS_DRAWN = 4;
+  }
+  optional DrawState draw_state = 3;
+  optional .android.graphics.RectProto system_decor_rect = 4;
 }
 
 /* represents WindowSurfaceController */
diff --git a/core/proto/android/util/event_log_tags.proto b/core/proto/android/util/event_log_tags.proto
new file mode 100644
index 0000000..cb039be
--- /dev/null
+++ b/core/proto/android/util/event_log_tags.proto
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+// Proto representation of event.logtags.
+// Usually sit in /system/etc/event-log-tags.
+message EventLogTagMapProto {
+    repeated EventLogTag event_log_tags = 1;
+}
+
+message EventLogTag {
+    optional uint32 tag_number = 1; // keyed by tag number.
+    optional string tag_name = 2;
+
+    message ValueDescriptor {
+        optional string name = 1;
+
+        enum DataType {
+            UNKNOWN = 0;
+            INT = 1;
+            LONG = 2;
+            STRING = 3;
+            LIST = 4;
+            FLOAT = 5;
+        }
+        optional DataType type = 2;
+
+        enum DataUnit {
+            UNSET = 0;          // this field is optional, so default is unset
+            OBJECTS = 1;        // Number of objects
+            BYTES = 2;          // Number of bytes (default for type of int/long)
+            MILLISECONDS = 3;   // Number of milliseconds
+            ALLOCATIONS = 4;    // Number of allocations
+            ID = 5;             // Id
+            PERCENT = 6;        // Percent
+            SECONDS = 115;      // 's', Number of seconds (monotonic time)
+        }
+        optional DataUnit unit = 3;
+    }
+    repeated ValueDescriptor value_descriptors = 3;
+}
\ No newline at end of file
diff --git a/core/proto/android/util/log.proto b/core/proto/android/util/log.proto
new file mode 100644
index 0000000..30ff412
--- /dev/null
+++ b/core/proto/android/util/log.proto
@@ -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.
+ */
+
+syntax = "proto2";
+package android.util;
+
+option java_multiple_files = true;
+
+// Represents a Text Log in logd
+// Next Tag: 9
+message TextLogEntry {
+    optional uint64 sec = 1;
+    optional uint64 nanosec = 2;
+
+    enum LogPriority {
+        LOG_UNKNOWN = 0;
+        LOG_DEFAULT = 1;
+        LOG_VERBOSE = 2;
+        LOG_DEBUG   = 3;
+        LOG_INFO    = 4;
+        LOG_WARN    = 5;
+        LOG_ERROR   = 6;
+        LOG_FATAL   = 7;
+        LOG_SILENT  = 8;
+    }
+    optional LogPriority priority = 3;
+    optional int32 uid = 4;
+    optional int32 pid = 5;
+    optional int32 tid = 6;
+    optional string tag = 7;
+    optional string log = 8;
+}
+
+// Represents a Binary Log in logd, need to look event-log-tags for more info.
+// Next Tag: 8
+message BinaryLogEntry {
+    optional uint64 sec = 1;
+    optional uint64 nanosec = 2;
+    optional int32 uid = 3;
+    optional int32 pid = 4;
+    optional int32 tid = 5;
+
+    // Index of the event tag, can look up in event-log-tags file
+    optional uint32 tag_index = 6;
+
+    message Elem {
+        // must be sync with liblog log/log.h
+        enum Type {
+            EVENT_TYPE_LIST_STOP = 10; // '\n'
+            EVENT_TYPE_UNKNOWN = 63; // '?'
+
+            EVENT_TYPE_INT = 0;
+            EVENT_TYPE_LONG = 1;
+            EVENT_TYPE_STRING = 2;
+            EVENT_TYPE_LIST = 3;
+            EVENT_TYPE_FLOAT = 4;
+        }
+        optional Type type = 1 [default=EVENT_TYPE_UNKNOWN];
+
+        oneof value {
+            int32 val_int32 = 2;
+            int64 val_int64 = 3;
+            string val_string = 4;
+            float val_float = 5;
+        }
+    }
+    repeated Elem elems = 7;
+}
+
+message LogProto {
+    repeated TextLogEntry text_logs = 1;
+
+    repeated BinaryLogEntry binary_logs = 2;
+}
+
diff --git a/core/proto/android/view/display.proto b/core/proto/android/view/display.proto
index 210c6d1..cac0830 100644
--- a/core/proto/android/view/display.proto
+++ b/core/proto/android/view/display.proto
@@ -38,4 +38,16 @@
         // The display is on and optimized for VR mode.
         DISPLAY_STATE_VR = 5;
     }
+    enum ColorMode {
+        COLOR_MODE_INVALID = -1;
+        COLOR_MODE_BT601_625 = 1;
+        COLOR_MODE_BT601_625_UNADJUSTED = 2;
+        COLOR_MODE_BT601_525 = 3;
+        COLOR_MODE_BT601_525_UNADJUSTED = 4;
+        COLOR_MODE_BT709 = 5;
+        COLOR_MODE_DCI_P3 = 6;
+        COLOR_MODE_SRGB = 7;
+        COLOR_MODE_ADOBE_RGB = 8;
+        COLOR_MODE_DISPLAY_P3 = 9;
+    }
 }
diff --git a/core/proto/android/view/displaycutout.proto b/core/proto/android/view/displaycutout.proto
new file mode 100644
index 0000000..ff13fab
--- /dev/null
+++ b/core/proto/android/view/displaycutout.proto
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+
+package android.view;
+option java_multiple_files = true;
+
+message DisplayCutoutProto {
+  optional .android.graphics.RectProto insets = 1;
+  optional .android.graphics.RectProto bounds = 2;
+}
diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto
index 7821212..b81cd1f 100644
--- a/core/proto/android/view/windowlayoutparams.proto
+++ b/core/proto/android/view/windowlayoutparams.proto
@@ -15,11 +15,51 @@
  */
 
 syntax = "proto2";
-package android.view;
 
+import "frameworks/base/core/proto/android/graphics/pixelformat.proto";
+import "frameworks/base/core/proto/android/view/display.proto";
+
+package android.view;
 option java_multiple_files = true;
 
 /* represents WindowManager.LayoutParams */
 message WindowLayoutParamsProto {
   optional int32 type = 1;
+  optional int32 x = 2;
+  optional int32 y = 3;
+  optional int32 width = 4;
+  optional int32 height = 5;
+  optional float horizontal_margin = 6;
+  optional float vertical_margin = 7;
+  optional int32 gravity = 8;
+  optional int32 soft_input_mode = 9;
+  optional .android.graphics.PixelFormatProto.Format format = 10;
+  optional int32 window_animations = 11;
+  optional float alpha = 12;
+  optional float screen_brightness = 13;
+  optional float button_brightness = 14;
+  enum RotationAnimation {
+    ROTATION_ANIMATION_UNSPECIFIED = -1;
+    ROTATION_ANIMATION_CROSSFADE = 1;
+    ROTATION_ANIMATION_JUMPCUT = 2;
+    ROTATION_ANIMATION_SEAMLESS = 3;
+  }
+  optional RotationAnimation rotation_animation = 15;
+  optional float preferred_refresh_rate = 16;
+  optional int32 preferred_display_mode_id = 17;
+  optional bool has_system_ui_listeners = 18;
+  optional uint32 input_feature_flags = 19;
+  optional int64 user_activity_timeout = 20;
+  enum NeedsMenuState {
+    NEEDS_MENU_UNSET = 0;
+    NEEDS_MENU_SET_TRUE = 1;
+    NEEDS_MENU_SET_FALSE = 2;
+  }
+  optional NeedsMenuState needs_menu_key = 22;
+  optional .android.view.DisplayProto.ColorMode color_mode = 23;
+  optional uint32 flags = 24;
+  optional uint64 flags_extra = 25;
+  optional uint32 private_flags = 26;
+  optional uint32 system_ui_visibility_flags = 27;
+  optional uint32 subtree_system_ui_visibility_flags = 28;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 86d2ee3..b8d2606 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -159,6 +159,8 @@
     <protected-broadcast
         android:name="android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED" />
@@ -173,6 +175,8 @@
     <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED" />
@@ -566,6 +570,7 @@
 
     <!-- Made protected in P (was introduced in JB-MR2) -->
     <protected-broadcast android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
+    <protected-broadcast android:name="android.telephony.euicc.action.OTA_STATUS_CHANGED" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -1797,6 +1802,15 @@
     <permission android:name="android.permission.ALLOCATE_AGGRESSIVE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide
+         Allows an application to use reserved disk space.
+         <p>Not for use by third-party applications.  Should only be requested by
+         apps that provide core system functionality, to ensure system stability
+         when disk is otherwise completely full.
+    -->
+    <permission android:name="android.permission.USE_RESERVED_DISK"
+        android:protectionLevel="signature|privileged" />
+
     <!-- ================================== -->
     <!-- Permissions for screenlock         -->
     <!-- ================================== -->
@@ -3125,6 +3139,12 @@
     <permission android:name="android.permission.BACKUP"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows application to manage RecoverableKeyStoreLoader.
+    <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.RECOVER_KEYSTORE"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows a package to launch the secure full-backup confirmation UI.
          ONLY the system process may hold this permission.
          @hide -->
@@ -3534,11 +3554,19 @@
     @hide -->
     <permission android:name="android.permission.ACCESS_INSTANT_APPS"
             android:protectionLevel="signature|installer|verifier" />
+    <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS"/>
 
     <!-- Allows the holder to view the instant applications on the device.
     @hide -->
     <permission android:name="android.permission.VIEW_INSTANT_APPS"
-            android:protectionLevel="signature|preinstalled" />
+                android:protectionLevel="signature|preinstalled" />
+
+    <!-- Allows the holder to manage whether the system can bind to services
+         provided by instant apps. This permission is intended to protect
+         test/development fucntionality and should be used only in such cases.
+    @hide -->
+    <permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"
+                android:protectionLevel="signature" />
 
     <!-- Allows receiving the usage of media resource e.g. video/audio codec and
          graphic memory.
@@ -3862,6 +3890,14 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver"
+                  android:permission="android.permission.UPDATE_CONFIG">
+            <intent-filter>
+                <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" />
+                <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+            </intent-filter>
+        </receiver>
+
         <receiver android:name="com.android.server.updates.ApnDbInstallReceiver"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
@@ -3902,6 +3938,14 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name="com.android.server.updates.CarrierIdInstallReceiver"
+                  android:permission="android.permission.UPDATE_CONFIG">
+            <intent-filter>
+                <action android:name="com.android.internal.intent.action.UPDATE_CARRIER_ID_DB" />
+                <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+            </intent-filter>
+        </receiver>
+
         <receiver android:name="com.android.server.MasterClearReceiver"
             android:permission="android.permission.MASTER_CLEAR">
             <intent-filter
diff --git a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
new file mode 100644
index 0000000..3254ebb
--- /dev/null
+++ b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<!-- This should be kept in sync with task_open_enter.xml -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false" android:zAdjustment="top">
+
+    <alpha android:fromAlpha="0" android:toAlpha="1.0"
+           android:startOffset="300"
+           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+           android:interpolator="@interpolator/decelerate_quart"
+           android:duration="167"/>
+
+    <translate android:fromYDelta="110%" android:toYDelta="0"
+               android:startOffset="300"
+               android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+               android:interpolator="@interpolator/decelerate_quint"
+               android:duration="417"/>
+
+    <!-- To keep the thumbnail around longer -->
+    <alpha android:fromAlpha="1.0" android:toAlpha="0"
+           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+           android:interpolator="@interpolator/decelerate_quint"
+           android:startOffset="717"
+           android:duration="200"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml
index e511cc9..b73e14f 100644
--- a/core/res/res/anim/task_open_enter.xml
+++ b/core/res/res/anim/task_open_enter.xml
@@ -16,7 +16,7 @@
 ** limitations under the License.
 */
 -->
-
+<!-- This should in sync with task_open_enter_cross_profile_apps.xml -->
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:shareInterpolator="false" android:zAdjustment="top">
 
diff --git a/core/res/res/anim/task_open_enter_cross_profile_apps.xml b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
new file mode 100644
index 0000000..ad89fde
--- /dev/null
+++ b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<!-- This should in sync with task_open_enter.xml -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false" android:zAdjustment="top">
+
+    <alpha android:fromAlpha="0" android:toAlpha="1.0"
+           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+           android:interpolator="@interpolator/decelerate_quart"
+           android:startOffset="300"
+           android:duration="167"/>
+
+    <translate android:fromYDelta="110%" android:toYDelta="0"
+               android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+               android:interpolator="@interpolator/decelerate_quint"
+               android:startOffset="300"
+               android:duration="417"/>
+
+    <!-- To keep the transition around longer for the thumbnail, should be kept in sync with
+         cross_profile_apps_thumbmail.xml -->
+    <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+           android:startOffset="717"
+           android:duration="200"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/values-af/required_apps_managed_device.xml b/core/res/res/values-af/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-af/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-af/required_apps_managed_profile.xml b/core/res/res/values-af/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-af/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-af/required_apps_managed_user.xml b/core/res/res/values-af/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-af/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 1646073..e33cfc1 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Titelloos&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tik om alle netwerke te sien"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Koppel"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Alle netwerke"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi sal outomaties aanskakel"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Wanneer jy naby \'n gestoorde hoëgehaltenetwerk is"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Moenie weer aanskakel nie"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Meld aan by Wi-Fi-netwerk"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Meld by netwerk aan"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-am/required_apps_managed_device.xml b/core/res/res/values-am/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-am/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-am/required_apps_managed_profile.xml b/core/res/res/values-am/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-am/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-am/required_apps_managed_user.xml b/core/res/res/values-am/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-am/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 868b065..e992266 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"ኪባ"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;ርዕስ አልባ&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ሁሉንም አውታረ መረቦችን ለማየት መታ ያድርጉ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"አገናኝ"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"ሁሉም አውታረ መረቦች"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi በራስ-ሰር ይበራል"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ከፍተኛ ጥራት ያለው የተቀመጠ አውታረ መረብ አቅራቢያ ሲሆኑ"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"መልሰህ አታብራ"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"ወደ Wi-Fi አውታረ መረብ በመለያ ግባ"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ወደ አውታረ መረብ በመለያ ይግቡ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-ar/required_apps_managed_device.xml b/core/res/res/values-ar/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ar/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ar/required_apps_managed_profile.xml b/core/res/res/values-ar/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ar/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ar/required_apps_managed_user.xml b/core/res/res/values-ar/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ar/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 2f69bd5..2002084 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"بايت"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"كيلوبايت"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"ميغابايت"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"غيغابايت"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"تيرابايت"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"بيتابايت"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"‏&lt;بلا عنوان&gt;"</string>
@@ -1204,6 +1208,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"انقر للاطلاع على جميع الشبكات"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"اتصال"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"جميع الشبكات"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏سيتم تشغيل شبكة Wi-Fi تلقائيًا."</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"عندما تكون بالقرب من شبكة محفوظة عالية الجودة"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"عدم إعادة التشغيل"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"‏تسجيل الدخول إلى شبكة Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"تسجيل الدخول إلى الشبكة"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index a90ef03..a05d49f 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"Başlıqsız"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Bütün şəbəkələri görmək üçün klikləyin"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Qoşulun"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Bütün Şəbəkələr"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi avtomatik olaraq aktiv ediləcək"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Yadda saxlanmış yüksək keyfiyyətli şəbəkələr yaxınlıqda olduqda"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Yenidən aktiv etməyin"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fi şəbəkəsinə daxil ol"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Şəbəkəyə daxil olun"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 6725865..ce220db 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Bez naslova&gt;"</string>
@@ -1138,6 +1142,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dodirnite da biste videli sve mreže"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Poveži"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Sve mreže"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi će se automatski uključiti"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kada ste u blizini sačuvane mreže visokog kvaliteta"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne uključuj ponovo"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Prijavljivanje na Wi-Fi mrežu"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Prijavite se na mrežu"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-be/required_apps_managed_device.xml b/core/res/res/values-be/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-be/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-be/required_apps_managed_profile.xml b/core/res/res/values-be/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-be/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-be/required_apps_managed_user.xml b/core/res/res/values-be/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-be/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index ca75fac..f636f48 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"б"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"КБ"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"Мб"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"ГБ"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"Тб"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"Пб"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Без назвы&gt;"</string>
@@ -1160,6 +1164,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Дакраніцеся, каб убачыць усе сеткі"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Падключыцца"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Усе сеткі"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi уключыцца аўтаматычна"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Побач з захаванай сеткай з высакаякасным сігналам"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не ўключаць зноў"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Уваход у сетку Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Увайдзіце ў сетку"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-bg/required_apps_managed_device.xml b/core/res/res/values-bg/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-bg/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-bg/required_apps_managed_profile.xml b/core/res/res/values-bg/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-bg/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-bg/required_apps_managed_user.xml b/core/res/res/values-bg/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-bg/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 44f183f..7a37939 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"Б"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"КБ"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"МБ"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"ГБ"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"ТБ"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"ПБ"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Без заглавие&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Докоснете, за да видите всички мрежи"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Свързване"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Всички мрежи"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ще се включи автоматично"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Когато сте в района на запазена мрежа с високо качество"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Без повторно включване"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Влизане в Wi-Fi мрежа"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Вход в мрежата"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-bn/required_apps_managed_device.xml b/core/res/res/values-bn/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-bn/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-bn/required_apps_managed_profile.xml b/core/res/res/values-bn/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-bn/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-bn/required_apps_managed_user.xml b/core/res/res/values-bn/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-bn/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index fa3c9b1d..b88d2c8 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"বাইট"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;শিরোনামহীন&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"সমস্ত নেটওয়ার্ক দেখতে ট্যাপ করুন"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"সংযুক্ত করুন"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"সমস্ত নেটওয়ার্ক"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ওয়াই-ফাই নিজে থেকেই চালু হবে"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"যখন আপনি একটি উচ্চ মানের সংরক্ষিত নেটওয়ার্ক কাছাকাছি থাকেন"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"আবার চালু করবেন না"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"ওয়াই-ফাই নেটওয়ার্কে সাইন-ইন করুন"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"নেটওয়ার্কে সাইন-ইন করুন"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index c93a0e9..e19a974 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Bez naslova&gt;"</string>
@@ -86,7 +90,7 @@
     <string name="notification_channel_network_alert" msgid="4427736684338074967">"Upozorenja"</string>
     <string name="notification_channel_call_forward" msgid="2419697808481833249">"Preusmjeravanje poziva"</string>
     <string name="notification_channel_emergency_callback" msgid="6686166232265733921">"Način rada za hitni povratni poziv"</string>
-    <string name="notification_channel_mobile_data_status" msgid="4575131690860945836">"Status mobilnih podataka"</string>
+    <string name="notification_channel_mobile_data_status" msgid="4575131690860945836">"Status prijenosa podataka na mobilnoj mreži"</string>
     <string name="notification_channel_sms" msgid="3441746047346135073">"SMS poruke"</string>
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Poruke govorne pošte"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi pozivanje"</string>
@@ -1140,6 +1144,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dodirnite da vidite sve mreže"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Povežite se"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Sve mreže"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi veza će se automatski uključiti"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kada ste u blizini sačuvane mreže visokog kvaliteta"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nemoj ponovo uključiti"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Prijavljivanje na Wi-Fi mrežu"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Prijava na mrežu"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1150,7 +1157,7 @@
     <string name="network_switch_metered_detail" msgid="775163331794506615">"Kada <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nema pristup internetu, uređaj koristi mrežu <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Moguća je naplata usluge."</string>
     <string name="network_switch_metered_toast" msgid="5779283181685974304">"Prebačeno iz mreže <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> u <xliff:g id="NEW_NETWORK">%2$s</xliff:g> mrežu"</string>
   <string-array name="network_switch_type_name">
-    <item msgid="3979506840912951943">"mobilni podaci"</item>
+    <item msgid="3979506840912951943">"prijenos podataka na mobilnoj mreži"</item>
     <item msgid="75483255295529161">"Wi-Fi"</item>
     <item msgid="6862614801537202646">"Bluetooth"</item>
     <item msgid="5447331121797802871">"Ethernet"</item>
diff --git a/core/res/res/values-ca/required_apps_managed_device.xml b/core/res/res/values-ca/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ca/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ca/required_apps_managed_profile.xml b/core/res/res/values-ca/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ca/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ca/required_apps_managed_user.xml b/core/res/res/values-ca/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ca/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 64d0ccd..7e109ac 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Sense títol&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toca per veure totes les xarxes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connecta"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Totes les xarxes"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"La Wi-Fi s\'activarà automàticament"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quan siguis a prop d\'una xarxa de qualitat desada"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"No tornis a activar"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Inicia la sessió a la xarxa Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Inicia la sessió a la xarxa"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-cs/required_apps_managed_device.xml b/core/res/res/values-cs/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-cs/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-cs/required_apps_managed_profile.xml b/core/res/res/values-cs/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-cs/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-cs/required_apps_managed_user.xml b/core/res/res/values-cs/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-cs/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 0f24965..8b0e46e 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Bez názvu&gt;"</string>
@@ -1160,6 +1164,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Klepnutím zobrazíte všechny sítě"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Připojit"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Všechny sítě"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi se zapne automaticky"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Když budete v dosahu kvalitní uložené sítě"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Znovu nezapínat"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Přihlásit se k síti Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Přihlásit se k síti"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-da/required_apps_managed_device.xml b/core/res/res/values-da/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-da/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-da/required_apps_managed_profile.xml b/core/res/res/values-da/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-da/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-da/required_apps_managed_user.xml b/core/res/res/values-da/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-da/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 0581742..0be9d20 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"b"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"Mb"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"Tb"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"Pb"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Uden titel&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tryk for at se alle netværk"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Opret forbindelse"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Alle netværk"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi aktiveres automatisk"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Når du er i nærheden af et gemt netværk af høj kvalitet"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Aktivér ikke igen"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Log ind på Wi-Fi-netværk"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Log ind på netværk"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1463,7 +1470,7 @@
     <string name="kg_sim_pin_instructions" msgid="2319508550934557331">"Indtast pinkode til SIM-kort"</string>
     <string name="kg_pin_instructions" msgid="2377242233495111557">"Indtast pinkode"</string>
     <string name="kg_password_instructions" msgid="5753646556186936819">"Angiv adgangskode"</string>
-    <string name="kg_puk_enter_puk_hint" msgid="453227143861735537">"SIM-kortet er nu deaktiveret. Indtast PUK-koden for at fortsætte. Kontakt mobiloperatøren for at få flere oplysninger."</string>
+    <string name="kg_puk_enter_puk_hint" msgid="453227143861735537">"SIM-kortet er nu deaktiveret. Indtast PUK-koden for at fortsætte. Kontakt mobilselskabet for at få flere oplysninger."</string>
     <string name="kg_puk_enter_pin_hint" msgid="7871604527429602024">"Indtast den ønskede pinkode"</string>
     <string name="kg_enter_confirm_pin_hint" msgid="325676184762529976">"Bekræft den ønskede pinkode"</string>
     <string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM-kortet låses op…"</string>
diff --git a/core/res/res/values-de/required_apps_managed_device.xml b/core/res/res/values-de/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-de/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-de/required_apps_managed_profile.xml b/core/res/res/values-de/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-de/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-de/required_apps_managed_user.xml b/core/res/res/values-de/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-de/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index f694064..367949c 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Unbenannt&gt;"</string>
@@ -545,8 +549,8 @@
     <string name="permdesc_handoverStatus" msgid="4788144087245714948">"Ermöglicht dieser App, Informationen zu aktuellen Android Beam-Übertragungen zu erhalten"</string>
     <string name="permlab_removeDrmCertificates" msgid="7044888287209892751">"DRM-Zertifikate entfernen"</string>
     <string name="permdesc_removeDrmCertificates" msgid="7272999075113400993">"Ermöglicht einer App das Entfernen von DRM-Zertifikaten. Sollte für normale Apps nie benötigt werden."</string>
-    <string name="permlab_bindCarrierMessagingService" msgid="1490229371796969158">"An einen Mobilfunkanbieter-SMS/MMS-Dienst binden"</string>
-    <string name="permdesc_bindCarrierMessagingService" msgid="2762882888502113944">"Ermöglicht dem Inhaber die Bindung an die Oberfläche eines Mobilfunkanbieter-SMS/MMS-Dienstes auf oberster Ebene. Für normale Apps sollte dies nie erforderlich sein."</string>
+    <string name="permlab_bindCarrierMessagingService" msgid="1490229371796969158">"An einen Mobilfunkanbieter-Messaging-Dienst binden"</string>
+    <string name="permdesc_bindCarrierMessagingService" msgid="2762882888502113944">"Ermöglicht dem Inhaber die Bindung an die Oberfläche eines Mobilfunkanbieter-Messaging-Dienstes auf oberster Ebene. Für normale Apps sollte dies nie erforderlich sein."</string>
     <string name="permlab_bindCarrierServices" msgid="3233108656245526783">"An Mobilfunkanbieter-Dienste binden"</string>
     <string name="permdesc_bindCarrierServices" msgid="1391552602551084192">"Ermöglicht dem Inhaber die Bindung an Mobilfunkanbieter-Dienste. Für normale Apps sollte dies nicht erforderlich sein."</string>
     <string name="permlab_access_notification_policy" msgid="4247510821662059671">"Auf \"Nicht stören\" zugreifen"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tippen, um alle Netzwerke zu sehen"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Verbinden"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Alle Netzwerke"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WLAN wird automatisch aktiviert"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Wenn du in der Nähe eines sicheren gespeicherten Netzwerks bist"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nicht wieder aktivieren"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"In WLAN anmelden"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Im Netzwerk anmelden"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1640,7 +1647,7 @@
     <string name="package_installed_device_owner" msgid="6875717669960212648">"Von deinem Administrator installiert"</string>
     <string name="package_updated_device_owner" msgid="1847154566357862089">"Von deinem Administrator aktualisiert"</string>
     <string name="package_deleted_device_owner" msgid="2307122077550236438">"Von deinem Administrator gelöscht"</string>
-    <string name="battery_saver_description" msgid="5394663545060026162">"Der Energiesparmodus schont den Akku, indem er die Leistung des Geräts reduziert und die Vibrationsfunktion, Standortdienste sowie die meisten Hintergrunddatenaktivitäten einschränkt. E-Mail, SMS/MMS und andere Apps, die auf deinem Gerät synchronisiert werden, werden möglicherweise nur aktualisiert, wenn du sie öffnest.\n\nDer Energiesparmodus wird automatisch deaktiviert, wenn dein Gerät aufgeladen wird."</string>
+    <string name="battery_saver_description" msgid="5394663545060026162">"Der Energiesparmodus schont den Akku, indem er die Leistung des Geräts reduziert und die Vibrationsfunktion, Standortdienste sowie die meisten Hintergrunddatenaktivitäten einschränkt. E-Mail, Messaging und andere Apps, die auf deinem Gerät synchronisiert werden, werden möglicherweise nur aktualisiert, wenn du sie öffnest.\n\nDer Energiesparmodus wird automatisch deaktiviert, wenn dein Gerät aufgeladen wird."</string>
     <string name="data_saver_description" msgid="6015391409098303235">"Mit dem Datensparmodus wird die Datennutzung verringert, indem verhindert wird, dass im Hintergrund Daten von Apps gesendet oder empfangen werden. Datenzugriffe sind mit einer aktiven App zwar möglich, erfolgen aber seltener. Als Folge davon könnten Bilder beispielsweise erst dann sichtbar werden, wenn sie angetippt werden."</string>
     <string name="data_saver_enable_title" msgid="4674073932722787417">"Datensparmodus aktivieren?"</string>
     <string name="data_saver_enable_button" msgid="7147735965247211818">"Aktivieren"</string>
diff --git a/core/res/res/values-el/required_apps_managed_device.xml b/core/res/res/values-el/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-el/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-el/required_apps_managed_profile.xml b/core/res/res/values-el/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-el/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-el/required_apps_managed_user.xml b/core/res/res/values-el/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-el/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index c20a03f..05c51cf 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Χωρίς τίτλο&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Πατήστε για να δείτε όλα τα δίκτυα"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Σύνδεση"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Όλα τα δίκτυα"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Το Wi‑Fi θα ενεργοποιηθεί αυτόματα"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Όταν βρίσκεστε κοντά σε αποθηκευμένο δίκτυο υψηλής ποιότητας"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Να μην ενεργοποιηθεί ξανά"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Συνδεθείτε στο δίκτυο Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Σύνδεση στο δίκτυο"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index a175e6c..ed97563 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Untitled&gt;"</string>
@@ -212,6 +216,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
     <string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1115,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tap to see all networks"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connect"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"All Networks"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi will turn on automatically"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"When you\'re near a high‑quality saved network"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Don\'t turn back on"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Sign in to a Wi-Fi network"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Sign in to network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1498,6 +1506,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1694,16 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
     <string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index a175e6c..ed97563 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Untitled&gt;"</string>
@@ -212,6 +216,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
     <string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1115,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tap to see all networks"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connect"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"All Networks"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi will turn on automatically"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"When you\'re near a high‑quality saved network"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Don\'t turn back on"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Sign in to a Wi-Fi network"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Sign in to network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1498,6 +1506,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1694,16 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
     <string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index a175e6c..ed97563 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Untitled&gt;"</string>
@@ -212,6 +216,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
     <string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1115,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tap to see all networks"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connect"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"All Networks"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi will turn on automatically"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"When you\'re near a high‑quality saved network"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Don\'t turn back on"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Sign in to a Wi-Fi network"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Sign in to network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1498,6 +1506,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1694,16 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
     <string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index a175e6c..ed97563 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Untitled&gt;"</string>
@@ -212,6 +216,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
     <string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1115,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tap to see all networks"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connect"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"All Networks"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi will turn on automatically"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"When you\'re near a high‑quality saved network"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Don\'t turn back on"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Sign in to a Wi-Fi network"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Sign in to network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1498,6 +1506,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1694,16 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
     <string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 5635292..2833630 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‎‎‎‎‏‎‎‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎B‎‏‎‎‏‎"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‎kB‎‏‎‎‏‎"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎MB‎‏‎‎‏‎"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‎GB‎‏‎‎‏‎"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‎‎‏‎‎‎‎‎‏‎‎‏‎‎TB‎‏‎‎‏‎"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‎PB‎‏‎‎‏‎"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="UNIT">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="untitled" msgid="4638956954852782576">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎‎‎&lt;Untitled&gt;‎‏‎‎‏‎"</string>
@@ -212,6 +216,7 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‎‎Power off‎‏‎‎‏‎"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‎‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎Emergency‎‏‎‎‏‎"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‎Bug report‎‏‎‎‏‎"</string>
+    <string name="global_action_logout" msgid="935179188218826050">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‎End session‎‏‎‎‏‎"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎Take bug report‎‏‎‎‏‎"</string>
     <string name="bugreport_message" msgid="398447048750350456">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‎‎‎This will collect information about your current device state, to send as an e-mail message. It will take a little time from starting the bug report until it is ready to be sent; please be patient.‎‏‎‎‏‎"</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‏‏‏‏‎Interactive report‎‏‎‎‏‎"</string>
@@ -1115,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‎‏‏‎‎‏‏‎‎‏‏‎‏‎‏‎‏‎Tap to see all networks‎‏‎‎‏‎"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‎‎Connect‎‏‎‎‏‎"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎‎‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‎‏‎All Networks‎‏‎‎‏‎"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‎‎‏‏‎‎‎‎‏‏‎‏‏‎‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‏‎‏‎‎‎Wi‑Fi will turn on automatically‎‏‎‎‏‎"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎When you\'re near a high quality saved network‎‏‎‎‏‎"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎Don\'t turn back on‎‏‎‎‏‎"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‎‎‏‏‎‎Sign in to Wi-Fi network‎‏‎‎‏‎"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎Sign in to network‎‏‎‎‏‎"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1498,6 +1506,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ Current accessibility feature:‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="SERVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ You can change the feature in Settings &gt; Accessibility.‎‏‎‎‏‎"</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎Turn off Shortcut‎‏‎‎‏‎"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‏‎‎‎‎Use Shortcut‎‏‎‎‏‎"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎Color Inversion‎‏‎‎‏‎"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‏‏‎‏‎‏‎Color Correction‎‏‎‎‏‎"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‏‏‎‎‎Accessibility Shortcut turned ‎‏‎‎‏‏‎<xliff:g id="SERVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ on‎‏‎‎‏‎"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‏‏‎‏‎Accessibility Shortcut turned ‎‏‎‎‏‏‎<xliff:g id="SERVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ off‎‏‎‎‏‎"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‏‏‎Choose a feature to use when you tap the Accessibility button:‎‏‎‎‏‎"</string>
@@ -1684,13 +1694,16 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‏‏‏‏‏‏‏‏‎Weeknight‎‏‎‎‏‎"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‎‎‏‎‏‎‏‏‏‎‎‎Weekend‎‏‎‎‏‎"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‎Event‎‏‎‎‏‎"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‏‎‎‏‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎Sleeping‎‏‎‎‏‎"</string>
     <string name="muted_by" msgid="6147073845094180001">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‎‎‎‏‎Muted by ‎‏‎‎‏‏‎<xliff:g id="THIRD_PARTY">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎There\'s an internal problem with your device, and it may be unstable until you factory data reset.‎‏‎‎‏‎"</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‎‎‎There\'s an internal problem with your device. Contact your manufacturer for details.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‎USSD request is modified to DIAL request.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎USSD request is modified to SS request.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‏‎USSD request is modified to new USSD request.‎‏‎‎‏‎"</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‏‎‎‏‎USSD request is modified to Video DIAL request.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‎‏‎‎SS request is modified to DIAL request.‎‏‎‎‏‎"</string>
+    <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‏‎SS request is modified to Video DIAL request.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎SS request is modified to USSD request.‎‏‎‎‏‎"</string>
     <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‎SS request is modified to new SS request.‎‏‎‎‏‎"</string>
     <string name="notification_work_profile_content_description" msgid="4600554564103770764">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‎‎‎‎‎‎‎‎‏‎‏‎‎‏‏‎‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎Work profile‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/required_apps_managed_device.xml b/core/res/res/values-es-rUS/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-es-rUS/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-es-rUS/required_apps_managed_profile.xml b/core/res/res/values-es-rUS/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-es-rUS/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-es-rUS/required_apps_managed_user.xml b/core/res/res/values-es-rUS/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-es-rUS/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 58268eb..3efbad9 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Sin título&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Presiona para ver todas las redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectar"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Todas las redes"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Se activará la conexión Wi-Fi automáticamente"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Cuando estés cerca de una red guardada de alta calidad"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"No volver a activar"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Accede a una red Wi-Fi."</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Acceder a la red"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index d03498c..5a0a118 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Sin título&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toca para ver todas las redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectarse"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Todas las redes"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"La conexión Wi‑Fi se activará automáticamente"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Cuando estés cerca de una red de alta calidad guardada"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"No volver a activar"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Iniciar sesión en red Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Iniciar sesión en la red"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-et/required_apps_managed_device.xml b/core/res/res/values-et/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-et/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-et/required_apps_managed_profile.xml b/core/res/res/values-et/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-et/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-et/required_apps_managed_user.xml b/core/res/res/values-et/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-et/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 754cdb3..4b2d13b 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Pealkirjata&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Puudutage kõikide võrkude nägemiseks"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Ühenda"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Kõik võrgud"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WiFi lülitub sisse automaatselt"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kui olete kvaliteetse salvestatud võrgu läheduses"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ära lülita tagasi sisse"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Logi sisse WiFi-võrku"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Võrku sisselogimine"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-eu/required_apps_managed_device.xml b/core/res/res/values-eu/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-eu/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-eu/required_apps_managed_profile.xml b/core/res/res/values-eu/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-eu/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-eu/required_apps_managed_user.xml b/core/res/res/values-eu/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-eu/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 2e10fa2..f468b5d 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Izengabea&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Sakatu hau sare guztiak ikusteko"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Konektatu"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Sare guztiak"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi konexioa automatikoki aktibatuko da"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Gordeta daukazun kalitate handiko sare batetik gertu zaudenean"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ez aktibatu berriro"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Hasi saioa Wi-Fi sarean"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Hasi saioa sarean"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1389,7 +1396,7 @@
     <string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB unitatea"</string>
     <string name="storage_usb" msgid="3017954059538517278">"USB memoria"</string>
     <string name="extract_edit_menu_button" msgid="8940478730496610137">"Editatu"</string>
-    <string name="data_usage_warning_title" msgid="3620440638180218181">"Datuen erabilerari buruzko abisua"</string>
+    <string name="data_usage_warning_title" msgid="3620440638180218181">"Datuen erabilerari buruzko alerta"</string>
     <string name="data_usage_warning_body" msgid="6660692274311972007">"Sakatu erabilera eta ezarpenak ikusteko."</string>
     <string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2-3 GB-ko mugara iritsi zara"</string>
     <string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4 GB-ko mugara iritsi zara"</string>
@@ -1500,8 +1507,8 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"Lasterbidea aktibatuta dagoenean, bi bolumen-botoiak hiru segundoz sakatuta abiaraziko da erabilerraztasun-eginbidea.\n\n Uneko erabilerraztasun-eginbidea:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Eginbidea aldatzeko, joan Ezarpenak &gt; Erabilerraztasuna atalera."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Desaktibatu lasterbidea"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Erabili lasterbidea"</string>
-    <string name="color_inversion_feature_name" msgid="4231186527799958644">"Koloreak alderantzikatzeko aukera"</string>
-    <string name="color_correction_feature_name" msgid="6779391426096954933">"Kolorearen zuzenketa"</string>
+    <string name="color_inversion_feature_name" msgid="4231186527799958644">"Koloreen alderantzikatzea"</string>
+    <string name="color_correction_feature_name" msgid="6779391426096954933">"Koloreen zuzenketa"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Erabilerraztasun-lasterbideak <xliff:g id="SERVICE_NAME">%1$s</xliff:g> aktibatu du"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Erabilerraztasun-lasterbideak <xliff:g id="SERVICE_NAME">%1$s</xliff:g> desaktibatu du"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Aukeratu zein eginbide erabili nahi duzun Erabilerraztasuna botoia sakatzean:"</string>
diff --git a/core/res/res/values-fa/required_apps_managed_device.xml b/core/res/res/values-fa/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-fa/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-fa/required_apps_managed_profile.xml b/core/res/res/values-fa/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-fa/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-fa/required_apps_managed_user.xml b/core/res/res/values-fa/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-fa/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index dfa350b..8061962 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"بایت"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"کیلوبایت"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"مگابایت"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"گیگابایت"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"ترابایت"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"پتابایت"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> ‏<xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"‏&lt;بدون عنوان&gt;"</string>
@@ -453,7 +457,7 @@
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"‏به برنامه اجازه می‌دهد به دریافت بسته‌های ارسالی به همه دستگاه‌های موجود در شبکه Wi-Fi با استفاده از آدرس‌های پخش چندگانه و نه فقط به تلفن شما بپردازند. این از توان مصرف بیشتری نسبت به حالت پخش غیرچندگانه استفاده می‌کند."</string>
     <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"دسترسی به تنظیمات بلوتوث"</string>
     <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"‏به برنامه اجازه می‎دهد تا رایانهٔ لوحی بلوتوث محلی را پیکربندی کرده، دستگاه‌های راه دور را شناسایی کرده و با آن‌ها مرتبط‌سازی شود."</string>
-    <string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"به برنامه اجازه می‌دهد تا تلویزیون بلوتوث محلی را پیکربندی کند و دستگاه‌های از راه دور را شناسایی کند و با آنها مرتبط شود."</string>
+    <string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"به برنامه اجازه می‌دهد تا تلویزیون بلوتوث محلی را پیکربندی کند و دستگاه‌های ازراه‌دور را شناسایی کند و با آنها مرتبط شود."</string>
     <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"‏به برنامه اجازه می‎دهد تا تلفن بلوتوث محلی را پیکربندی کند و دستگاه‌های راه دور را پیدا کند و با آن‌ها مرتبط‌سازی شود."</string>
     <string name="permlab_accessWimaxState" msgid="4195907010610205703">"‏اتصال و قطع اتصال از WiMAX"</string>
     <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"به برنامه امکان می‌دهد فعال بودن وایمکس و اطلاعات مربوط به هر یک از شبکه‌های وایمکس متصل را مشخص کند."</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"برای دیدن همه شبکه‌ها ضربه بزنید"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"اتصال"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"همه شبکه‌ها"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏Wi‑Fi به‌طور خودکار روشن خواهد شد"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"وقتی نزدیک شبکه ذخیره‌شده با کیفیت بالا هستید"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"دوباره روشن نشود"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"‏ورود به شبکه Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ورود به سیستم شبکه"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-fi/required_apps_managed_device.xml b/core/res/res/values-fi/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-fi/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-fi/required_apps_managed_profile.xml b/core/res/res/values-fi/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-fi/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-fi/required_apps_managed_user.xml b/core/res/res/values-fi/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-fi/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 5696b5a..4385466 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"t"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kt"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"Mt"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"Gt"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"Tt"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"Pt"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Nimetön&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Napauta, niin näet kaikki verkot."</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Yhdistä"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Kaikki verkot"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi käynnistyy automaattisesti"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kun olet lähellä laadukasta tallennettua verkkoa"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Älä käynnistä uudelleen"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Kirjaudu Wi-Fi-verkkoon"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Kirjaudu verkkoon"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-fr-rCA/required_apps_managed_device.xml b/core/res/res/values-fr-rCA/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-fr-rCA/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-fr-rCA/required_apps_managed_profile.xml b/core/res/res/values-fr-rCA/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-fr-rCA/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-fr-rCA/required_apps_managed_user.xml b/core/res/res/values-fr-rCA/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-fr-rCA/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 6e97844..49a3640 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"o"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"ko"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"Mo"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"Go"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"To"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"Po"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Sans_titre&gt;"</string>
@@ -982,7 +986,7 @@
     <string name="email" msgid="4560673117055050403">"Courriel"</string>
     <string name="dial" msgid="1253998302767701559">"Appel"</string>
     <string name="map" msgid="6521159124535543457">"Localiser"</string>
-    <string name="browse" msgid="1245903488306147205">"Ouvert"</string>
+    <string name="browse" msgid="1245903488306147205">"Ouvrir"</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>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Touchez pour afficher tous les réseaux"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connexion"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Tous les réseaux"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Le Wi-Fi s\'activera automatiquement"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Lorsque vous êtes près d\'un réseau enregistré de haute qualité"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne pas réactiver"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Connectez-vous au réseau Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Connectez-vous au réseau"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 7dc33c4..5564d8b 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"octet(s)"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"Ko"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"Mo"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"Go"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"To"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"Po"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Sans nom&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Appuyer pour afficher tous les réseaux"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Se connecter"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Tous les réseaux"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Le Wi-Fi sera activé automatiquement"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Lorsque vous êtes à proximité d\'un réseau enregistré de haute qualité"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne pas réactiver"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Connectez-vous au réseau Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Se connecter au réseau"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1215,7 +1222,7 @@
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="alert_windows_notification_channel_group_name" msgid="1463953341148606396">"Afficher par-dessus les autres applications"</string>
     <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> est affichée sur les autres applications"</string>
-    <string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> s\'affiche sur autres applis"</string>
+    <string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> se superpose aux autres applis"</string>
     <string name="alert_windows_notification_message" msgid="8917232109522912560">"Si vous ne voulez pas que l\'application <xliff:g id="NAME">%s</xliff:g> utilise cette fonctionnalité, appuyez ici pour ouvrir les paramètres et la désactiver."</string>
     <string name="alert_windows_notification_turn_off_action" msgid="3367294525884949878">"DÉSACTIVER"</string>
     <string name="ext_media_checking_notification_title" msgid="5734005953288045806">"Préparation mémoire \"<xliff:g id="NAME">%s</xliff:g>\" en cours"</string>
diff --git a/core/res/res/values-gl/required_apps_managed_device.xml b/core/res/res/values-gl/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-gl/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-gl/required_apps_managed_profile.xml b/core/res/res/values-gl/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-gl/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-gl/required_apps_managed_user.xml b/core/res/res/values-gl/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-gl/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index b4e3904..40dff7e 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Sen título&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toca para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectarse"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Todas as redes"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"A wifi activarase automaticamente"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Cando esteas preto dunha rede gardada de alta calidade"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Non volver activar"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Inicia sesión na rede wifi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Inicia sesión na rede"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-gu/required_apps_managed_device.xml b/core/res/res/values-gu/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-gu/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-gu/required_apps_managed_profile.xml b/core/res/res/values-gu/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-gu/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-gu/required_apps_managed_user.xml b/core/res/res/values-gu/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-gu/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index eada633..7a4c1b2 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;અનામાંકિત&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"બધા નેટવર્ક જોવા ટૅપ કરો"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"કનેક્ટ કરો"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"બધા નેટવર્ક"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"વાઇ-ફાઇ આપમેળે ચાલુ થઈ જશે"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"જ્યારે તમે એક ઉચ્ચ ક્વૉલિટીવાળા સાચવેલ નેટવર્કની નજીક હોવ"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"પાછું ચાલુ કરશો નહીં"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"વાઇ-ફાઇ નેટવર્ક પર સાઇન ઇન કરો"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"નેટવર્ક પર સાઇન ઇન કરો"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-hi/required_apps_managed_device.xml b/core/res/res/values-hi/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-hi/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hi/required_apps_managed_profile.xml b/core/res/res/values-hi/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-hi/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hi/required_apps_managed_user.xml b/core/res/res/values-hi/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-hi/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index f9e8a03..adeba4e 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"केबी"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"एमबी"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"जीबी"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;शीर्षक-रहित&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"सभी नेटवर्क देखने के लिए यहां पर टैप करें"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"कनेक्ट करें"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"सभी नेटवर्क"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"वाई-फ़ाई अपने आप चालू हो जाएगा"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"जब आप किसी अच्छी क्वालिटी वाले सेव किए गए नेटवर्क के पास हों"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"वापस चालू न करें"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"वाई-फ़ाई  नेटवर्क में साइन इन करें"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"नेटवर्क में साइन इन करें"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-hr/required_apps_managed_device.xml b/core/res/res/values-hr/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-hr/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hr/required_apps_managed_profile.xml b/core/res/res/values-hr/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-hr/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hr/required_apps_managed_user.xml b/core/res/res/values-hr/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-hr/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 7e53209..6883769 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Bez naslova&gt;"</string>
@@ -1138,6 +1142,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dodirnite za prikaz svih mreža"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Poveži"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Sve mreže"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi će se uključiti automatski"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kada ste u blizini spremljene mreže visoke kvalitete"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Više ne uključuj"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Prijava na Wi-Fi mrežu"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Prijava na mrežu"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-hu/required_apps_managed_device.xml b/core/res/res/values-hu/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-hu/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hu/required_apps_managed_profile.xml b/core/res/res/values-hu/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-hu/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hu/required_apps_managed_user.xml b/core/res/res/values-hu/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-hu/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index b815035..2545283 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Névtelen&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Koppintással megjelenítheti az összes hálózatot"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Kapcsolódás"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Összes hálózat"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"A Wi-Fi automatikusan bekapcsol"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Amikor jó minőségű mentett hálózat közelében tartózkodik"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne kapcsolódjon vissza"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Bejelentkezés Wi-Fi hálózatba"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Bejelentkezés a hálózatba"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-hy/required_apps_managed_device.xml b/core/res/res/values-hy/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-hy/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hy/required_apps_managed_profile.xml b/core/res/res/values-hy/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-hy/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hy/required_apps_managed_user.xml b/core/res/res/values-hy/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-hy/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index ff615c50..d29c357 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"Բ"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"ԿԲ"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"ՄԲ"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"ԳԲ"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"ՏԲ"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"Պբ"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Անանուն&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Հպեք՝ բոլոր ցանցերը տեսնելու համար"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Միանալ"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Բոլոր ցանցերը"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi-ն ավտոմատ կմիանա"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Լավ ազդանշանով պահված ցանցի տարածքում գտնվելիս"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Նորից չմիացնել"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Մուտք գործեք Wi-Fi ցանց"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Մուտք գործեք ցանց"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index b8b76a0..d5d8d99 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Tanpa judul&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tap untuk melihat semua jaringan"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Hubungkan"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Semua Jaringan"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi akan aktif otomatis"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Saat berada di dekat jaringan berkualitas tinggi yang tersimpan"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Jangan aktifkan kembali"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Masuk ke jaringan Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Masuk ke jaringan"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-is/required_apps_managed_device.xml b/core/res/res/values-is/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-is/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-is/required_apps_managed_profile.xml b/core/res/res/values-is/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-is/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-is/required_apps_managed_user.xml b/core/res/res/values-is/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-is/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 76286e1..b65da75 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Ónefnt&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Ýttu til að sjá öll netkerfi"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Tengjast"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Öll netkerfi"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Kveikt verður sjálfkrafa á Wi‑Fi"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Þegar þú ert nálægt vistuðu hágæðaneti"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ekki kveikja aftur"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Skrá inn á Wi-Fi net"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Skrá inn á net"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 2116542..0f36301 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Senza nome&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tocca per vedere tutte le reti"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connetti"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Tutte le reti"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Il Wi‑Fi verrà attivato automaticamente"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando ti trovi nell\'area di una rete salvata di alta qualità"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Non riattivare"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Accedi a rete Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Accedi alla rete"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1694,7 +1701,7 @@
     <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"La richiesta USSD è stata modificata in richiesta DIAL."</string>
     <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"La richiesta USSD è stata modificata in richiesta SS."</string>
     <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"La richiesta USSD è stata modificata in nuova richiesta USSD."</string>
-    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"La richiesta USSD è stata modificata in richiesta DIAL."</string>
+    <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"La richiesta USSD è stata modificata in richiesta Video DIAL."</string>
     <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"La richiesta SS è stata modificata in richiesta DIAL."</string>
     <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"La richiesta SS è stata modificata in richiesta Video DIAL."</string>
     <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"La richiesta SS è stata modificata in richiesta USSD."</string>
diff --git a/core/res/res/values-iw/required_apps_managed_device.xml b/core/res/res/values-iw/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-iw/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-iw/required_apps_managed_profile.xml b/core/res/res/values-iw/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-iw/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-iw/required_apps_managed_user.xml b/core/res/res/values-iw/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-iw/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 87e20c0..a152840 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"‏&gt;ללא כותרת&lt;"</string>
@@ -531,8 +535,8 @@
     <string name="permdesc_manageNetworkPolicy" msgid="7537586771559370668">"מאפשר לאפליקציה לנהל מדיניות הרשת להגדיר כללים ספציפיים-לאפליקציה."</string>
     <string name="permlab_modifyNetworkAccounting" msgid="5088217309088729650">"שנה ניהול חשבונות של שימוש ברשת"</string>
     <string name="permdesc_modifyNetworkAccounting" msgid="5443412866746198123">"הרשאה זו מאפשרת לאפליקציה לשנות את אופן החישוב של נתוני שימוש ברשת מול כל אפליקציה. לא מיועד לשימוש באפליקציות רגילות."</string>
-    <string name="permlab_accessNotifications" msgid="7673416487873432268">"גישה להתראות"</string>
-    <string name="permdesc_accessNotifications" msgid="458457742683431387">"מאפשר לאפליקציה לאחזר, לבדוק ולמחוק התראות, כולל כאלה שפורסמו על ידי אפליקציות אחרות."</string>
+    <string name="permlab_accessNotifications" msgid="7673416487873432268">"גישה להודעות"</string>
+    <string name="permdesc_accessNotifications" msgid="458457742683431387">"מאפשר לאפליקציה לאחזר, לבדוק ולמחוק הודעות, כולל כאלה שפורסמו על ידי אפליקציות אחרות."</string>
     <string name="permlab_bindNotificationListenerService" msgid="7057764742211656654">"איגוד לשירות של מאזין להתראות"</string>
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"הרשאה זו מאפשרת למשתמש לבצע איגוד לממשק הרמה העליונה של שירות מאזין להתראות. הרשאה זו אף פעם אינה נחוצה לאפליקציות רגילים."</string>
     <string name="permlab_bindConditionProviderService" msgid="1180107672332704641">"איגוד לשירות ספק תנאי"</string>
@@ -1127,13 +1131,13 @@
     <string name="volume_call" msgid="3941680041282788711">"עוצמת קול בשיחה"</string>
     <string name="volume_bluetooth_call" msgid="2002891926351151534">"‏עוצמת הקול בשיחה ב-Bluetooth"</string>
     <string name="volume_alarm" msgid="1985191616042689100">"עוצמת קול של התראה"</string>
-    <string name="volume_notification" msgid="2422265656744276715">"עוצמת קול של התראות"</string>
+    <string name="volume_notification" msgid="2422265656744276715">"עוצמת קול של הודעות"</string>
     <string name="volume_unknown" msgid="1400219669770445902">"עוצמת קול"</string>
     <string name="volume_icon_description_bluetooth" msgid="6538894177255964340">"‏עוצמת קול של Bluetooth"</string>
     <string name="volume_icon_description_ringer" msgid="3326003847006162496">"עוצמת קול של רינגטון"</string>
     <string name="volume_icon_description_incall" msgid="8890073218154543397">"עוצמת קול של שיחות"</string>
     <string name="volume_icon_description_media" msgid="4217311719665194215">"עוצמת קול של מדיה"</string>
-    <string name="volume_icon_description_notification" msgid="7044986546477282274">"עוצמת קול של התראות"</string>
+    <string name="volume_icon_description_notification" msgid="7044986546477282274">"עוצמת קול של הודעות"</string>
     <string name="ringtone_default" msgid="3789758980357696936">"רינגטון ברירת מחדל"</string>
     <string name="ringtone_default_with_actual" msgid="1767304850491060581">"ברירת מחדל (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
     <string name="ringtone_silent" msgid="7937634392408977062">"ללא"</string>
@@ -1160,6 +1164,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"יש להקיש כדי לראות את כל הרשתות"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"התחבר"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"כל הרשתות"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏ה-Wi-Fi יופעל אוטומטית"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"כשתימצאו בקרבת רשת באיכות גבוהה ששמרתם"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"אל תפעיל שוב"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"‏היכנס לרשת Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"היכנס לרשת"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-ja/required_apps_managed_device.xml b/core/res/res/values-ja/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ja/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ja/required_apps_managed_profile.xml b/core/res/res/values-ja/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ja/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ja/required_apps_managed_user.xml b/core/res/res/values-ja/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ja/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index c2cc154..2f11776 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;新規&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"すべてのネットワークを表示するにはタップします"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"接続"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"すべてのネットワーク"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi は自動的にオンになります"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"高品質の保存済みネットワークの検出時"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"再度オンにしない"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fiネットワークにログイン"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ネットワークにログインしてください"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index ae4fb6f..40e9414 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"ბაიტი"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"კბაიტი"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"მბაიტი"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"გბაიტი"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"ტბაიტი"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"უსათაურო"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"შეეხეთ ყველა ქსელის სანახავად"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"დაკავშირება"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"ყველა ქსელი"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi ავტომატურად ჩაირთვება"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"როცა შენახულ მაღალხარისხიან ქსელებთან ახლოს იმყოფებით"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ხელახლა ნუ ჩართავ"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fi ქსელთან დაკავშირება"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ქსელში შესვლა"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-kk/required_apps_managed_device.xml b/core/res/res/values-kk/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-kk/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-kk/required_apps_managed_profile.xml b/core/res/res/values-kk/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-kk/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-kk/required_apps_managed_user.xml b/core/res/res/values-kk/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-kk/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index d56e6df..d428ff0 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"Б"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"КБ"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MБ"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"ГБ"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TБ"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"ПБ"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Атаусыз&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Барлық желілерді көру үшін түртіңіз"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Қосылу"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Барлық желілер"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi автоматты түрде қосылады"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Сақталған жоғары сапалы желіге жақын болғанда"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Қайта қоспау"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fi желісіне кіру"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Желіге кіру"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-km/required_apps_managed_device.xml b/core/res/res/values-km/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-km/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-km/required_apps_managed_profile.xml b/core/res/res/values-km/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-km/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-km/required_apps_managed_user.xml b/core/res/res/values-km/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-km/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 7be15f1..889ea5e 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"មេកាបៃ"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"ជីកាបៃ"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"តេរ៉ាបៃ"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;គ្មាន​ចំណង​ជើង&gt;"</string>
@@ -1118,6 +1122,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ចុចដើម្បីមើលបណ្តាញទាំងអស់"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ភ្ជាប់"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"បណ្តាញទាំងអស់"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi នឹង​បើក​ដោយ​ស្វ័យប្រវត្តិ"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"នៅពេល​ដែល​អ្នក​នៅ​ជិត​បណ្តាញ​គុណភាព​ខ្ពស់​ដែល​បាន​រក្សាទុក"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"កុំ​បើក​ឡើង​វិញ"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"ចូល​បណ្ដាញ​វ៉ាយហ្វាយ"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ចូលទៅបណ្តាញ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-kn/required_apps_managed_device.xml b/core/res/res/values-kn/required_apps_managed_device.xml
deleted file mode 100644
index e15b0b0..0000000
--- a/core/res/res/values-kn/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</item>
-    <item msgid="7004798084799227194">"com.android.ಸಂಪರ್ಕಗಳು"</item>
-    <item msgid="5782220690863647256">"com.android.ಡಯಲರ್"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್‌ಲೋಡ್‌ಗಳು"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-kn/required_apps_managed_profile.xml b/core/res/res/values-kn/required_apps_managed_profile.xml
deleted file mode 100644
index 92ab682..0000000
--- a/core/res/res/values-kn/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.ಸಂಪರ್ಕಗಳು"</item>
-    <item msgid="4633145750237794002">"com.android.ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</item>
-    <item msgid="6518205098643077579">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್‌ಲೋಡ್‌ಗಳು"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-kn/required_apps_managed_user.xml b/core/res/res/values-kn/required_apps_managed_user.xml
deleted file mode 100644
index 0c59edd..0000000
--- a/core/res/res/values-kn/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</item>
-    <item msgid="2250259015310893915">"com.android.ಸಂಪರ್ಕಗಳು"</item>
-    <item msgid="7166574999426592423">"com.android.ಡಯಲರ್"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್‌ಲೋಡ್‌ಗಳು"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 99db9e6..d751c13 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;ಶೀರ್ಷಿಕೆ ರಹಿತ&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ಎಲ್ಲಾ ನೆಟ್‌ವರ್ಕ್‌ಗಳನ್ನು ನೋಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ಸಂಪರ್ಕಿಸಿ"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"ಎಲ್ಲಾ ನೆಟ್‌ವರ್ಕ್‌ಗಳು"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ವೈ‑ಫೈ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಆನ್ ಆಗುತ್ತದೆ"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ನೀವು ಉಳಿಸಿದ ಅಧಿಕ ಗುಣಮಟ್ಟದ ನೆಟ್‌ವರ್ಕ್‌ ಸಮೀಪದಲ್ಲಿದ್ದಾಗ"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ಮತ್ತೆ ಆನ್ ಮಾಡಲು ಹಿಂತಿರುಗಬೇಡಿ"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"ವೈ-ಫೈ ನೆಟ್‍ವರ್ಕ್‌ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-ko/required_apps_managed_device.xml b/core/res/res/values-ko/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ko/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ko/required_apps_managed_profile.xml b/core/res/res/values-ko/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ko/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ko/required_apps_managed_user.xml b/core/res/res/values-ko/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ko/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 650d980..922f02f 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;제목 없음&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"모든 네트워크를 보려면 탭하세요."</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"연결"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"모든 네트워크"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi가 자동으로 사용 설정됨"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"저장된 고품질 네트워크가 가까이 있는 경우"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"다시 사용 설정하지 않음"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fi 네트워크에 로그인"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"네트워크에 로그인"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-ky/required_apps_managed_device.xml b/core/res/res/values-ky/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ky/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ky/required_apps_managed_profile.xml b/core/res/res/values-ky/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ky/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ky/required_apps_managed_user.xml b/core/res/res/values-ky/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ky/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index cfa05de..f3f7222 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"Б"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"Кб"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"Мб"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"Гб"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"ТБ"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"ПБ"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Баш аты жок&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Бардык тармактарды көрүү үчүн басыңыз"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Туташуу"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Бардык тармактар"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi автоматтык түрдө күйгүзүлөт"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Байланыш сигналы жакшы болгон тармактарга жакындаганда"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Өзү кайра күйбөйт"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fi түйүнүнө кирүү"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Тармакка кирүү"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-lo/required_apps_managed_device.xml b/core/res/res/values-lo/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-lo/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-lo/required_apps_managed_profile.xml b/core/res/res/values-lo/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-lo/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-lo/required_apps_managed_user.xml b/core/res/res/values-lo/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-lo/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index feedbf3..bc7e339 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;ບໍ່ມີຊື່&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ແຕະເພື່ອເບິ່ງເຄືອຂ່າຍທັງໝົດ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ເຊື່ອມ​ຕໍ່"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"ເຄືອຂ່າຍທັງໝົດ"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ຈະມີການເປີດໃຊ້ Wi‑Fi ອັດຕະໂນມັດ"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ເມື່ອທ່ານຢູ່ໃກ້ເຄືອຂ່າຍຄຸນນະພາບສູງທີ່ບັນທຶກໄວ້"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ບໍ່ຕ້ອງເປີດໃຊ້ຄືນໃໝ່"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"ເຂົ້າສູ່ລະບົບເຄືອຂ່າຍ Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ລົງຊື່ເຂົ້າເຄືອຂ່າຍ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-lt/required_apps_managed_device.xml b/core/res/res/values-lt/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-lt/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-lt/required_apps_managed_profile.xml b/core/res/res/values-lt/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-lt/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-lt/required_apps_managed_user.xml b/core/res/res/values-lt/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-lt/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 95e4862..2f5014e 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Be pavadinimo&gt;"</string>
@@ -1160,6 +1164,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Palieskite, jei norite matyti visus tinklus"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Prisijungti"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Visi tinklai"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"„Wi‑Fi“ bus įjungtas automatiškai"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kai būsite netoli išsaugoto aukštos kokybės tinklo"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Neįjunkite vėl"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Prisijungti prie „Wi-Fi“ tinklo"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Prisijungti prie tinklo"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-lv/required_apps_managed_device.xml b/core/res/res/values-lv/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-lv/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-lv/required_apps_managed_profile.xml b/core/res/res/values-lv/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-lv/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-lv/required_apps_managed_user.xml b/core/res/res/values-lv/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-lv/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 12bb04b..c2199a8 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Bez nosaukuma&gt;"</string>
@@ -1138,6 +1142,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Pieskarieties, lai skatītu visus tīklus"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Izveidot savienojumu"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Visi tīkli"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi tiks automātiski ieslēgts"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kad atrodaties saglabāta augstas kvalitātes tīkla tuvumā"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Neieslēgt atkārtoti"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Pierakstieties Wi-Fi tīklā"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Pierakstīšanās tīklā"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-mk/required_apps_managed_device.xml b/core/res/res/values-mk/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-mk/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-mk/required_apps_managed_profile.xml b/core/res/res/values-mk/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-mk/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-mk/required_apps_managed_user.xml b/core/res/res/values-mk/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-mk/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index c5773f8..e1e2b43 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"Б"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"КБ"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"МБ"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"ГБ"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"ТБ"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"ПБ"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Без наслов&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Допрете за да ги видите сите мрежи"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Поврзете се"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Сите мрежи"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ќе се вклучи автоматски"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Кога сте во близина на зачувана мрежа со висок квалитет"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не вклучувај повторно"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Најавете се на мрежа на Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Најавете се на мрежа"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-ml/required_apps_managed_device.xml b/core/res/res/values-ml/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ml/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ml/required_apps_managed_profile.xml b/core/res/res/values-ml/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ml/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ml/required_apps_managed_user.xml b/core/res/res/values-ml/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ml/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index fc958fa..6a8cea1 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;ശീർഷകമില്ലാത്ത&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"എല്ലാ നെറ്റ്‌വർക്കുകളും കാണാൻ ടാപ്പുചെയ്യുക"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"കണക്റ്റുചെയ്യുക"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"എല്ലാ നെറ്റ്‌വർക്കുകളും"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"വൈഫൈ സ്വമേധയാ ഓണാകും"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"നിങ്ങൾ ഉയർന്ന നിലവാരമുള്ള സംരക്ഷിക്കപ്പെട്ട നെറ്റ്‌വർക്കിനരികിലെത്തുമ്പോൾ"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"തിരികെ ഓണാക്കരുത്"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"വൈഫൈ നെറ്റ്‌വർക്കിലേക്ക് സൈൻ ഇൻ ചെയ്യുക"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"നെറ്റ്‌വർക്കിലേക്ക് സൈൻ ഇൻ ചെയ്യുക"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-mn/required_apps_managed_device.xml b/core/res/res/values-mn/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-mn/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-mn/required_apps_managed_profile.xml b/core/res/res/values-mn/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-mn/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-mn/required_apps_managed_user.xml b/core/res/res/values-mn/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-mn/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 9f26a3b..53740f94 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"килобайт"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"МБ"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"ГБ"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TБ"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"ПБ"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Гарчиггүй&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Бүх сүлжээг харахын тулд товшино уу"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Холбогдох"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Бүх сүлжээ"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi автоматаар асна"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Таныг хадгалсан, өндөр чанартай сүлжээний ойролцоо байх үед"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Буцааж асаахгүй"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fi сүлжээнд нэвтэрнэ үү"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Сүлжээнд нэвтэрнэ үү"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-mr/required_apps_managed_device.xml b/core/res/res/values-mr/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-mr/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-mr/required_apps_managed_profile.xml b/core/res/res/values-mr/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-mr/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-mr/required_apps_managed_user.xml b/core/res/res/values-mr/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-mr/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index a757849..398ad0d 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;अशीर्षकांकित&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"सर्व नेटवर्क पाहण्यासाठी टॅप करा"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"कनेक्ट करा"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"सर्व नेटवर्क"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"वाय-फाय आपोआप चालू होईल"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"तुम्ही जेव्हा सेव्ह केलेल्या उच्च दर्जाच्या नेटवर्कजवळ असाल तेव्हा"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"पुन्हा चालू करू नका"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"वाय-फाय नेटवर्कमध्‍ये साइन इन करा"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"नेटवर्कवर साइन इन करा"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1601,7 +1608,7 @@
     <string name="write_fail_reason_cancelled" msgid="7091258378121627624">"रद्द केले"</string>
     <string name="write_fail_reason_cannot_write" msgid="8132505417935337724">"आशय लिहिण्‍यात एरर"</string>
     <string name="reason_unknown" msgid="6048913880184628119">"अज्ञात"</string>
-    <string name="reason_service_unavailable" msgid="7824008732243903268">"मुद्रण सेवा सक्षम केली नाही"</string>
+    <string name="reason_service_unavailable" msgid="7824008732243903268">"प्रिंट सेवा सक्षम केली नाही"</string>
     <string name="print_service_installed_title" msgid="2246317169444081628">"<xliff:g id="NAME">%s</xliff:g> सेवा स्‍थापित केली"</string>
     <string name="print_service_installed_message" msgid="5897362931070459152">"सक्षम करण्यासाठी टॅप करा"</string>
     <string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"प्रशासक पिन एंटर करा"</string>
diff --git a/core/res/res/values-ms/required_apps_managed_device.xml b/core/res/res/values-ms/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ms/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ms/required_apps_managed_profile.xml b/core/res/res/values-ms/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ms/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ms/required_apps_managed_user.xml b/core/res/res/values-ms/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ms/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index aee2000..574c775 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B."</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Tidak bertajuk&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Ketik untuk melihat semua rangkaian"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Sambung"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Semua Rangkaian"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi akan dihidupkan secara automatik"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Apabila anda berada berdekatan dengan rangkaian disimpan yang berkualiti tinggi"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Jangan hidupkan kembali"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Log masuk ke rangkaian Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Log masuk ke rangkaian"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index fec37ab..9dc49cf 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;ခေါင်းစဉ်မဲ့&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ကွန်ရက်အားလုံးကို ကြည့်ရန် တို့ပါ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ချိတ်ဆက်ရန်"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"ကွန်ရက်အားလုံး"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ကို အလိုအလျောက်​ ပြန်ဖွင့်ပေးလိမ့်ပါမည်"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"သိမ်းဆည်းထားသည့် အရည်အသွေးမြင့်ကွန်ရက်များအနီးသို့ ရောက်ရှိသည့်အခါ"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ပြန်မဖွင့်ပါနှင့်"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"ဝိုင်ဖိုင်ကွန်ရက်သို့ လက်မှတ်ထိုးဝင်ပါ"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ကွန်ယက်သို့ လက်မှတ်ထိုးဝင်ရန်"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-nb/required_apps_managed_device.xml b/core/res/res/values-nb/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-nb/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-nb/required_apps_managed_profile.xml b/core/res/res/values-nb/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-nb/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-nb/required_apps_managed_user.xml b/core/res/res/values-nb/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-nb/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 915701c..d360fa4 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Uten navn&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Trykk for å se alle nettverkene"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Koble til"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Alle nettverk"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi slås på automatisk"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Når du er i nærheten av et lagret nettverk av høy kvalitet"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ikke slå på igjen"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Logg på Wi-Fi-nettverket"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Logg på nettverk"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-ne/required_apps_managed_device.xml b/core/res/res/values-ne/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ne/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ne/required_apps_managed_profile.xml b/core/res/res/values-ne/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ne/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ne/required_apps_managed_user.xml b/core/res/res/values-ne/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ne/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index f0a45a3..3d05239 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"के.बि."</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;बिना शीर्षक&gt;"</string>
@@ -378,9 +382,9 @@
     <string name="permlab_bodySensors" msgid="4683341291818520277">"शरीरका सेन्सरहरूमा पहुँच गराउनुहोस् (जस्तै हृदय धड्कन निगरानीहरू)"</string>
     <string name="permdesc_bodySensors" product="default" msgid="4380015021754180431">"तपाईँको हृदय गति जस्तो सेंसर बाट डेटा पहुँचको लागि अनुप्रयोग अनुमति दिन्छ जसले तपाईँको भौतिक अवस्था अनुगमन गर्छ।"</string>
     <string name="permlab_readCalendar" msgid="6716116972752441641">"पात्रोका कार्यक्रम र विवरणहरू पढ्ने"</string>
-    <string name="permdesc_readCalendar" product="tablet" msgid="4993979255403945892">"यस अनुप्रयोगले तपाईंको ट्याब्लेटमा भण्डारण गरिएका पात्रो सम्बन्धी सबै कार्यक्रमहरू पढ्न र तपाईंको पात्रोको डेटा आदान-प्रदान वा सुरक्षित गर्न सक्छ।"</string>
-    <string name="permdesc_readCalendar" product="tv" msgid="8837931557573064315">"यस अनुप्रयोगले तपाईंको TV मा भण्डारण गरिएका पात्रो सम्बन्धी सबै कार्यक्रमहरू पढ्न र तपाईंको पात्रोको डेटा आदान-प्रदान वा सुरक्षित गर्न सक्छ।"</string>
-    <string name="permdesc_readCalendar" product="default" msgid="4373978642145196715">"यस अनुप्रयोगले तपाईंको फोनमा भण्डारण गरिएका पात्रो सम्बन्धी सबै कार्यक्रमहरू पढ्न र तपाईंको पात्रोको डेटा आदान-प्रदान वा सुरक्षित गर्न सक्छ।"</string>
+    <string name="permdesc_readCalendar" product="tablet" msgid="4993979255403945892">"यस अनुप्रयोगले तपाईंको ट्याब्लेटमा भण्डारण गरिएका पात्रो सम्बन्धी सबै कार्यक्रमहरू पढ्न र तपाईंको पात्रोको डेटा आदान प्रदान वा सुरक्षित गर्न सक्छ।"</string>
+    <string name="permdesc_readCalendar" product="tv" msgid="8837931557573064315">"यस अनुप्रयोगले तपाईंको TV मा भण्डारण गरिएका पात्रो सम्बन्धी सबै कार्यक्रमहरू पढ्न र तपाईंको पात्रोको डेटा आदान प्रदान वा सुरक्षित गर्न सक्छ।"</string>
+    <string name="permdesc_readCalendar" product="default" msgid="4373978642145196715">"यस अनुप्रयोगले तपाईंको फोनमा भण्डारण गरिएका पात्रो सम्बन्धी सबै कार्यक्रमहरू पढ्न र तपाईंको पात्रोको डेटा आदान प्रदान वा सुरक्षित गर्न सक्छ।"</string>
     <string name="permlab_writeCalendar" msgid="8438874755193825647">"पात्रो घटनाहरू थप्नुहोस् वा परिमार्जन गर्नुहोस् र मालिकको ज्ञान बिना नै पाहुनाहरूलाई इमेल पठाउनुहोस्"</string>
     <string name="permdesc_writeCalendar" product="tablet" msgid="1675270619903625982">"यस अनुप्रयोगले तपाईंको ट्याब्लेटमा पात्रोका कार्यक्रमहरू थप्न, हटाउन वा परिवर्तन गर्न सक्छ। यस अनुप्रयोगले पात्रोका मालिकहरू मार्फत आएको जस्तो लाग्ने सन्देशहरू पठाउन वा तिनीहरूका मालिकहरूलाई सूचित नगरिकन कार्यक्रमहरू परिवर्तन गर्न सक्छ।"</string>
     <string name="permdesc_writeCalendar" product="tv" msgid="9017809326268135866">"यस अनुप्रयोगले तपाईंको TV मा पात्रोका कार्यक्रमहरू थप्न, हटाउन वा परिवर्तन गर्न सक्छ। यस अनुप्रयोगले पात्रोका मालिकहरू मार्फत आएको जस्तो लाग्ने सन्देशहरू पठाउन वा तिनीहरूका मालिकहरूलाई सूचित नगरिकन कार्यक्रमहरू परिवर्तन गर्न सक्छ।"</string>
@@ -1122,6 +1126,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"सबै नेटवर्कहरू हेर्न ट्याप गर्नुहोस्"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"जडान गर्नुहोस्"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"सबै नेटवर्कहरू"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi स्वतः सक्रिय हुनेछ"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"तपाईं कुनै सुरक्षित गरिएको उच्च गुणस्तरीय नेटवर्कको नजिक हुनुभएको अवस्थामा"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"फेरि सक्रिय नगर्नुहोला"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fi नेटवर्कमा साइन इन गर्नुहोस्"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"सञ्जालमा साइन इन गर्नुहोस्"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1209,7 +1216,7 @@
     <string name="taking_remote_bugreport_notification_title" msgid="6742483073875060934">"बग रिपोर्ट लिँदै..."</string>
     <string name="share_remote_bugreport_notification_title" msgid="4987095013583691873">"बग रिपोर्टलाई साझेदारी गर्ने हो?"</string>
     <string name="sharing_remote_bugreport_notification_title" msgid="7572089031496651372">"बग रिपोर्टलाई साझेदारी गर्दै ..."</string>
-    <string name="share_remote_bugreport_notification_message_finished" msgid="6029609949340992866">"तपाईंका प्रशासकले यस यन्त्रको समस्या निवारण गर्नमा मद्दत गर्नका लागि एउटा बग रिपोर्टको अनुरोध गर्नुभएको छ। अनुप्रयोगहरू र डेटा आदान-प्रदान गर्न पनि सकिन्छ।"</string>
+    <string name="share_remote_bugreport_notification_message_finished" msgid="6029609949340992866">"तपाईंका प्रशासकले यस यन्त्रको समस्या निवारण गर्नमा मद्दत गर्नका लागि एउटा बग रिपोर्टको अनुरोध गर्नुभएको छ। अनुप्रयोगहरू र डेटा आदान प्रदान गर्न पनि सकिन्छ।"</string>
     <string name="share_remote_bugreport_action" msgid="6249476773913384948">"साझेदारी गर्नुहोस्"</string>
     <string name="decline_remote_bugreport_action" msgid="6230987241608770062">"अस्वीकार गर्नुहोस्"</string>
     <string name="select_input_method" msgid="8547250819326693584">"कुञ्जीपाटी परिवर्तन गर्नुहोस्"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 5ae838e..725fe8e 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">" KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Naamloos&gt;"</string>
@@ -835,8 +839,8 @@
     <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="6825527469145760922">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bladwijzers die zijn opgeslagen op je tablet. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden.."</string>
     <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="7007393823197766548">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bladwijzers die zijn opgeslagen op je tv. De app kan browsergegevens wissen of aanpassen. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string>
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bladwijzers die zijn opgeslagen op je telefoon. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string>
-    <string name="permlab_setAlarm" msgid="1379294556362091814">"een alarm instellen"</string>
-    <string name="permdesc_setAlarm" msgid="316392039157473848">"Hiermee kan de app een alarm instellen in een geïnstalleerde wekkerapp. Deze functie wordt door sommige wekkerapps niet geïmplementeerd."</string>
+    <string name="permlab_setAlarm" msgid="1379294556362091814">"een wekker instellen"</string>
+    <string name="permdesc_setAlarm" msgid="316392039157473848">"Hiermee kan de app een wekker instellen in een geïnstalleerde wekker-app. Deze functie wordt door sommige wekker-apps niet geïmplementeerd."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"voicemail toevoegen"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Hiermee kan de app berichten toevoegen aan de inbox van je voicemail."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"geolocatiemachtigingen voor browser aanpassen"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tik om alle netwerken te bekijken"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Verbinding maken"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Alle netwerken"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wifi wordt automatisch ingeschakeld"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Wanneer je in de buurt van een opgeslagen netwerk van hoge kwaliteit bent"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Niet weer inschakelen"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Inloggen bij wifi-netwerk"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Inloggen bij netwerk"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1677,7 +1684,7 @@
       <item quantity="one">Gedurende 1 u</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
-    <string name="zen_mode_alarm" msgid="9128205721301330797">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (volgend alarm)"</string>
+    <string name="zen_mode_alarm" msgid="9128205721301330797">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (volgende wekker)"</string>
     <string name="zen_mode_forever" msgid="1916263162129197274">"Totdat je \'Niet storen\' uitschakelt"</string>
     <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Totdat u \'Niet storen\' uitschakelt"</string>
     <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-pa/required_apps_managed_device.xml b/core/res/res/values-pa/required_apps_managed_device.xml
deleted file mode 100644
index faadc50..0000000
--- a/core/res/res/values-pa/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.download"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-pa/required_apps_managed_profile.xml b/core/res/res/values-pa/required_apps_managed_profile.xml
deleted file mode 100644
index 537a80c..0000000
--- a/core/res/res/values-pa/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.download"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-pa/required_apps_managed_user.xml b/core/res/res/values-pa/required_apps_managed_user.xml
deleted file mode 100644
index e69bbbc..0000000
--- a/core/res/res/values-pa/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.download"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index b5b6a00..cf90550 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;ਬਿਨਾਂ ਸਿਰਲੇਖ&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ਸਾਰੇ ਨੈੱਟਵਰਕਾਂ ਨੂੰ ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ਕਨੈਕਟ ਕਰੋ"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"ਸਾਰੇ ਨੈੱਟਵਰਕ"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ਵਾਈ‑ਫਾਈ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਚੱਲ ਪਵੇਗਾ"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ਜਦੋਂ ਤੁਸੀਂ ਕਿਸੇ ਰੱਖਿਅਤ ਕੀਤੇ ਉੱਚ-ਗੁਣਵੱਤਾ ਵਾਲੇ ਨੈੱਟਵਰਕ ਦੇ ਨੇੜੇ ਹੋਵੋ"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ਵਾਪਸ ਚਾਲੂ ਨਾ ਕਰੋ"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ਨੈੱਟਵਰਕ \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-pl/required_apps_managed_device.xml b/core/res/res/values-pl/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-pl/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-pl/required_apps_managed_profile.xml b/core/res/res/values-pl/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-pl/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-pl/required_apps_managed_user.xml b/core/res/res/values-pl/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-pl/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 77d706a..412d5a7 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Bez nazwy&gt;"</string>
@@ -392,9 +396,9 @@
     <string name="permdesc_writeCalendar" product="tv" msgid="9017809326268135866">"Ta aplikacja może dodawać, usuwać i zmieniać wydarzenia z kalendarza na telewizorze. Ta aplikacja może wysyłać wiadomości wyglądające jak utworzone przez właścicieli kalendarza lub zmieniać wydarzenia bez wiedzy ich właścicieli."</string>
     <string name="permdesc_writeCalendar" product="default" msgid="7592791790516943173">"Ta aplikacja może dodawać, usuwać i zmieniać wydarzenia z kalendarza na telefonie. Ta aplikacja może wysyłać wiadomości wyglądające jak utworzone przez właścicieli kalendarza lub zmieniać wydarzenia bez wiedzy ich właścicieli."</string>
     <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"dostęp do dodatkowych poleceń dostawcy informacji o lokalizacji"</string>
-    <string name="permdesc_accessLocationExtraCommands" msgid="6078307221056649927">"Pozwala aplikacji na dostęp do dodatkowych poleceń dostawcy informacji o lokalizacji. Aplikacje z tym uprawnieniem mogą wpływać na działanie GPS-u lub innych źródeł lokalizacji."</string>
-    <string name="permlab_accessFineLocation" msgid="251034415460950944">"dostęp do dokładnej lokalizacji (na podstawie GPS-u i sieci)"</string>
-    <string name="permdesc_accessFineLocation" msgid="5821994817969957884">"Ta aplikacja może określać Twoją lokalizację na podstawie GPS-u lub sieciowych źródeł lokalizacji, takich jak stacje bazowe i sieci Wi-Fi. Te usługi lokalizacyjne muszą być włączone i dostępne na telefonie, by aplikacja mogła z nich korzystać. Może to zwiększyć zużycie baterii."</string>
+    <string name="permdesc_accessLocationExtraCommands" msgid="6078307221056649927">"Pozwala aplikacji na dostęp do dodatkowych poleceń dostawcy informacji o lokalizacji. Aplikacje z tym uprawnieniem mogą wpływać na działanie GPS-a lub innych źródeł lokalizacji."</string>
+    <string name="permlab_accessFineLocation" msgid="251034415460950944">"dostęp do dokładnej lokalizacji (na podstawie GPS-a i sieci)"</string>
+    <string name="permdesc_accessFineLocation" msgid="5821994817969957884">"Ta aplikacja może określać Twoją lokalizację na podstawie GPS-a lub sieciowych źródeł lokalizacji, takich jak stacje bazowe i sieci Wi-Fi. Te usługi lokalizacyjne muszą być włączone i dostępne na telefonie, by aplikacja mogła z nich korzystać. Może to zwiększyć zużycie baterii."</string>
     <string name="permlab_accessCoarseLocation" msgid="7715277613928539434">"dostęp do przybliżonej lokalizacji (na podstawie sieci)"</string>
     <string name="permdesc_accessCoarseLocation" product="tablet" msgid="3373266766487862426">"Ta aplikacja może określać Twoją lokalizację na podstawie źródeł sieciowych, takich jak stacje bazowe i sieci Wi-Fi. Te usługi lokalizacyjne muszą być włączone i dostępne na tablecie, by aplikacja mogła z nich korzystać."</string>
     <string name="permdesc_accessCoarseLocation" product="tv" msgid="1884022719818788511">"Ta aplikacja może określać Twoją lokalizację na podstawie źródeł sieciowych, takich jak stacje bazowe i sieci Wi-Fi. Te usługi lokalizacyjne muszą być włączone i dostępne na telewizorze, by aplikacja mogła z nich korzystać."</string>
@@ -1160,6 +1164,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Kliknij, by zobaczyć wszystkie sieci"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Połącz"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Wszystkie sieci"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi włączy się automatycznie"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Gdy znajdziesz się w pobliżu zapisanej sieci o mocnym sygnale"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nie włączaj ponownie"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Zaloguj się w sieci Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Zaloguj się do sieci"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1260,7 +1267,7 @@
     <string name="alert_windows_notification_channel_group_name" msgid="1463953341148606396">"Wyświetlanie nad innymi aplikacjami"</string>
     <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"Wyświetlanie aplikacji <xliff:g id="NAME">%s</xliff:g> nad innymi"</string>
     <string name="alert_windows_notification_title" msgid="3697657294867638947">"Aplikacja <xliff:g id="NAME">%s</xliff:g> jest nad innymi"</string>
-    <string name="alert_windows_notification_message" msgid="8917232109522912560">"Jeśli nie chcesz, by aplikacja <xliff:g id="NAME">%s</xliff:g> korzystała z tej funkcji, otwórz ustawienia i ją wyłącz."</string>
+    <string name="alert_windows_notification_message" msgid="8917232109522912560">"Jeśli nie chcesz, by aplikacja <xliff:g id="NAME">%s</xliff:g> korzystała z tej funkcji, otwórz ustawienia i wyłącz ją."</string>
     <string name="alert_windows_notification_turn_off_action" msgid="3367294525884949878">"WYŁĄCZ"</string>
     <string name="ext_media_checking_notification_title" msgid="5734005953288045806">"Przygotowuję: <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_checking_notification_message" msgid="4747432538578886744">"Sprawdzanie w poszukiwaniu błędów"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index cce4bf0..bf83d77 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Sem título&gt;"</string>
@@ -200,9 +204,9 @@
     <string name="shutdown_confirm" product="tv" msgid="476672373995075359">"Sua TV será desligada."</string>
     <string name="shutdown_confirm" product="watch" msgid="3490275567476369184">"Seu relógio será desligado."</string>
     <string name="shutdown_confirm" product="default" msgid="649792175242821353">"O seu telefone será desligado."</string>
-    <string name="shutdown_confirm_question" msgid="2906544768881136183">"Deseja desligar?"</string>
+    <string name="shutdown_confirm_question" msgid="2906544768881136183">"Quer desligar?"</string>
     <string name="reboot_safemode_title" msgid="7054509914500140361">"Reiniciar no modo de segurança"</string>
-    <string name="reboot_safemode_confirm" msgid="55293944502784668">"Deseja reiniciar no modo de segurança? Isso desativará todos os apps de terceiros instalados. Eles serão restaurados quando você reiniciar novamente."</string>
+    <string name="reboot_safemode_confirm" msgid="55293944502784668">"Quer reiniciar no modo de segurança? Isso desativará todos os apps de terceiros instalados. Eles serão restaurados quando você reiniciar novamente."</string>
     <string name="recent_tasks_title" msgid="3691764623638127888">"Recente"</string>
     <string name="no_recent_tasks" msgid="8794906658732193473">"Nenhum app recente"</string>
     <string name="global_actions" product="tablet" msgid="408477140088053665">"Opções do tablet"</string>
@@ -808,7 +812,7 @@
     <string name="js_dialog_before_unload_title" msgid="2619376555525116593">"Confirmar navegação"</string>
     <string name="js_dialog_before_unload_positive_button" msgid="3112752010600484130">"Sair desta página"</string>
     <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Permanecer nesta página"</string>
-    <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nTem certeza de que deseja sair desta página?"</string>
+    <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nTem certeza que quer sair desta página?"</string>
     <string name="save_password_label" msgid="6860261758665825069">"Confirmar"</string>
     <string name="double_tap_toast" msgid="4595046515400268881">"Dica: toque duas vezes para aumentar e diminuir o zoom."</string>
     <string name="autofill_this_form" msgid="4616758841157816676">"Preench. aut."</string>
@@ -841,7 +845,7 @@
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que o app adicione mensagens a sua caixa de entrada do correio de voz."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modifique as permissões de geolocalização de seu navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que o app modifique as permissões de geolocalização do navegador. Apps maliciosos podem usar isso para permitir o envio de informações locais para sites arbitrários."</string>
-    <string name="save_password_message" msgid="767344687139195790">"Deseja que o navegador lembre desta senha?"</string>
+    <string name="save_password_message" msgid="767344687139195790">"Quer que o navegador lembre desta senha?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"Agora não"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"Lembrar"</string>
     <string name="save_password_never" msgid="8274330296785855105">"Nunca"</string>
@@ -1043,7 +1047,7 @@
     <string name="force_close" msgid="8346072094521265605">"OK"</string>
     <string name="report" msgid="4060218260984795706">"Informar"</string>
     <string name="wait" msgid="7147118217226317732">"Aguardar"</string>
-    <string name="webpage_unresponsive" msgid="3272758351138122503">"A página não responde.\n\nDeseja fechá-la?"</string>
+    <string name="webpage_unresponsive" msgid="3272758351138122503">"A página não responde.\n\nQuer fechá-la?"</string>
     <string name="launch_warning_title" msgid="1547997780506713581">"App redirecionado"</string>
     <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> não está em execução."</string>
     <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> foi iniciado."</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toque para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectar"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Todas as redes"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"O Wi‑Fi será ativado automaticamente"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando você estiver perto de uma rede salva de alta qualidade"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Não ativar novamente"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Fazer login na rede Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Fazer login na rede"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1156,7 +1163,7 @@
     <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"O telefone desconectará temporariamente da rede Wi-Fi enquanto estiver conectado a <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="select_character" msgid="3365550120617701745">"Inserir caractere"</string>
     <string name="sms_control_title" msgid="7296612781128917719">"Enviando mensagens SMS"</string>
-    <string name="sms_control_message" msgid="3867899169651496433">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; envia uma grande quantidade de mensagens SMS. Deseja permitir que este app continue enviando mensagens?"</string>
+    <string name="sms_control_message" msgid="3867899169651496433">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; envia uma grande quantidade de mensagens SMS. Quer permitir que este app continue enviando mensagens?"</string>
     <string name="sms_control_yes" msgid="3663725993855816807">"Permitir"</string>
     <string name="sms_control_no" msgid="625438561395534982">"Negar"</string>
     <string name="sms_short_code_confirm_message" msgid="1645436466285310855">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; deseja enviar uma mensagem para &lt;b&gt;<xliff:g id="DEST_ADDRESS">%2$s</xliff:g>&lt;/b&gt;."</string>
@@ -1279,7 +1286,7 @@
     <string name="dial_number_using" msgid="5789176425167573586">"Discar número\nusando <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="create_contact_using" msgid="4947405226788104538">"Criar contato \nusando <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="grant_credentials_permission_message_header" msgid="2106103817937859662">"O app a seguir ou outros apps solicitam permissão para acessar sua conta, agora e no futuro."</string>
-    <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Deseja permitir essa solicitação?"</string>
+    <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Quer permitir essa solicitação?"</string>
     <string name="grant_permissions_header_text" msgid="6874497408201826708">"Solicitação de acesso"</string>
     <string name="allow" msgid="7225948811296386551">"Permitir"</string>
     <string name="deny" msgid="2081879885755434506">"Negar"</string>
diff --git a/core/res/res/values-pt-rPT/required_apps_managed_device.xml b/core/res/res/values-pt-rPT/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-pt-rPT/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-pt-rPT/required_apps_managed_profile.xml b/core/res/res/values-pt-rPT/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-pt-rPT/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-pt-rPT/required_apps_managed_user.xml b/core/res/res/values-pt-rPT/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-pt-rPT/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index dc2d6e4..31b67a0 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Sem nome&gt;"</string>
@@ -951,7 +955,7 @@
       <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> ano</item>
     </plurals>
     <string name="VideoView_error_title" msgid="3534509135438353077">"Problema com o vídeo"</string>
-    <string name="VideoView_error_text_invalid_progressive_playback" msgid="3186670335938670444">"Este vídeo não é válido para transmissão em fluxo contínuo neste aparelho."</string>
+    <string name="VideoView_error_text_invalid_progressive_playback" msgid="3186670335938670444">"Este vídeo não é válido para transmissão neste aparelho."</string>
     <string name="VideoView_error_text_unknown" msgid="3450439155187810085">"Não é possível reproduzir este vídeo."</string>
     <string name="VideoView_error_button" msgid="2822238215100679592">"OK"</string>
     <string name="relative_time" msgid="1818557177829411417">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toque para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Ligar"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Todas as redes"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"O Wi‑Fi será ativado automaticamente"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando estiver próximo de uma rede de alta qualidade guardada."</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Não reativar"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Iniciar sessão na rede Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Início de sessão na rede"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index cce4bf0..bf83d77 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Sem título&gt;"</string>
@@ -200,9 +204,9 @@
     <string name="shutdown_confirm" product="tv" msgid="476672373995075359">"Sua TV será desligada."</string>
     <string name="shutdown_confirm" product="watch" msgid="3490275567476369184">"Seu relógio será desligado."</string>
     <string name="shutdown_confirm" product="default" msgid="649792175242821353">"O seu telefone será desligado."</string>
-    <string name="shutdown_confirm_question" msgid="2906544768881136183">"Deseja desligar?"</string>
+    <string name="shutdown_confirm_question" msgid="2906544768881136183">"Quer desligar?"</string>
     <string name="reboot_safemode_title" msgid="7054509914500140361">"Reiniciar no modo de segurança"</string>
-    <string name="reboot_safemode_confirm" msgid="55293944502784668">"Deseja reiniciar no modo de segurança? Isso desativará todos os apps de terceiros instalados. Eles serão restaurados quando você reiniciar novamente."</string>
+    <string name="reboot_safemode_confirm" msgid="55293944502784668">"Quer reiniciar no modo de segurança? Isso desativará todos os apps de terceiros instalados. Eles serão restaurados quando você reiniciar novamente."</string>
     <string name="recent_tasks_title" msgid="3691764623638127888">"Recente"</string>
     <string name="no_recent_tasks" msgid="8794906658732193473">"Nenhum app recente"</string>
     <string name="global_actions" product="tablet" msgid="408477140088053665">"Opções do tablet"</string>
@@ -808,7 +812,7 @@
     <string name="js_dialog_before_unload_title" msgid="2619376555525116593">"Confirmar navegação"</string>
     <string name="js_dialog_before_unload_positive_button" msgid="3112752010600484130">"Sair desta página"</string>
     <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Permanecer nesta página"</string>
-    <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nTem certeza de que deseja sair desta página?"</string>
+    <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nTem certeza que quer sair desta página?"</string>
     <string name="save_password_label" msgid="6860261758665825069">"Confirmar"</string>
     <string name="double_tap_toast" msgid="4595046515400268881">"Dica: toque duas vezes para aumentar e diminuir o zoom."</string>
     <string name="autofill_this_form" msgid="4616758841157816676">"Preench. aut."</string>
@@ -841,7 +845,7 @@
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que o app adicione mensagens a sua caixa de entrada do correio de voz."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modifique as permissões de geolocalização de seu navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que o app modifique as permissões de geolocalização do navegador. Apps maliciosos podem usar isso para permitir o envio de informações locais para sites arbitrários."</string>
-    <string name="save_password_message" msgid="767344687139195790">"Deseja que o navegador lembre desta senha?"</string>
+    <string name="save_password_message" msgid="767344687139195790">"Quer que o navegador lembre desta senha?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"Agora não"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"Lembrar"</string>
     <string name="save_password_never" msgid="8274330296785855105">"Nunca"</string>
@@ -1043,7 +1047,7 @@
     <string name="force_close" msgid="8346072094521265605">"OK"</string>
     <string name="report" msgid="4060218260984795706">"Informar"</string>
     <string name="wait" msgid="7147118217226317732">"Aguardar"</string>
-    <string name="webpage_unresponsive" msgid="3272758351138122503">"A página não responde.\n\nDeseja fechá-la?"</string>
+    <string name="webpage_unresponsive" msgid="3272758351138122503">"A página não responde.\n\nQuer fechá-la?"</string>
     <string name="launch_warning_title" msgid="1547997780506713581">"App redirecionado"</string>
     <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> não está em execução."</string>
     <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> foi iniciado."</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toque para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectar"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Todas as redes"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"O Wi‑Fi será ativado automaticamente"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando você estiver perto de uma rede salva de alta qualidade"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Não ativar novamente"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Fazer login na rede Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Fazer login na rede"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1156,7 +1163,7 @@
     <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"O telefone desconectará temporariamente da rede Wi-Fi enquanto estiver conectado a <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="select_character" msgid="3365550120617701745">"Inserir caractere"</string>
     <string name="sms_control_title" msgid="7296612781128917719">"Enviando mensagens SMS"</string>
-    <string name="sms_control_message" msgid="3867899169651496433">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; envia uma grande quantidade de mensagens SMS. Deseja permitir que este app continue enviando mensagens?"</string>
+    <string name="sms_control_message" msgid="3867899169651496433">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; envia uma grande quantidade de mensagens SMS. Quer permitir que este app continue enviando mensagens?"</string>
     <string name="sms_control_yes" msgid="3663725993855816807">"Permitir"</string>
     <string name="sms_control_no" msgid="625438561395534982">"Negar"</string>
     <string name="sms_short_code_confirm_message" msgid="1645436466285310855">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; deseja enviar uma mensagem para &lt;b&gt;<xliff:g id="DEST_ADDRESS">%2$s</xliff:g>&lt;/b&gt;."</string>
@@ -1279,7 +1286,7 @@
     <string name="dial_number_using" msgid="5789176425167573586">"Discar número\nusando <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="create_contact_using" msgid="4947405226788104538">"Criar contato \nusando <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="grant_credentials_permission_message_header" msgid="2106103817937859662">"O app a seguir ou outros apps solicitam permissão para acessar sua conta, agora e no futuro."</string>
-    <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Deseja permitir essa solicitação?"</string>
+    <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Quer permitir essa solicitação?"</string>
     <string name="grant_permissions_header_text" msgid="6874497408201826708">"Solicitação de acesso"</string>
     <string name="allow" msgid="7225948811296386551">"Permitir"</string>
     <string name="deny" msgid="2081879885755434506">"Negar"</string>
diff --git a/core/res/res/values-ro/required_apps_managed_device.xml b/core/res/res/values-ro/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ro/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ro/required_apps_managed_profile.xml b/core/res/res/values-ro/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ro/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ro/required_apps_managed_user.xml b/core/res/res/values-ro/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ro/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index eece63e..4f15c33 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TO"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PO"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Fără titlu&gt;"</string>
@@ -1138,6 +1142,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Atingeți pentru a vedea toate rețelele"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectați-vă"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Toate rețelele"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi se va activa automat"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Când vă aflați lângă o rețea salvată, de înaltă calitate"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nu reactivați"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Conectați-vă la rețeaua Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Conectați-vă la rețea"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 53930c1..60ad441 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"Б"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"КБ"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"МБ"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"ГБ"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TБ"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"ПБ"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Без названия&gt;"</string>
@@ -1160,6 +1164,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Нажмите, чтобы увидеть список сетей"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Подключиться"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Все сети"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi включится автоматически"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Когда вы будете в зоне действия сохраненной сети с хорошим сигналом."</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не включать снова"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Подключение к Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Регистрация в сети"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 2a1d635..5b0eda3 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;නම් යොදා නැත&gt;"</string>
@@ -1118,6 +1122,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"සියලු ජාල බැලීමට තට්ටු කරන්න"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"සම්බන්ධ කරන්න"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"සියලු ජාල"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ස්වයංක්‍රියව ක්‍රියාත්මක වනු ඇත"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ඔබ උසස් තත්ත්වයේ සුරැකි ජාලයක් අවට සිටින විට"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"නැවත ක්‍රියාත්මක නොකරන්න"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fi ජාලයට පුරනය වන්න"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ජාලයට පුරනය වන්න"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-sk/required_apps_managed_device.xml b/core/res/res/values-sk/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-sk/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sk/required_apps_managed_profile.xml b/core/res/res/values-sk/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-sk/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sk/required_apps_managed_user.xml b/core/res/res/values-sk/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-sk/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index a0de5eb..1f11fb4 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Bez mena&gt;"</string>
@@ -1160,6 +1164,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Klepnutím zobrazíte všetky siete"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Pripojiť"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Všetky siete"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi sa zapne automaticky"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Keď budete v blízkosti kvalitnej uloženej siete"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Znova nezapínať"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Prihlásiť sa do siete Wi‑Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Prihlásenie do siete"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-sl/required_apps_managed_device.xml b/core/res/res/values-sl/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-sl/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sl/required_apps_managed_profile.xml b/core/res/res/values-sl/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-sl/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sl/required_apps_managed_user.xml b/core/res/res/values-sl/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-sl/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index fb96279..e8f89a5 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Brez naslova&gt;"</string>
@@ -1160,6 +1164,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dotaknite se, če si želite ogledati vsa omrežja"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Vzpostavi povezavo"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Vsa omrežja"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Povezava Wi‑Fi se bo samodejno vklopila"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Ko ste v bližini zanesljivega shranjenega omrežja"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne vklopi znova"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Prijavite se v omrežje Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Prijava v omrežje"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-sq/required_apps_managed_device.xml b/core/res/res/values-sq/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-sq/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sq/required_apps_managed_profile.xml b/core/res/res/values-sq/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-sq/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sq/required_apps_managed_user.xml b/core/res/res/values-sq/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-sq/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 4aee9bc..a03ac24 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"terabajt"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"petabajt"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Pa titull&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Trokit për të parë të gjitha rrjetet"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Lidhu"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Të gjitha rrjetet"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi do të aktivizohet automatikisht"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kur ndodhesh pranë një rrjeti të ruajtur me cilësi të lartë"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Mos e aktivizo përsëri"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Identifikohu në rrjetin Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Identifikohu në rrjet"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-sr/required_apps_managed_device.xml b/core/res/res/values-sr/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-sr/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sr/required_apps_managed_profile.xml b/core/res/res/values-sr/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-sr/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sr/required_apps_managed_user.xml b/core/res/res/values-sr/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-sr/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 146f8b3..8bec119 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Без наслова&gt;"</string>
@@ -1138,6 +1142,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Додирните да бисте видели све мреже"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Повежи"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Све мреже"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ће се аутоматски укључити"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Када сте у близини сачуване мреже високог квалитета"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не укључуј поново"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Пријављивање на Wi-Fi мрежу"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Пријавите се на мрежу"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-sv/required_apps_managed_device.xml b/core/res/res/values-sv/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-sv/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sv/required_apps_managed_profile.xml b/core/res/res/values-sv/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-sv/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sv/required_apps_managed_user.xml b/core/res/res/values-sv/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-sv/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 2705326..1f590d3 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Okänd&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tryck för att visa alla nätverk"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Anslut"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Alla nätverk"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi aktiveras automatiskt"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"När du är i närheten av ett sparat nätverk av hög kvalitet"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Återaktivera inte"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Logga in på ett Wi-Fi-nätverk"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Logga in på nätverket"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-sw/required_apps_managed_device.xml b/core/res/res/values-sw/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-sw/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sw/required_apps_managed_profile.xml b/core/res/res/values-sw/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-sw/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sw/required_apps_managed_user.xml b/core/res/res/values-sw/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-sw/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index a8ab410..bd8b029 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Haina jina&gt;"</string>
@@ -1114,6 +1118,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Gusa ili uone mitandao yote"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Unganisha"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Mitandao Yote"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi itawashwa kiotomatiki"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Unapokuwa karibu na mtandao uliohifadhiwa wenye ubora wa juu"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Usiwashe tena"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Ingia kwa mtandao wa Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Ingia katika mtandao"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
@@ -1730,7 +1737,7 @@
     <string name="work_mode_turn_on" msgid="2062544985670564875">"Washa"</string>
     <string name="new_sms_notification_title" msgid="8442817549127555977">"Una ujumbe mpya"</string>
     <string name="new_sms_notification_content" msgid="7002938807812083463">"Fungua programu ya SMS ili uweze kuangalia"</string>
-    <string name="user_encrypted_title" msgid="9054897468831672082">"Huenda baadhi ya utendaji ukawa vikwazo"</string>
+    <string name="user_encrypted_title" msgid="9054897468831672082">"Huenda baadhi ya vipengele havifanyi kazi"</string>
     <string name="user_encrypted_message" msgid="4923292604515744267">"Gusa ili ufungue"</string>
     <string name="user_encrypted_detail" msgid="5708447464349420392">"Data ya mtumiaji imefungwa"</string>
     <string name="profile_encrypted_detail" msgid="3700965619978314974">"Wasifu wa kazini umefungwa"</string>
diff --git a/core/res/res/values-ta/required_apps_managed_device.xml b/core/res/res/values-ta/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ta/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ta/required_apps_managed_profile.xml b/core/res/res/values-ta/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ta/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ta/required_apps_managed_user.xml b/core/res/res/values-ta/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ta/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index c517223..6d247f4 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"பை."</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"கி.பை."</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"மெ.பை."</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"ஜி.பை."</string>
+    <string name="terabyteShort" msgid="231613018159186962">"டெ.பை."</string>
     <string name="petabyteShort" msgid="5637816680144990219">"பெ.பை."</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;பெயரிடப்படாதது&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"எல்லா நெட்வொர்க்குகளையும் பார்க்க, தட்டவும்"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"இணை"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"எல்லா நெட்வொர்க்குகளும்"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"வைஃபை தானாக ஆன் ஆகும்"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"சேமித்த, உயர்தர நெட்வொர்க்கிற்கு அருகில் இருக்கும்போது"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"மீண்டும் ஆன் செய்யாதே"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"வைஃபை நெட்வொர்க்கில் உள்நுழையவும்"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"நெட்வொர்க்கில் உள்நுழையவும்"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-te/required_apps_managed_device.xml b/core/res/res/values-te/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-te/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-te/required_apps_managed_profile.xml b/core/res/res/values-te/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-te/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-te/required_apps_managed_user.xml b/core/res/res/values-te/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-te/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index fd103bc..d39467b 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;శీర్షిక లేనిది&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"అన్ని నెట్‌వర్క్‌లు చూడటానికి నొక్కండి"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"కనెక్ట్ చేయి"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"అన్ని నెట్‌వర్క్‌లు"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi స్వయంచాలకంగా ఆన్ అవుతుంది"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"మీరు అధిక నాణ్యత గల సేవ్ చేసిన నెట్‌వర్క్‌కు సమీపంగా ఉన్నప్పుడు"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"తిరిగి ఆన్ చేయవద్దు"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fi నెట్‌వర్క్‌కి సైన్ ఇన్ చేయండి"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"నెట్‌వర్క్‌కి సైన్ ఇన్ చేయండి"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-th/required_apps_managed_device.xml b/core/res/res/values-th/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-th/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-th/required_apps_managed_profile.xml b/core/res/res/values-th/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-th/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-th/required_apps_managed_user.xml b/core/res/res/values-th/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-th/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 2515241..1bea4e4 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;ไม่มีชื่อ&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"แตะเพื่อดูเครือข่ายทั้งหมด"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"เชื่อมต่อ"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"เครือข่ายทั้งหมด"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi จะเปิดโดยอัตโนมัติ"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"เมื่อคุณอยู่ใกล้เครือข่ายคุณภาพสูงที่บันทึกไว้"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ไม่ต้องเปิดอีกครั้ง"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"ลงชื่อเข้าใช้เครือข่าย WiFi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"ลงชื่อเข้าใช้เครือข่าย"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-tl/required_apps_managed_device.xml b/core/res/res/values-tl/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-tl/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-tl/required_apps_managed_profile.xml b/core/res/res/values-tl/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-tl/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-tl/required_apps_managed_user.xml b/core/res/res/values-tl/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-tl/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index d30267b..2bc5780 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Walang pamagat&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"I-tap upang makita ang lahat ng network"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Kumonekta"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Lahat ng Network"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Awtomatikong mag-o-on ang Wi‑Fi"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kapag malapit ka sa naka-save na network na mataas ang kalidad"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Huwag i-on muli"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Mag-sign in sa Wi-Fi network"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Mag-sign in sa network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-tr/required_apps_managed_device.xml b/core/res/res/values-tr/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-tr/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-tr/required_apps_managed_profile.xml b/core/res/res/values-tr/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-tr/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-tr/required_apps_managed_user.xml b/core/res/res/values-tr/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-tr/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index e6646c4..337d458 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Adsız&gt;"</string>
@@ -270,7 +274,7 @@
     <string name="permgroupdesc_calendar" msgid="3889615280211184106">"takviminize erişme"</string>
     <string name="permgrouprequest_calendar" msgid="6704529828699071445">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasının takviminize erişmesine izin verin"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
-    <string name="permgroupdesc_sms" msgid="4656988620100940350">"SMS iletileri gönderme ve görüntüleme"</string>
+    <string name="permgroupdesc_sms" msgid="4656988620100940350">"SMS mesajları gönderme ve görüntüleme"</string>
     <string name="permgrouprequest_sms" msgid="605618939583628306">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasının SMS mesajları göndermesine ve gelen mesajları görüntülemesine izin verin"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Depolama"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"cihazınızdaki fotoğraflara, medyaya ve dosyalara erişme"</string>
@@ -282,8 +286,8 @@
     <string name="permgroupdesc_camera" msgid="3250611594678347720">"fotoğraf çekme ve video kaydetme"</string>
     <string name="permgrouprequest_camera" msgid="810824326507258410">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasının resim çekmesine ve video kaydı yapmasına izin verin"</string>
     <string name="permgrouplab_phone" msgid="5229115638567440675">"Telefon"</string>
-    <string name="permgroupdesc_phone" msgid="6234224354060641055">"telefon aramaları yapma ve çağrıları yönetme"</string>
-    <string name="permgrouprequest_phone" msgid="7084161459732093690">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasının telefon etmesine ve gelen çağrıları yönetmesine izin verin"</string>
+    <string name="permgroupdesc_phone" msgid="6234224354060641055">"telefon çağrıları yapma ve yönetme"</string>
+    <string name="permgrouprequest_phone" msgid="7084161459732093690">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasına telefon çağrıları yapma ve yönetme izni verin"</string>
     <string name="permgrouplab_sensors" msgid="416037179223226722">"Vücut Sensörleri"</string>
     <string name="permgroupdesc_sensors" msgid="7147968539346634043">"hayati belirtilerinizle ilgili sensör verilerine erişme"</string>
     <string name="permgrouprequest_sensors" msgid="8631146669524259656">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasının hayati belirtilerinizle ilgili sensör verilerine erişmesine izin verin"</string>
@@ -321,7 +325,7 @@
     <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Uygulamaya, cihazınız tarafından alınan hücre yayını mesajlarını okuma izni verir. Hücre yayını uyarıları bazı yerlerde acil durumlar konusunda sizi uyarmak için gönderilir. Kötü amaçlı uygulamalar acil hücre yayını alındığında cihazınızın performansına ya da çalışmasına engel olabilir."</string>
     <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"abone olunan yayınları okuma"</string>
     <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Uygulamaya, o anda senkronize olan özet akışları ile ilgili bilgi alma izni verir."</string>
-    <string name="permlab_sendSms" msgid="7544599214260982981">"SMS iletileri gönderme ve görüntüleme"</string>
+    <string name="permlab_sendSms" msgid="7544599214260982981">"SMS mesajları gönderme ve görüntüleme"</string>
     <string name="permdesc_sendSms" msgid="7094729298204937667">"Uygulamaya SMS iletisi gönderme izni verir. Bu durum beklenmeyen ödemelere neden olabilir. Kötü amaçlı uygulamalar onayınız olmadan iletiler göndererek sizi zarara uğratabilir."</string>
     <string name="permlab_readSms" msgid="8745086572213270480">"kısa mesajlarımı (SMS veya MMS) oku"</string>
     <string name="permdesc_readSms" product="tablet" msgid="4741697454888074891">"Bu uygulama, tabletinizde kayıtlı tüm SMS mesajlarını (kısa mesajları) okuyabilir."</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tüm ağları görmek için dokunun"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Bağlan"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Tüm Ağlar"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Kablosuz özelliği otomatik olarak açılacak"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Daha önce kaydedilmiş yüksek kaliteli bir ağın yakınında olduğunuzda"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Tekrar açılmasın"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Kablosuz ağda oturum açın"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Ağda oturum açın"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index bfbe902..5344dcf 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"б"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"КБ"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"МБ"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"ГБ"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"ТБ"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"Пб"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Без назви&gt;"</string>
@@ -1160,6 +1164,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Торкніться, щоб побачити всі мережі"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Під’єднатися"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Усі мережі"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi вмикатиметься автоматично"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Коли ви поблизу збереженої мережі високої якості"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не вмикати знову"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Вхід у мережу Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Вхід у мережу"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-ur/required_apps_managed_device.xml b/core/res/res/values-ur/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-ur/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ur/required_apps_managed_profile.xml b/core/res/res/values-ur/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-ur/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ur/required_apps_managed_user.xml b/core/res/res/values-ur/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-ur/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 2771432..4ced9cf 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"بائٹس"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"‏‎&gt;‎بلا عنوان‎&lt;‎"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"تمام نیٹ ورکس دیکھنے کیلئے تھپتھپائيں"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"منسلک کریں"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"تمام نیٹ ورکس"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏Wi‑Fi از خود آن ہو جائے گا"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"جب آپ اعلی معیار کے محفوظ کردہ نیٹ ورک کے قریب ہوں"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"دوبارہ آن نہ کریں"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"‏Wi-Fi نیٹ ورک میں سائن ان کریں"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"نیٹ ورک میں سائن ان کریں"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-uz/required_apps_managed_device.xml b/core/res/res/values-uz/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-uz/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-uz/required_apps_managed_profile.xml b/core/res/res/values-uz/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-uz/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-uz/required_apps_managed_user.xml b/core/res/res/values-uz/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-uz/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index c488473..d7d3941 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Nomsiz&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Barcha tarmoqlarni ko‘rish uchun bosing"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Ulanish"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Barcha tarmoqlar"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi avtomatik ravishda yoqiladi"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Saqlangan tarmoqlar ichidan signali yaxshisi hududida ekaningizda"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Qayta yoqilmasin"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Wi-Fi tarmoqqa kirish"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Tarmoqqa kirish"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-vi/required_apps_managed_device.xml b/core/res/res/values-vi/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-vi/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-vi/required_apps_managed_profile.xml b/core/res/res/values-vi/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-vi/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-vi/required_apps_managed_user.xml b/core/res/res/values-vi/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-vi/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 5f72834..c5a370a 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Không có tiêu đề&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Nhấn để xem tất cả các mạng"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Kết nối"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Tất cả các mạng"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi sẽ tự động bật"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Khi bạn ở gần mạng đã lưu chất lượng cao"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Không bật lại"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Đăng nhập vào mạng Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Đăng nhập vào mạng"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-zh-rCN/required_apps_managed_device.xml b/core/res/res/values-zh-rCN/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-zh-rCN/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-zh-rCN/required_apps_managed_profile.xml b/core/res/res/values-zh-rCN/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-zh-rCN/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-zh-rCN/required_apps_managed_user.xml b/core/res/res/values-zh-rCN/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-zh-rCN/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 028c082..e8ad015 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;未命名&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"点按即可查看所有网络"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"连接"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"所有网络"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WLAN 将自动开启"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"当您位于已保存的高品质网络信号范围内时"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"不要重新开启"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"登录到WLAN网络"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"登录到网络"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-zh-rHK/required_apps_managed_device.xml b/core/res/res/values-zh-rHK/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-zh-rHK/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-zh-rHK/required_apps_managed_profile.xml b/core/res/res/values-zh-rHK/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-zh-rHK/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-zh-rHK/required_apps_managed_user.xml b/core/res/res/values-zh-rHK/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-zh-rHK/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 2da0d4a..bedaef4 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;未命名&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"輕按即可查看所有網絡"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"連線"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"所有網絡"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi 將會自動開啟"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"當您位於已儲存的高品質網絡信號範圍內時"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"不要重新開啟"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"登入 Wi-Fi 網絡"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"登入網絡"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-zh-rTW/required_apps_managed_device.xml b/core/res/res/values-zh-rTW/required_apps_managed_device.xml
deleted file mode 100644
index 9044fcc..0000000
--- a/core/res/res/values-zh-rTW/required_apps_managed_device.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_device">
-    <item msgid="1104492179978792509">"com.android.settings"</item>
-    <item msgid="7004798084799227194">"com.android.contacts"</item>
-    <item msgid="5782220690863647256">"com.android.dialer"</item>
-    <item msgid="5746338511138092673">"com.android.stk"</item>
-    <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
-    <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
-    <item msgid="3892021562839042708">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-zh-rTW/required_apps_managed_profile.xml b/core/res/res/values-zh-rTW/required_apps_managed_profile.xml
deleted file mode 100644
index 4296b0d..0000000
--- a/core/res/res/values-zh-rTW/required_apps_managed_profile.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_profile">
-    <item msgid="1457364287544474838">"com.android.contacts"</item>
-    <item msgid="4633145750237794002">"com.android.settings"</item>
-    <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
-    <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
-    <item msgid="6106837921940099371">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-zh-rTW/required_apps_managed_user.xml b/core/res/res/values-zh-rTW/required_apps_managed_user.xml
deleted file mode 100644
index 1a7ade9..0000000
--- a/core/res/res/values-zh-rTW/required_apps_managed_user.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="required_apps_managed_user">
-    <item msgid="4823915868435007499">"com.android.settings"</item>
-    <item msgid="2250259015310893915">"com.android.contacts"</item>
-    <item msgid="7166574999426592423">"com.android.dialer"</item>
-    <item msgid="7306937186458176744">"com.android.stk"</item>
-    <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
-    <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
-    <item msgid="8640522622655589373">"com.android.documentsui"</item>
-  </string-array>
-</resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 4f2492f..38109f8 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"位元組"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"KB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;未命名&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"輕觸即可查看所有網路"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"連線"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"所有網路"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi 將自動開啟"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"當你位於已儲存的高品質網路範圍內時"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"不要重新開啟"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"登入 Wi-Fi 網路"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"登入網路"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 2977a85..57b9fc3 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -21,6 +21,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="byteShort" msgid="8340973892742019101">"B"</string>
+    <string name="kilobyteShort" msgid="7542884022844556968">"kB"</string>
+    <string name="megabyteShort" msgid="6355851576770428922">"MB"</string>
+    <string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
+    <string name="terabyteShort" msgid="231613018159186962">"TB"</string>
     <string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
     <string name="fileSizeSuffix" msgid="8897567456150907538">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
     <string name="untitled" msgid="4638956954852782576">"&lt;Akunasihloko&gt;"</string>
@@ -1116,6 +1120,9 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Thepha ukuze ubone onke amanethiwekhi"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Xhuma"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Onke amanethiwekhi"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"I-Wi-Fi izovuleka ngokuzenzakalela"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Uma useduze kwenethiwekhi yekhwalithi ephezulu elondoloziwe"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ungaphindi uvule"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Ngena ngemvume kunethiwekhi ye-Wi-Fi"</string>
     <string name="network_available_sign_in" msgid="1848877297365446605">"Ngena ngemvume kunethiwekhi"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 64febf1..ee7c795 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4470,6 +4470,10 @@
         <attr name="shadowRadius" format="float" />
         <!-- Elegant text height, especially for less compacted complex script text. -->
         <attr name="elegantTextHeight" format="boolean" />
+        <!-- Whether to respect the ascent and descent of the fallback fonts that are used in
+        displaying the text. When true, fallback fonts that end up getting used can increase
+        the ascent and descent of the lines that they are used on. -->
+        <attr name="fallbackLineSpacing" format="boolean"/>
         <!-- Text letter-spacing. -->
         <attr name="letterSpacing" format="float" />
         <!-- Font feature settings. -->
@@ -4803,6 +4807,10 @@
         <attr name="textAllCaps" />
         <!-- Elegant text height, especially for less compacted complex script text. -->
         <attr name="elegantTextHeight" />
+        <!-- Whether to respect the ascent and descent of the fallback fonts that are used in
+        displaying the text. When true, fallback fonts that end up getting used can increase
+        the ascent and descent of the lines that they are used on. -->
+        <attr name="fallbackLineSpacing" format="boolean"/>
         <!-- Text letter-spacing. -->
         <attr name="letterSpacing" />
         <!-- Font feature settings. -->
@@ -8699,8 +8707,11 @@
          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. -->
+        <!-- The index of the font in the ttc (TrueType Collection) font file. If the font file
+         referenced is not in the ttc format, this attribute needs not be specified.
+         {@see android.graphics.Typeface#Builder.setTtcIndex(int)}.
+         The default value is 0. More details about the TrueType Collection font format can be found
+         here: https://en.wikipedia.org/wiki/TrueType#TrueType_Collection. -->
         <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
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index e2498df7..7048511 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -31,10 +31,8 @@
     <color name="tertiary_device_default_settings">@color/tertiary_material_settings</color>
     <color name="quaternary_device_default_settings">@color/quaternary_material_settings</color>
 
-    <color name="accent_device_default_700">@color/accent_material_700</color>
     <color name="accent_device_default_light">@color/accent_material_light</color>
     <color name="accent_device_default_dark">@color/accent_material_dark</color>
-    <color name="accent_device_default_50">@color/accent_material_50</color>
 
     <color name="background_device_default_dark">@color/background_material_dark</color>
     <color name="background_device_default_light">@color/background_material_light</color>
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index 8c37d4b..0413100 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -39,10 +39,8 @@
     <color name="tertiary_material_settings">@color/material_blue_grey_700</color>
     <color name="quaternary_material_settings">@color/material_blue_grey_200</color>
 
-    <color name="accent_material_700">@color/material_deep_teal_700</color>
     <color name="accent_material_light">@color/material_deep_teal_500</color>
     <color name="accent_material_dark">@color/material_deep_teal_200</color>
-    <color name="accent_material_50">@color/material_deep_teal_50</color>
 
     <color name="button_material_dark">#ff5a595b</color>
     <color name="button_material_light">#ffd6d7d7</color>
@@ -95,12 +93,10 @@
     <color name="material_grey_100">#fff5f5f5</color>
     <color name="material_grey_50">#fffafafa</color>
 
-    <color name="material_deep_teal_50">#ffe0f2f1</color>
     <color name="material_deep_teal_100">#ffb2dfdb</color>
     <color name="material_deep_teal_200">#ff80cbc4</color>
     <color name="material_deep_teal_300">#ff4db6ac</color>
     <color name="material_deep_teal_500">#ff009688</color>
-    <color name="material_deep_teal_700">#ff00796b</color>
 
     <color name="material_blue_grey_200">#ffb0bec5</color>
     <color name="material_blue_grey_700">#ff455a64</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dc791cf..d2685cf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1302,8 +1302,8 @@
 
          If this is defined then:
             - config_autoBrightnessLcdBacklightValues should not be defined
-            - config_screenBrightnessMinimumNits must be defined
-            - config_screenBrightnessMaximumNits must be defined
+            - config_screenBrightnessNits must be defined
+            - config_screenBrightnessBacklight must be defined
 
          This array should have size one greater than the size of the config_autoBrightnessLevels
          array. The brightness values must be non-negative and non-decreasing. This must be
@@ -1347,28 +1347,23 @@
         <item>200</item>
     </integer-array>
 
-    <!-- The minimum brightness of the display in nits. On OLED displays this should be measured
-         with an all white image while the display is fully on and the backlight is set to
-         config_screenBrightnessSettingMinimum or config_screenBrightnessSettingDark, whichever
-         is darker.
+    <!-- An array describing the screen's backlight values corresponding to the brightness
+         values in the config_screenBrightnessNits array.
 
-         If this and config_screenBrightnessMinimumNits are set, then the display's brightness
-         range is assumed to be linear between
-         (config_screenBrightnessSettingMinimum, config_screenBrightnessMinimumNits) and
-         (config_screenBrightnessSettingMaximum, config_screenBrightnessMaximumNits). -->
-    <item name="config_screenBrightnessMinimumNits" format="float" type="dimen">-1.0</item>
+         This array should be equal in size to config_screenBrightnessBacklight. -->
+    <integer-array name="config_screenBrightnessBacklight">
+    </integer-array>
 
-    <!-- The maximum brightness of the display in nits. On OLED displays this should be measured
-         with an all white image while the display is fully on and the "backlight" is set to
-         config_screenBrightnessSettingMaximum. Note that this value should *not* reflect the
-         maximum brightness value for any high brightness modes but only the maximum brightness
-         value obtainable in a sustainable manner.
+    <!-- An array of floats describing the screen brightness in nits corresponding to the backlight
+         values in the config_screenBrightnessBacklight array.  On OLED displays these  values
+         should be measured with an all white image while the display is in the fully on state.
+         Note that this value should *not* reflect the maximum brightness value for any high
+         brightness modes but only the maximum brightness value obtainable in a sustainable manner.
 
-         If this and config_screenBrightnessMinimumNits are set to something non-negative, then the
-         display's brightness range is assumed to be linear between
-         (config_screenBrightnessSettingMinimum, config_screenBrightnessMaximumNits) and
-         (config_screenBrightnessSettingMaximum, config_screenBrightnessMaximumNits). -->
-    <item name="config_screenBrightnessMaximumNits" format="float" type="dimen">-1.0</item>
+         This array should be equal in size to config_screenBrightnessBacklight -->
+    <array name="config_screenBrightnessNits">
+    </array>
+
 
     <!-- Array of ambient lux threshold values. This is used for determining hysteresis constraint
          values by calculating the index to use for lookup and then setting the constraint value
@@ -2789,7 +2784,7 @@
     <bool name="config_eap_sim_based_auth_supported">true</bool>
 
     <!-- How long history of previous vibrations should be kept for the dumpsys. -->
-    <integer name="config_previousVibrationsDumpLimit">20</integer>
+    <integer name="config_previousVibrationsDumpLimit">50</integer>
 
     <!-- The default vibration strength, must be between 1 and 255 inclusive. -->
     <integer name="config_defaultVibrationAmplitude">255</integer>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index a659b37..6c1661c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -631,4 +631,8 @@
 
     <!-- Default dialog corner radius -->
     <dimen name="dialog_corner_radius">2dp</dimen>
+
+    <!-- Size of thumbnail used in the cross profile apps animation -->
+    <dimen name="cross_profile_apps_thumbnail_size">72dp</dimen>
+
 </resources>
diff --git a/core/res/res/values/disallowed_apps_managed_device.xml b/core/res/res/values/disallowed_apps_managed_device.xml
index 8940b15..3fb9ba4 100644
--- a/core/res/res/values/disallowed_apps_managed_device.xml
+++ b/core/res/res/values/disallowed_apps_managed_device.xml
@@ -18,6 +18,6 @@
 -->
 <resources>
     <!-- A list of apps to be removed from the managed device. -->
-    <string-array name="disallowed_apps_managed_device">
+    <string-array translatable="false" name="disallowed_apps_managed_device">
     </string-array>
 </resources>
diff --git a/core/res/res/values/disallowed_apps_managed_profile.xml b/core/res/res/values/disallowed_apps_managed_profile.xml
index e3a513f..c3ea8ec 100644
--- a/core/res/res/values/disallowed_apps_managed_profile.xml
+++ b/core/res/res/values/disallowed_apps_managed_profile.xml
@@ -18,6 +18,6 @@
 -->
 <resources>
     <!-- A list of apps to be removed from the managed profile. -->
-    <string-array name="disallowed_apps_managed_profile">
+    <string-array translatable="false" name="disallowed_apps_managed_profile">
     </string-array>
 </resources>
diff --git a/core/res/res/values/disallowed_apps_managed_user.xml b/core/res/res/values/disallowed_apps_managed_user.xml
index b7b645d..e5b29afe 100644
--- a/core/res/res/values/disallowed_apps_managed_user.xml
+++ b/core/res/res/values/disallowed_apps_managed_user.xml
@@ -18,6 +18,6 @@
 -->
 <resources>
     <!-- A list of apps to be removed from the managed user. -->
-    <string-array name="disallowed_apps_managed_user">
+    <string-array translatable="false" name="disallowed_apps_managed_user">
     </string-array>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 8540ff1..6ec88dc 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2860,6 +2860,7 @@
       <public name="isVrOnly"/>
       <public name="widgetFeatures" />
       <public name="appComponentFactory" />
+      <public name="fallbackLineSpacing" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/required_apps_managed_device.xml b/core/res/res/values/required_apps_managed_device.xml
index 0ac706f..40db9df 100644
--- a/core/res/res/values/required_apps_managed_device.xml
+++ b/core/res/res/values/required_apps_managed_device.xml
@@ -19,7 +19,7 @@
 <resources>
     <!-- A list of apps to be retained on the managed device.
             Takes precedence over the disallowed apps lists. -->
-    <string-array name="required_apps_managed_device">
+    <string-array translatable="false" name="required_apps_managed_device">
         <item>com.android.settings</item>
         <item>com.android.contacts</item>
         <item>com.android.dialer</item>
diff --git a/core/res/res/values/required_apps_managed_profile.xml b/core/res/res/values/required_apps_managed_profile.xml
index a0b8492..c6b37e8 100644
--- a/core/res/res/values/required_apps_managed_profile.xml
+++ b/core/res/res/values/required_apps_managed_profile.xml
@@ -19,7 +19,7 @@
 <resources>
     <!-- A list of apps to be retained in the managed profile.
             Takes precedence over the disallowed apps lists. -->
-    <string-array name="required_apps_managed_profile">
+    <string-array translatable="false" name="required_apps_managed_profile">
         <item>com.android.contacts</item>
         <item>com.android.settings</item>
         <item>com.android.providers.downloads</item>
diff --git a/core/res/res/values/required_apps_managed_user.xml b/core/res/res/values/required_apps_managed_user.xml
index e8fdb21..8800e535 100644
--- a/core/res/res/values/required_apps_managed_user.xml
+++ b/core/res/res/values/required_apps_managed_user.xml
@@ -19,7 +19,7 @@
 <resources>
     <!-- A list of apps to be retained on the managed user.
             Takes precedence over the disallowed apps lists. -->
-    <string-array name="required_apps_managed_user">
+    <string-array translatable="false" name="required_apps_managed_user">
         <item>com.android.settings</item>
         <item>com.android.contacts</item>
         <item>com.android.dialer</item>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5783435..0618a82 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -20,13 +20,23 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Suffix added to a number to signify size in bytes. -->
     <string name="byteShort">B</string>
+    <!-- Suffix added to a number to signify size in kilobytes (1000 bytes).
+        If you retain the Latin script for the localization, please use the lowercase
+        'k', as it signifies 1000 bytes as opposed to 1024 bytes. -->
+    <string name="kilobyteShort">kB</string>
+    <!-- Suffix added to a number to signify size in megabytes. -->
+    <string name="megabyteShort">MB</string>
+    <!-- Suffix added to a number to signify size in gigabytes. -->
+    <string name="gigabyteShort">GB</string>
+    <!-- Suffix added to a number to signify size in terabytes. -->
+    <string name="terabyteShort">TB</string>
     <!-- Suffix added to a number to signify size in petabytes. -->
     <string name="petabyteShort">PB</string>
-    <!-- Format string used to add a suffix like "B" or "PB" to a number
-         to display a size in bytes or petabytes.
-         Some languages may want to remove the space between the placeholders
-         or replace it with a non-breaking space. -->
-    <string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g> <xliff:g id="unit" example="B">%2$s</xliff:g></string>
+    <!-- Format string used to add a suffix like "kB" or "MB" to a number
+         to display a size in kilobytes, megabytes, or other size units.
+         Some languages (like French) will want to add a space between
+         the placeholders. -->
+    <string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g> <xliff:g id="unit" example="MB">%2$s</xliff:g></string>
 
     <!-- Used in Contacts for a field that has no label and in Note Pad
          for a note with no name. -->
@@ -4775,6 +4785,11 @@
     -->
     <string name="shortcut_restore_unknown_issue">Couldn\u2019t restore shortcut</string>
 
+    <!--
+    A toast message shown when an app shortcut is disabled for an unknown rason.
+    -->
+    <string name="shortcut_disabled_reason_unknown">Shortcut is disabled</string>
+
     <!--Battery saver warning. STOPSHIP: Remove it eventually. -->
     <string name="battery_saver_warning" translatable="false">\"Extreme\" battery saver activated.\n\nSee the details at: go/extreme-battery-saver\n\nEBS aggressively throttles background apps and changes screen-off behavior.\n</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7f71446..22a4251 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -705,6 +705,7 @@
   <java-symbol type="string" name="fileSizeSuffix" />
   <java-symbol type="string" name="force_close" />
   <java-symbol type="string" name="gadget_host_error_inflating" />
+  <java-symbol type="string" name="gigabyteShort" />
   <java-symbol type="string" name="gpsNotifMessage" />
   <java-symbol type="string" name="gpsNotifTicker" />
   <java-symbol type="string" name="gpsNotifTitle" />
@@ -760,6 +761,7 @@
   <java-symbol type="string" name="keyboardview_keycode_enter" />
   <java-symbol type="string" name="keyboardview_keycode_mode_change" />
   <java-symbol type="string" name="keyboardview_keycode_shift" />
+  <java-symbol type="string" name="kilobyteShort" />
   <java-symbol type="string" name="last_month" />
   <java-symbol type="string" name="launchBrowserDefault" />
   <java-symbol type="string" name="lock_to_app_toast" />
@@ -779,6 +781,7 @@
   <java-symbol type="string" name="lockscreen_emergency_call" />
   <java-symbol type="string" name="lockscreen_return_to_call" />
   <java-symbol type="string" name="low_memory" />
+  <java-symbol type="string" name="megabyteShort" />
   <java-symbol type="string" name="midnight" />
   <java-symbol type="string" name="mismatchPin" />
   <java-symbol type="string" name="mmiComplete" />
@@ -981,6 +984,7 @@
   <java-symbol type="string" name="sync_really_delete" />
   <java-symbol type="string" name="sync_too_many_deletes_desc" />
   <java-symbol type="string" name="sync_undo_deletes" />
+  <java-symbol type="string" name="terabyteShort" />
   <java-symbol type="string" name="text_copied" />
   <java-symbol type="string" name="time_of_day" />
   <java-symbol type="string" name="time_picker_decrement_hour_button" />
@@ -1574,6 +1578,10 @@
   <java-symbol type="anim" name="voice_activity_close_enter" />
   <java-symbol type="anim" name="voice_activity_open_exit" />
   <java-symbol type="anim" name="voice_activity_open_enter" />
+  <java-symbol type="anim" name="task_open_exit" />
+  <java-symbol type="anim" name="task_open_enter" />
+  <java-symbol type="anim" name="cross_profile_apps_thumbnail_enter" />
+  <java-symbol type="anim" name="task_open_enter_cross_profile_apps" />
 
   <java-symbol type="array" name="config_autoRotationTiltTolerance" />
   <java-symbol type="array" name="config_keyboardTapVibePattern" />
@@ -1720,6 +1728,7 @@
   <java-symbol type="style" name="Theme.ExpandedMenu" />
   <java-symbol type="string" name="forward_intent_to_owner" />
   <java-symbol type="string" name="forward_intent_to_work" />
+  <java-symbol type="dimen" name="cross_profile_apps_thumbnail_size" />
 
   <!-- From services -->
   <java-symbol type="anim" name="screen_rotate_0_enter" />
@@ -3191,7 +3200,9 @@
   <java-symbol type="string" name="global_action_logout" />
   <java-symbol type="drawable" name="ic_logout" />
 
-  <java-symbol type="dimen" name="config_screenBrightnessMinimumNits" />
-  <java-symbol type="dimen" name="config_screenBrightnessMaximumNits" />
   <java-symbol type="array" name="config_autoBrightnessDisplayValuesNits" />
+  <java-symbol type="array" name="config_screenBrightnessBacklight" />
+  <java-symbol type="array" name="config_screenBrightnessNits" />
+
+  <java-symbol type="string" name="shortcut_disabled_reason_unknown" />
 </resources>
diff --git a/core/res/res/values/vendor_disallowed_apps_managed_device.xml b/core/res/res/values/vendor_disallowed_apps_managed_device.xml
index c826d27..493cd60 100644
--- a/core/res/res/values/vendor_disallowed_apps_managed_device.xml
+++ b/core/res/res/values/vendor_disallowed_apps_managed_device.xml
@@ -18,6 +18,6 @@
 -->
 <resources>
     <!-- A list of apps to be removed from the managed device by a particular vendor. -->
-    <string-array name="vendor_disallowed_apps_managed_device">
+    <string-array translatable="false" name="vendor_disallowed_apps_managed_device">
     </string-array>
 </resources>
diff --git a/core/res/res/values/vendor_disallowed_apps_managed_profile.xml b/core/res/res/values/vendor_disallowed_apps_managed_profile.xml
index 5fcb2778..84cab5f 100644
--- a/core/res/res/values/vendor_disallowed_apps_managed_profile.xml
+++ b/core/res/res/values/vendor_disallowed_apps_managed_profile.xml
@@ -18,6 +18,6 @@
 -->
 <resources>
     <!-- A list of apps to be removed from the managed profile by a particular vendor. -->
-    <string-array name="vendor_disallowed_apps_managed_profile">
+    <string-array translatable="false" name="vendor_disallowed_apps_managed_profile">
     </string-array>
 </resources>
diff --git a/core/res/res/values/vendor_disallowed_apps_managed_user.xml b/core/res/res/values/vendor_disallowed_apps_managed_user.xml
index 3355d77..975bb5d 100644
--- a/core/res/res/values/vendor_disallowed_apps_managed_user.xml
+++ b/core/res/res/values/vendor_disallowed_apps_managed_user.xml
@@ -18,6 +18,6 @@
 -->
 <resources>
     <!-- A list of apps to be removed from the managed user by a particular vendor. -->
-    <string-array name="vendor_disallowed_apps_managed_user">
+    <string-array translatable="false" name="vendor_disallowed_apps_managed_user">
     </string-array>
 </resources>
diff --git a/core/res/res/values/vendor_required_apps_managed_device.xml b/core/res/res/values/vendor_required_apps_managed_device.xml
index e684e22..1ef48db 100644
--- a/core/res/res/values/vendor_required_apps_managed_device.xml
+++ b/core/res/res/values/vendor_required_apps_managed_device.xml
@@ -19,6 +19,6 @@
 <resources>
     <!-- A list of apps to be retained on the managed device by a particular vendor.
             Takes precedence over the disallowed apps lists. -->
-    <string-array name="vendor_required_apps_managed_device">
+    <string-array translatable="false" name="vendor_required_apps_managed_device">
     </string-array>
 </resources>
diff --git a/core/res/res/values/vendor_required_apps_managed_profile.xml b/core/res/res/values/vendor_required_apps_managed_profile.xml
index 4a3edf8..49cfa8b 100644
--- a/core/res/res/values/vendor_required_apps_managed_profile.xml
+++ b/core/res/res/values/vendor_required_apps_managed_profile.xml
@@ -19,6 +19,6 @@
 <resources>
     <!-- A list of apps to be retained in the managed profile by a particular vendor.
             Takes precedence over the disallowed apps lists. -->
-    <string-array name="vendor_required_apps_managed_profile">
+    <string-array translatable="false" name="vendor_required_apps_managed_profile">
     </string-array>
 </resources>
diff --git a/core/res/res/values/vendor_required_apps_managed_user.xml b/core/res/res/values/vendor_required_apps_managed_user.xml
index 71dbd62..bad7803 100644
--- a/core/res/res/values/vendor_required_apps_managed_user.xml
+++ b/core/res/res/values/vendor_required_apps_managed_user.xml
@@ -19,6 +19,6 @@
 <resources>
     <!-- A list of apps to be retained on the managed user by a particular vendor.
             Takes precedence over the disallowed apps lists. -->
-    <string-array name="vendor_required_apps_managed_user">
+    <string-array translatable="false" name="vendor_required_apps_managed_user">
     </string-array>
 </resources>
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
index a8d5164..896fd15 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
@@ -149,6 +149,10 @@
 
         mRadioTuner = mRadioManager.openTuner(mModule.getId(),
                 mFmBandConfig, withAudio, mCallback, null);
+        if (!withAudio) {
+            // non-audio sessions might not be supported - if so, then skip the test
+            assumeNotNull(mRadioTuner);
+        }
         assertNotNull(mRadioTuner);
         verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
         resetCallback();
@@ -207,20 +211,8 @@
     public void testSetBadConfiguration() throws Throwable {
         openTuner();
 
-        // set bad config
-        Constructor<RadioManager.AmBandConfig> configConstr =
-                RadioManager.AmBandConfig.class.getDeclaredConstructor(
-                        int.class, int.class, int.class, int.class, int.class, boolean.class);
-        configConstr.setAccessible(true);
-        RadioManager.AmBandConfig badConfig = configConstr.newInstance(
-                0 /*region*/, RadioManager.BAND_AM /*type*/,
-                10000 /*lowerLimit*/, 1 /*upperLimit*/, 100 /*spacing*/, false /*stereo*/);
-        int ret = mRadioTuner.setConfiguration(badConfig);
-        assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
-        verify(mCallback, never()).onConfigurationChanged(any());
-
         // set null config
-        ret = mRadioTuner.setConfiguration(null);
+        int ret = mRadioTuner.setConfiguration(null);
         assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
         verify(mCallback, never()).onConfigurationChanged(any());
 
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 7183934..b51c677 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -28,6 +28,7 @@
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Icon;
 import android.media.session.MediaSession;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.test.InstrumentationRegistry;
@@ -215,9 +216,11 @@
     }
 
     @Test
-    public void testMessagingStyle_isGroupConversation() {
+    public void messagingStyle_isGroupConversation() {
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
-                .setGroupConversation(true);
+                .setGroupConversation(true)
+                .setConversationTitle("test conversation title");
         Notification notification = new Notification.Builder(mContext, "test id")
                 .setSmallIcon(1)
                 .setContentTitle("test title")
@@ -228,6 +231,56 @@
         assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
     }
 
+    @Test
+    public void messagingStyle_isGroupConversation_noConversationTitle() {
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+                .setGroupConversation(true)
+                .setConversationTitle(null);
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertTrue(messagingStyle.isGroupConversation());
+        assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+    }
+
+    @Test
+    public void messagingStyle_isGroupConversation_withConversationTitle_legacy() {
+        // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+                .setGroupConversation(false)
+                .setConversationTitle("test conversation title");
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertTrue(messagingStyle.isGroupConversation());
+        assertFalse(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+    }
+
+    @Test
+    public void messagingStyle_isGroupConversation_withoutConversationTitle_legacy() {
+        // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+                .setGroupConversation(true)
+                .setConversationTitle(null);
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertFalse(messagingStyle.isGroupConversation());
+        assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+    }
+
     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/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
index 1a54bd6..63a5e4c 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
@@ -41,6 +41,8 @@
 
     private static final String ANDROID_TEST_MOCK = "android.test.mock";
 
+    private static final String OTHER_LIBRARY = "other.library";
+
     private Package mPackage;
 
     private static ArrayList<String> arrayList(String... strings) {
@@ -78,6 +80,18 @@
     }
 
     @Test
+    public void targeted_at_O_not_empty_usesLibraries() {
+        mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+        mPackage.usesLibraries = arrayList(OTHER_LIBRARY);
+        PackageBackwardCompatibility.modifySharedLibraries(mPackage);
+        // The org.apache.http.legacy jar should be added at the start of the list.
+        assertEquals("usesLibraries not updated correctly",
+                arrayList(ORG_APACHE_HTTP_LEGACY, OTHER_LIBRARY),
+                mPackage.usesLibraries);
+        assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries);
+    }
+
+    @Test
     public void targeted_at_O_org_apache_http_legacy_in_usesLibraries() {
         mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
         mPackage.usesLibraries = arrayList(ORG_APACHE_HTTP_LEGACY);
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index b7a48c7..64fadc0 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -16,6 +16,8 @@
 
 package android.graphics.drawable;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Region;
@@ -108,6 +110,19 @@
     }
 
     @SmallTest
+    public void testScaleDownIfNecessary() throws Exception {
+        final Bitmap bm = Bitmap.createBitmap(4321, 78, Bitmap.Config.ARGB_8888);
+        final Icon ic = Icon.createWithBitmap(bm);
+        ic.scaleDownIfNecessary(40, 20);
+
+        assertThat(bm.getWidth()).isEqualTo(4321);
+        assertThat(bm.getHeight()).isEqualTo(78);
+
+        assertThat(ic.getBitmap().getWidth()).isLessThan(41);
+        assertThat(ic.getBitmap().getHeight()).isLessThan(21);
+    }
+
+    @SmallTest
     public void testWithAdaptiveBitmap() throws Exception {
         final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
 
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 704b780..a427a2f 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -20,6 +20,7 @@
 
 import junit.framework.TestCase;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -127,6 +128,23 @@
         assertFalse(ws3.equals(ws1));
     }
 
+    public void testEquals_workChains_nullEmptyAreEquivalent() {
+        // Construct a WorkSource that has no WorkChains, but whose workChains list
+        // is non-null.
+        WorkSource ws1 = new WorkSource();
+        ws1.add(100);
+        ws1.createWorkChain().addNode(100, null);
+        ws1.getWorkChains().clear();
+
+        WorkSource ws2 = new WorkSource();
+        ws2.add(100);
+
+        assertEquals(ws1, ws2);
+
+        ws2.createWorkChain().addNode(100, null);
+        assertFalse(ws1.equals(ws2));
+    }
+
     public void testWorkSourceParcelling() {
         WorkSource ws = new WorkSource();
 
@@ -218,4 +236,136 @@
         assertEquals(20, ws2.get(0));
         assertEquals("foo", ws2.getName(0));
     }
+
+    public void testDiffChains_noChanges() {
+        // WorkSources with no chains.
+        assertEquals(null, WorkSource.diffChains(new WorkSource(), new WorkSource()));
+
+        // WorkSources with the same chains.
+        WorkSource ws1 = new WorkSource();
+        ws1.createWorkChain().addNode(50, "tag");
+        ws1.createWorkChain().addNode(60, "tag2");
+
+        WorkSource ws2 = new WorkSource();
+        ws2.createWorkChain().addNode(50, "tag");
+        ws2.createWorkChain().addNode(60, "tag2");
+
+        assertEquals(null, WorkSource.diffChains(ws1, ws1));
+        assertEquals(null, WorkSource.diffChains(ws2, ws1));
+    }
+
+    public void testDiffChains_noChains() {
+        // Diffs against a worksource with no chains.
+        WorkSource ws1 = new WorkSource();
+        WorkSource ws2 = new WorkSource();
+        ws2.createWorkChain().addNode(70, "tag");
+        ws2.createWorkChain().addNode(60, "tag2");
+
+        // The "old" work source has no chains, so "newChains" should be non-null.
+        ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2);
+        assertNotNull(diffs[0]);
+        assertNull(diffs[1]);
+        assertEquals(2, diffs[0].size());
+        assertEquals(ws2.getWorkChains(), diffs[0]);
+
+        // The "new" work source has no chains, so "oldChains" should be non-null.
+        diffs = WorkSource.diffChains(ws2, ws1);
+        assertNull(diffs[0]);
+        assertNotNull(diffs[1]);
+        assertEquals(2, diffs[1].size());
+        assertEquals(ws2.getWorkChains(), diffs[1]);
+    }
+
+    public void testDiffChains_onlyAdditionsOrRemovals() {
+        WorkSource ws1 = new WorkSource();
+        WorkSource ws2 = new WorkSource();
+        ws2.createWorkChain().addNode(70, "tag");
+        ws2.createWorkChain().addNode(60, "tag2");
+
+        // Both work sources have WorkChains : test the case where changes were only added
+        // or were only removed.
+        ws1.createWorkChain().addNode(70, "tag");
+
+        // The "new" work source only contains additions (60, "tag2") in this case.
+        ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2);
+        assertNotNull(diffs[0]);
+        assertNull(diffs[1]);
+        assertEquals(1, diffs[0].size());
+        assertEquals(new WorkChain().addNode(60, "tag2"), diffs[0].get(0));
+
+        // The "new" work source only contains removals (60, "tag2") in this case.
+        diffs = WorkSource.diffChains(ws2, ws1);
+        assertNull(diffs[0]);
+        assertNotNull(diffs[1]);
+        assertEquals(1, diffs[1].size());
+        assertEquals(new WorkChain().addNode(60, "tag2"), diffs[1].get(0));
+    }
+
+
+    public void testDiffChains_generalCase() {
+        WorkSource ws1 = new WorkSource();
+        WorkSource ws2 = new WorkSource();
+
+        // Both work sources have WorkChains, test the case where chains were added AND removed.
+        ws1.createWorkChain().addNode(0, "tag0");
+        ws2.createWorkChain().addNode(0, "tag0_changed");
+        ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2);
+        assertNotNull(diffs[0]);
+        assertNotNull(diffs[1]);
+        assertEquals(ws2.getWorkChains(), diffs[0]);
+        assertEquals(ws1.getWorkChains(), diffs[1]);
+
+        // Give both WorkSources a chain in common; it should not be a part of any diffs.
+        ws1.createWorkChain().addNode(1, "tag1");
+        ws2.createWorkChain().addNode(1, "tag1");
+        diffs = WorkSource.diffChains(ws1, ws2);
+        assertNotNull(diffs[0]);
+        assertNotNull(diffs[1]);
+        assertEquals(1, diffs[0].size());
+        assertEquals(1, diffs[1].size());
+        assertEquals(new WorkChain().addNode(0, "tag0_changed"), diffs[0].get(0));
+        assertEquals(new WorkChain().addNode(0, "tag0"), diffs[1].get(0));
+
+        // Finally, test the case where more than one chain was added / removed.
+        ws1.createWorkChain().addNode(2, "tag2");
+        ws2.createWorkChain().addNode(2, "tag2_changed");
+        diffs = WorkSource.diffChains(ws1, ws2);
+        assertNotNull(diffs[0]);
+        assertNotNull(diffs[1]);
+        assertEquals(2, diffs[0].size());
+        assertEquals(2, diffs[1].size());
+        assertEquals(new WorkChain().addNode(0, "tag0_changed"), diffs[0].get(0));
+        assertEquals(new WorkChain().addNode(2, "tag2_changed"), diffs[0].get(1));
+        assertEquals(new WorkChain().addNode(0, "tag0"), diffs[1].get(0));
+        assertEquals(new WorkChain().addNode(2, "tag2"), diffs[1].get(1));
+    }
+
+    public void testGetAttributionId() {
+        WorkSource ws1 = new WorkSource();
+        WorkChain wc = ws1.createWorkChain();
+        wc.addNode(100, "tag");
+        assertEquals(100, wc.getAttributionUid());
+        wc.addNode(200, "tag2");
+        assertEquals(100, wc.getAttributionUid());
+    }
+
+    public void testRemove_fromChainedWorkSource() {
+        WorkSource ws1 = new WorkSource();
+        ws1.createWorkChain().addNode(50, "foo");
+        ws1.createWorkChain().addNode(75, "bar");
+        ws1.add(100);
+
+        WorkSource ws2 = new WorkSource();
+        ws2.add(100);
+
+        assertTrue(ws1.remove(ws2));
+        assertEquals(2, ws1.getWorkChains().size());
+        assertEquals(50, ws1.getWorkChains().get(0).getAttributionUid());
+        assertEquals(75, ws1.getWorkChains().get(1).getAttributionUid());
+
+        ws2.createWorkChain().addNode(50, "foo");
+        assertTrue(ws1.remove(ws2));
+        assertEquals(1, ws1.getWorkChains().size());
+        assertEquals(75, ws1.getWorkChains().get(0).getAttributionUid());
+    }
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 78d7785..7cfedc8 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -112,6 +112,7 @@
                     Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
                     Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
                     Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
+                    Settings.Global.BATTERY_STATS_CONSTANTS,
                     Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
                     Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
                     Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
@@ -173,6 +174,7 @@
                     Settings.Global.DEVICE_DEMO_MODE,
                     Settings.Global.DEVICE_IDLE_CONSTANTS,
                     Settings.Global.BATTERY_SAVER_CONSTANTS,
+                    Settings.Global.BATTERY_TIP_CONSTANTS,
                     Settings.Global.DEFAULT_SM_DP_PLUS,
                     Settings.Global.DEVICE_NAME,
                     Settings.Global.DEVICE_POLICY_CONSTANTS,
@@ -280,6 +282,7 @@
                     Settings.Global.NETWORK_SCORING_UI_ENABLED,
                     Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
                     Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
+                    Settings.Global.NETWORK_WATCHLIST_ENABLED,
                     Settings.Global.NEW_CONTACT_AGGREGATOR,
                     Settings.Global.NITZ_UPDATE_DIFF,
                     Settings.Global.NITZ_UPDATE_SPACING,
@@ -451,7 +454,6 @@
                  Settings.Secure.COMPLETED_CATEGORY_PREFIX,
                  Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                  Settings.Secure.DEFAULT_INPUT_METHOD,
-                 Settings.Secure.DEMO_USER_SETUP_COMPLETE,
                  Settings.Secure.DEVICE_PAIRED,
                  Settings.Secure.DIALER_DEFAULT_APPLICATION,
                  Settings.Secure.DISABLED_PRINT_SERVICES,
@@ -524,7 +526,8 @@
                  Settings.Secure.VOICE_INTERACTION_SERVICE,
                  Settings.Secure.VOICE_RECOGNITION_SERVICE,
                  Settings.Secure.INSTANT_APPS_ENABLED,
-                 Settings.Secure.BACKUP_MANAGER_CONSTANTS);
+                 Settings.Secure.BACKUP_MANAGER_CONSTANTS,
+                 Settings.Secure.KEYGUARD_SLICE_URI);
 
     @Test
     public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java b/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java
new file mode 100644
index 0000000..1e3ddf3
--- /dev/null
+++ b/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java
@@ -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.
+ */
+package android.service.euicc;
+
+import static org.junit.Assert.assertArrayEquals;
+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 android.os.Parcel;
+import android.service.carrier.CarrierIdentifier;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.UiccAccessRule;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class EuiccProfileInfoTest {
+    @Test
+    public void testWriteToParcel() {
+        EuiccProfileInfo p =
+                new EuiccProfileInfo.Builder()
+                        .setIccid("21430000000000006587")
+                        .setNickname("profile nickname")
+                        .setServiceProviderName("service provider")
+                        .setProfileName("profile name")
+                        .setProfileClass(EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL)
+                        .setState(EuiccProfileInfo.PROFILE_STATE_ENABLED)
+                        .setCarrierIdentifier(
+                                new CarrierIdentifier(
+                                        new byte[] {0x23, 0x45, 0x67},
+                                        "123",
+                                        "45"))
+                        .setPolicyRules(EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE)
+                        .setUiccAccessRule(
+                                new UiccAccessRule[] {
+                                        new UiccAccessRule(new byte[] {}, "package", 12345L)
+                                })
+                        .build();
+
+        Parcel parcel = Parcel.obtain();
+        assertTrue(parcel != null);
+        p.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        EuiccProfileInfo fromParcel = EuiccProfileInfo.CREATOR.createFromParcel(parcel);
+
+        assertEquals(p, fromParcel);
+    }
+
+    @Test
+    public void testWriteToParcelNullCarrierId() {
+        EuiccProfileInfo p =
+                new EuiccProfileInfo.Builder()
+                        .setIccid("21430000000000006587")
+                        .setNickname("profile nickname")
+                        .setServiceProviderName("service provider")
+                        .setProfileName("profile name")
+                        .setProfileClass(EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL)
+                        .setState(EuiccProfileInfo.PROFILE_STATE_ENABLED)
+                        .setPolicyRules(EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE)
+                        .setUiccAccessRule(
+                                new UiccAccessRule[] {
+                                        new UiccAccessRule(new byte[] {}, "package", 12345L)
+                                })
+                        .build();
+
+        Parcel parcel = Parcel.obtain();
+        assertTrue(parcel != null);
+        p.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        EuiccProfileInfo fromParcel = EuiccProfileInfo.CREATOR.createFromParcel(parcel);
+
+        assertEquals(p, fromParcel);
+    }
+
+    @Test
+    public void testBuilderAndGetters() {
+        EuiccProfileInfo p =
+                new EuiccProfileInfo.Builder()
+                        .setIccid("21430000000000006587")
+                        .setNickname("profile nickname")
+                        .setProfileName("profile name")
+                        .setServiceProviderName("service provider")
+                        .setCarrierIdentifier(
+                                new CarrierIdentifier(
+                                        new byte[] {0x23, 0x45, 0x67},
+                                        "123",
+                                        "45"))
+                        .setState(EuiccProfileInfo.PROFILE_STATE_ENABLED)
+                        .setProfileClass(EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL)
+                        .setPolicyRules(EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE)
+                        .setUiccAccessRule(
+                                new UiccAccessRule[] {
+                                        new UiccAccessRule(new byte[0], null, 0)
+                                })
+                        .build();
+
+        assertEquals("21430000000000006587", p.getIccid());
+        assertEquals("profile nickname", p.getNickname());
+        assertEquals("profile name", p.getProfileName());
+        assertEquals("service provider", p.getServiceProviderName());
+        assertEquals("325", p.getCarrierIdentifier().getMcc());
+        assertEquals("764", p.getCarrierIdentifier().getMnc());
+        assertEquals("123", p.getCarrierIdentifier().getGid1());
+        assertEquals("45", p.getCarrierIdentifier().getGid2());
+        assertEquals(EuiccProfileInfo.PROFILE_STATE_ENABLED, p.getState());
+        assertEquals(EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL, p.getProfileClass());
+        assertEquals(EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE, p.getPolicyRules());
+        assertTrue(p.hasPolicyRules());
+        assertTrue(p.hasPolicyRule(EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE));
+        assertFalse(p.hasPolicyRule(EuiccProfileInfo.POLICY_RULE_DO_NOT_DISABLE));
+        assertArrayEquals(
+                new UiccAccessRule[] {new UiccAccessRule(new byte[0], null, 0)},
+                p.getUiccAccessRules());
+    }
+
+    @Test
+    public void testBuilder_BasedOnAnotherProfile() {
+        EuiccProfileInfo p =
+                new EuiccProfileInfo.Builder()
+                        .setIccid("21430000000000006587")
+                        .setNickname("profile nickname")
+                        .setProfileName("profile name")
+                        .setServiceProviderName("service provider")
+                        .setCarrierIdentifier(
+                                new CarrierIdentifier(
+                                        new byte[] {0x23, 0x45, 0x67},
+                                        "123",
+                                        "45"))
+                        .setState(EuiccProfileInfo.PROFILE_STATE_ENABLED)
+                        .setProfileClass(EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL)
+                        .setPolicyRules(EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE)
+                        .setUiccAccessRule(
+                                new UiccAccessRule[] {
+                                        new UiccAccessRule(new byte[0], null, 0)
+                                })
+                        .build();
+
+        EuiccProfileInfo copied = new EuiccProfileInfo.Builder(p).build();
+
+        assertEquals(p, copied);
+        assertEquals(p.hashCode(), copied.hashCode());
+    }
+
+    @Test
+    public void testEqualsHashCode() {
+        EuiccProfileInfo p =
+                new EuiccProfileInfo.Builder()
+                        .setIccid("21430000000000006587")
+                        .setNickname("profile nickname")
+                        .setProfileName("profile name")
+                        .setServiceProviderName("service provider")
+                        .setCarrierIdentifier(
+                                new CarrierIdentifier(
+                                        new byte[] {0x23, 0x45, 0x67},
+                                        "123",
+                                        "45"))
+                        .setState(EuiccProfileInfo.PROFILE_STATE_ENABLED)
+                        .setProfileClass(EuiccProfileInfo.PROFILE_STATE_ENABLED)
+                        .setPolicyRules(EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE)
+                        .setUiccAccessRule(
+                                new UiccAccessRule[] {
+                                        new UiccAccessRule(new byte[0], null, 0)
+                                })
+                        .build();
+
+        assertTrue(p.equals(p));
+        assertFalse(p.equals(new Object()));
+
+        EuiccProfileInfo t = null;
+        assertFalse(p.equals(t));
+
+        t = new EuiccProfileInfo.Builder(p).setIccid("21").build();
+        assertFalse(p.equals(t));
+        assertNotEquals(p.hashCode(), t.hashCode());
+
+        t = new EuiccProfileInfo.Builder(p).setNickname(null).build();
+        assertFalse(p.equals(t));
+        assertNotEquals(p.hashCode(), t.hashCode());
+
+        t = new EuiccProfileInfo.Builder(p).setProfileName(null).build();
+        assertFalse(p.equals(t));
+        assertNotEquals(p.hashCode(), t.hashCode());
+
+        t = new EuiccProfileInfo.Builder(p).setServiceProviderName(null).build();
+        assertFalse(p.equals(t));
+        assertNotEquals(p.hashCode(), t.hashCode());
+
+        t = new EuiccProfileInfo.Builder(p).setCarrierIdentifier(null).build();
+        assertFalse(p.equals(t));
+        assertNotEquals(p.hashCode(), t.hashCode());
+
+        t = new EuiccProfileInfo.Builder(p)
+                .setState(EuiccProfileInfo.PROFILE_STATE_DISABLED).build();
+        assertFalse(p.equals(t));
+        assertNotEquals(p.hashCode(), t.hashCode());
+
+        t = new EuiccProfileInfo.Builder(p)
+                .setProfileClass(EuiccProfileInfo.PROFILE_CLASS_TESTING).build();
+        assertFalse(p.equals(t));
+        assertNotEquals(p.hashCode(), t.hashCode());
+
+        t = new EuiccProfileInfo.Builder(p).setPolicyRules(0).build();
+        assertFalse(p.equals(t));
+        assertNotEquals(p.hashCode(), t.hashCode());
+
+        t = new EuiccProfileInfo.Builder(p).setUiccAccessRule(null).build();
+        assertFalse(p.equals(t));
+        assertNotEquals(p.hashCode(), t.hashCode());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testBuilderBuild_NoIccid() {
+        new EuiccProfileInfo.Builder().build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilderSetOperatorMccMnc_Illegal() {
+        new EuiccProfileInfo.Builder()
+                .setCarrierIdentifier(new CarrierIdentifier(new byte[] {1, 2, 3, 4}, null, null));
+    }
+
+    @Test
+    public void testCreatorNewArray() {
+        EuiccProfileInfo[] profiles = EuiccProfileInfo.CREATOR.newArray(123);
+        assertEquals(123, profiles.length);
+    }
+}
diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java
index 9c544f4..04d2dad 100644
--- a/core/tests/coretests/src/android/text/format/FormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/FormatterTest.java
@@ -17,7 +17,6 @@
 package android.text.format;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -210,24 +209,4 @@
 
         Locale.setDefault(locale);
     }
-
-    /**
-     * Verifies that Formatter doesn't "leak" the locally defined petabyte unit into ICU via the
-     * {@link MeasureUnit} registry. This test can fail for two reasons:
-     * 1. we regressed and started leaking again. In this case the code needs to be fixed.
-     * 2. ICU started supporting petabyte as a unit, in which case change one needs to revert this
-     * change (I494fb59a3b3742f35cbdf6b8705817f404a2c6b0), remove Formatter.PETABYTE and replace any
-     * usages of that field with just MeasureUnit.PETABYTE.
-     */
-    // http://b/65632959
-    @Test
-    public void doesNotLeakPetabyte() {
-        // Ensure that the Formatter class is loaded when we call .getAvailable().
-        Formatter.formatFileSize(mContext, Long.MAX_VALUE);
-        Set<MeasureUnit> digitalUnits = MeasureUnit.getAvailable("digital");
-        for (MeasureUnit unit : digitalUnits) {
-            // This assert can fail if we don't leak PETABYTE, but ICU has added it, see #2 above.
-            assertNotEquals("petabyte", unit.getSubtype());
-        }
-    }
 }
diff --git a/core/tests/coretests/src/android/util/DataUnitTest.java b/core/tests/coretests/src/android/util/DataUnitTest.java
new file mode 100644
index 0000000..4eae8b4
--- /dev/null
+++ b/core/tests/coretests/src/android/util/DataUnitTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.support.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+@SmallTest
+public class DataUnitTest extends TestCase {
+    public void testSi() throws Exception {
+        assertEquals(12_000L, DataUnit.KILOBYTES.toBytes(12));
+        assertEquals(12_000_000L, DataUnit.MEGABYTES.toBytes(12));
+        assertEquals(12_000_000_000L, DataUnit.GIGABYTES.toBytes(12));
+    }
+
+    public void testIec() throws Exception {
+        assertEquals(12_288L, DataUnit.KIBIBYTES.toBytes(12));
+        assertEquals(12_582_912L, DataUnit.MEBIBYTES.toBytes(12));
+        assertEquals(12_884_901_888L, DataUnit.GIBIBYTES.toBytes(12));
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewCaptureTest.java b/core/tests/coretests/src/android/view/ViewCaptureTest.java
index 1b4d4a2..4405934 100644
--- a/core/tests/coretests/src/android/view/ViewCaptureTest.java
+++ b/core/tests/coretests/src/android/view/ViewCaptureTest.java
@@ -25,10 +25,14 @@
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.SparseIntArray;
+import android.view.ViewDebug.CanvasProvider;
+import android.view.ViewDebug.HardwareCanvasProvider;
+import android.view.ViewDebug.SoftwareCanvasProvider;
 
 import com.android.frameworks.coretests.R;
 
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -55,23 +59,42 @@
     @Before
     public void setUp() throws Exception {
         mActivity = mActivityRule.getActivity();
-        mViewToCapture = (ViewGroup) mActivity.findViewById(R.id.capture);
+        mViewToCapture = mActivity.findViewById(R.id.capture);
     }
 
     @Test
     @SmallTest
-    public void testCreateSnapshot() {
+    public void testCreateSnapshot_software() {
         assertChildrenVisibility();
-        testCreateSnapshot(true, R.drawable.view_capture_test_no_children_golden);
+        testCreateSnapshot(new SoftwareCanvasProvider(), true,
+                R.drawable.view_capture_test_no_children_golden);
         assertChildrenVisibility();
-        testCreateSnapshot(false, R.drawable.view_capture_test_with_children_golden);
+        testCreateSnapshot(new SoftwareCanvasProvider(), false,
+                R.drawable.view_capture_test_with_children_golden);
         assertChildrenVisibility();
     }
 
-    private void testCreateSnapshot(boolean skipChildren, int goldenResId) {
-        Bitmap result = mViewToCapture.createSnapshot(Bitmap.Config.ARGB_8888, 0, skipChildren);
+    @Test
+    @SmallTest
+    public void testCreateSnapshot_hardware() {
+        Assume.assumeTrue(mViewToCapture.isHardwareAccelerated());
+        assertChildrenVisibility();
+        testCreateSnapshot(new HardwareCanvasProvider(), true,
+                R.drawable.view_capture_test_no_children_golden);
+        assertChildrenVisibility();
+        testCreateSnapshot(new HardwareCanvasProvider(), false,
+                R.drawable.view_capture_test_with_children_golden);
+        assertChildrenVisibility();
+    }
+
+    private void testCreateSnapshot(
+            CanvasProvider canvasProvider, boolean skipChildren, int goldenResId) {
+        Bitmap result = mViewToCapture.createSnapshot(canvasProvider, skipChildren);
         result.setHasAlpha(false); // resource will have no alpha, since content is opaque
         Bitmap golden = BitmapFactory.decodeResource(mActivity.getResources(), goldenResId);
+
+        // We dont care about the config of the bitmap, so convert to same config before comparing
+        result = result.copy(golden.getConfig(), false);
         assertTrue(golden.sameAs(result));
     }
 
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
new file mode 100644
index 0000000..fed197e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.view.accessibility;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * AccessibilityEvent is public, so CTS covers it pretty well. Verifying hidden methods here.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityEventTest {
+    @Test
+    public void testImportantForAccessibiity_getSetWorkAcrossParceling() {
+        AccessibilityEvent event = AccessibilityEvent.obtain();
+        event.setImportantForAccessibility(true);
+        assertTrue(copyEventViaParcel(event).isImportantForAccessibility());
+
+        event.setImportantForAccessibility(false);
+        assertFalse(copyEventViaParcel(event).isImportantForAccessibility());
+    }
+
+    @Test
+    public void testSouceNodeId_getSetWorkAcrossParceling() {
+        final long sourceNodeId = 0x1234567890ABCDEFL;
+        AccessibilityEvent event = AccessibilityEvent.obtain();
+        event.setSourceNodeId(sourceNodeId);
+        assertEquals(sourceNodeId, copyEventViaParcel(event).getSourceNodeId());
+    }
+
+    @Test
+    public void testWindowChanges_getSetWorkAcrossParceling() {
+        final int windowChanges = AccessibilityEvent.WINDOWS_CHANGE_TITLE
+                | AccessibilityEvent.WINDOWS_CHANGE_ACTIVE
+                | AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
+        AccessibilityEvent event = AccessibilityEvent.obtain();
+        event.setWindowChanges(windowChanges);
+        assertEquals(windowChanges, copyEventViaParcel(event).getWindowChanges());
+    }
+
+    private AccessibilityEvent copyEventViaParcel(AccessibilityEvent event) {
+        Parcel parcel = Parcel.obtain();
+        event.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return AccessibilityEvent.CREATOR.createFromParcel(parcel);
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewFallbackLineSpacingTest.java b/core/tests/coretests/src/android/widget/TextViewFallbackLineSpacingTest.java
new file mode 100644
index 0000000..e9d1d3e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/TextViewFallbackLineSpacingTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.text.DynamicLayout;
+import android.text.FontFallbackSetup;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.TextView.BufferType;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Parametrized test for TextView#setFallbackLineSpacing.
+ */
+@MediumTest
+@RunWith(Parameterized.class)
+public class TextViewFallbackLineSpacingTest {
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection layouts() {
+        return Arrays.asList(new Object[][] {
+                // name, enabled, BufferType
+                { "Enabled - StaticLayout", true, BufferType.NORMAL},
+                { "Disabled - StaticLayout", false, BufferType.NORMAL},
+                { "Enabled - DynamicLayout", true, BufferType.EDITABLE},
+                { "Disabled - DynamicLayout", false, BufferType.EDITABLE},
+        });
+    }
+
+    @Rule
+    public ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>(
+            TextViewActivity.class);
+
+    private final boolean mEnabled;
+    private final BufferType mBufferType;
+
+    public TextViewFallbackLineSpacingTest(String testName, boolean enabled,
+            BufferType bufferType) {
+        mEnabled = enabled;
+        mBufferType = bufferType;
+    }
+
+    @Test
+    public void testFallbackLineSpacing() {
+        // All glyphs in the fonts are 1em wide.
+        final String[] testFontFiles = {
+                // ascent == 1em, descent == 2em, only supports 'a' and space
+                "ascent1em-descent2em.ttf",
+                // ascent == 3em, descent == 4em, only supports 'b'
+                "ascent3em-descent4em.ttf"
+        };
+        final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+                + "<familyset>"
+                + "  <family name='sans-serif'>"
+                + "    <font weight='400' style='normal'>ascent1em-descent2em.ttf</font>"
+                + "  </family>"
+                + "  <family>"
+                + "    <font weight='400' style='normal'>ascent3em-descent4em.ttf</font>"
+                + "  </family>"
+                + "</familyset>";
+
+        try (FontFallbackSetup setup =
+                     new FontFallbackSetup("DynamicLayout", testFontFiles, xml)) {
+            final Activity activity = mActivityRule.getActivity();
+            final TextView textView = new TextView(activity);
+            textView.setTypeface(setup.getTypefaceFor("sans-serif"));
+            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 100);
+            // This should result in three lines.
+            textView.setText("aaaaa aabaa aaaaa", mBufferType);
+            textView.setPadding(0, 0, 0, 0);
+            textView.setIncludeFontPadding(false);
+            textView.setFallbackLineSpacing(mEnabled);
+
+            final int em = (int) Math.ceil(textView.getPaint().measureText("a"));
+            final int width = 5 * em;
+            final int height = 30 * em; // tall enough to not affect our other measurements
+            textView.measure(
+                    View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+                    View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
+            textView.layout(0, 0, width, height);
+
+            final Layout layout = textView.getLayout();
+            assertNotNull(layout);
+            if (mBufferType == BufferType.NORMAL) {
+                assertTrue(layout instanceof StaticLayout);
+            } else {
+                assertTrue(layout instanceof DynamicLayout);
+            }
+            assertEquals(3, layout.getLineCount());
+
+            assertEquals(-em, layout.getLineAscent(0));
+            assertEquals(2 * em, layout.getLineDescent(0));
+
+            if (mEnabled) {
+                // The second line has a 'b', so it needs more ascent and descent.
+                assertEquals(-3 * em, layout.getLineAscent(1));
+                assertEquals(4 * em, layout.getLineDescent(1));
+            } else {
+                // old behavior
+                assertEquals(-em, layout.getLineAscent(1));
+                assertEquals(2 * em, layout.getLineDescent(1));
+            }
+
+            assertEquals(-em, layout.getLineAscent(2));
+            assertEquals(2 * em, layout.getLineDescent(2));
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index 7875c17..2b5b27b 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -30,7 +30,6 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
-import android.text.FontFallbackSetup;
 import android.text.GetChars;
 import android.text.Layout;
 import android.text.Selection;
@@ -250,56 +249,4 @@
         }
         return builder.toString();
     }
-
-    @Test
-    public void testFallbackLineSpacing() {
-        // All glyphs in the fonts are 1em wide.
-        final String[] testFontFiles = {
-            // ascent == 1em, descent == 2em, only supports 'a' and space
-            "ascent1em-descent2em.ttf",
-            // ascent == 3em, descent == 4em, only supports 'b'
-            "ascent3em-descent4em.ttf"
-        };
-        final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
-                + "<familyset>"
-                + "  <family name='sans-serif'>"
-                + "    <font weight='400' style='normal'>ascent1em-descent2em.ttf</font>"
-                + "  </family>"
-                + "  <family>"
-                + "    <font weight='400' style='normal'>ascent3em-descent4em.ttf</font>"
-                + "  </family>"
-                + "</familyset>";
-
-        try (FontFallbackSetup setup =
-                new FontFallbackSetup("DynamicLayout", testFontFiles, xml)) {
-            mTextView = new TextView(mActivity);
-            mTextView.setTypeface(setup.getTypefaceFor("sans-serif"));
-            mTextView.setTextSize(100);
-            mTextView.setText("aaaaa aabaa aaaaa"); // This should result in three lines.
-            mTextView.setPadding(0, 0, 0, 0);
-            mTextView.setIncludeFontPadding(false);
-
-            final int em = (int) Math.ceil(mTextView.getPaint().measureText("a"));
-            final int width = 5 * em;
-            final int height = 30 * em; // tall enough to not affect our other measurements
-            mTextView.measure(
-                    View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
-                    View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
-            mTextView.layout(0, 0, width, height);
-
-            final Layout layout = mTextView.getLayout();
-            assertNotNull(layout);
-            assertEquals(3, layout.getLineCount());
-
-            assertEquals(-em, layout.getLineAscent(0));
-            assertEquals(2 * em, layout.getLineDescent(0));
-
-            // The second line has a 'b', so it needs more ascent and descent.
-            assertEquals(-3 * em, layout.getLineAscent(1));
-            assertEquals(4 * em, layout.getLineDescent(1));
-
-            assertEquals(-em, layout.getLineAscent(2));
-            assertEquals(2 * em, layout.getLineDescent(2));
-        }
-    }
 }
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 c611a01..acf3022 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
@@ -194,9 +194,19 @@
 
     @SmallTest
     public void testAppBluetoothScan() throws Exception {
+        doTestAppBluetoothScanInternal(new WorkSource(UID));
+    }
+
+    @SmallTest
+    public void testAppBluetoothScan_workChain() throws Exception {
+        WorkSource ws = new WorkSource();
+        ws.createWorkChain().addNode(UID, "foo");
+        doTestAppBluetoothScanInternal(ws);
+    }
+
+    private void doTestAppBluetoothScanInternal(WorkSource ws) throws Exception {
         final MockClocks clocks = new MockClocks();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
-        WorkSource ws = new WorkSource(UID); // needed for bluetooth
         long curr = 0; // realtime in us
 
         // On battery
@@ -263,6 +273,53 @@
     }
 
     @SmallTest
+    public void testAppBluetoothScan_workChainAccounting() throws Exception {
+        final MockClocks clocks = new MockClocks();MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        long curr = 0; // realtime in us
+
+        // On battery
+        curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr); // on battery
+
+        // App in foreground
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+        WorkSource ws = new WorkSource();
+        ws.createWorkChain().addNode(500, "foo");
+        ws.createWorkChain().addNode(500, "bar");
+
+        // Test start / stop and reset with isUnoptimized == false.
+        bi.noteBluetoothScanStartedFromSourceLocked(ws, false);
+        BatteryStatsImpl.Uid stats = (BatteryStatsImpl.Uid) bi.getUidStats().get(500);
+        assertEquals(ws.getWorkChains(), stats.getAllBluetoothWorkChains());
+        assertNull(stats.getUnoptimizedBluetoothWorkChains());
+
+        bi.noteBluetoothScanStoppedFromSourceLocked(ws, false);
+        assertTrue(stats.getAllBluetoothWorkChains().isEmpty());
+        assertNull(stats.getUnoptimizedBluetoothWorkChains());
+
+        bi.noteBluetoothScanStartedFromSourceLocked(ws, false);
+        bi.noteResetBluetoothScanLocked();
+        assertTrue(stats.getAllBluetoothWorkChains().isEmpty());
+        assertNull(stats.getUnoptimizedBluetoothWorkChains());
+
+        // Test start / stop  and reset with isUnoptimized == true.
+        bi.noteBluetoothScanStartedFromSourceLocked(ws, true);
+        stats = (BatteryStatsImpl.Uid) bi.getUidStats().get(500);
+        assertEquals(ws.getWorkChains(), stats.getAllBluetoothWorkChains());
+        assertEquals(ws.getWorkChains(), stats.getUnoptimizedBluetoothWorkChains());
+
+        bi.noteBluetoothScanStoppedFromSourceLocked(ws, true);
+        assertTrue(stats.getAllBluetoothWorkChains().isEmpty());
+        assertTrue(stats.getUnoptimizedBluetoothWorkChains().isEmpty());
+
+        bi.noteBluetoothScanStartedFromSourceLocked(ws, true);
+        bi.noteResetBluetoothScanLocked();
+        assertTrue(stats.getAllBluetoothWorkChains().isEmpty());
+        assertTrue(stats.getUnoptimizedBluetoothWorkChains().isEmpty());
+    }
+
+    @SmallTest
     public void testJob() throws Exception {
         final MockClocks clocks = new MockClocks();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index 3794b5f..a8094ea 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -71,7 +71,7 @@
     @Test
     public void testUpdateProcStateCpuTimes() {
         mBatteryStatsImpl.setOnBatteryInternal(true);
-        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+        mBatteryStatsImpl.updateTimeBasesLocked(false, Display.STATE_ON, 0, 0);
 
         final int[] testUids = {10032, 10048, 10145, 10139};
         final int[] testProcStates = {
@@ -98,7 +98,7 @@
             }
         }
 
-        mBatteryStatsImpl.updateProcStateCpuTimes();
+        mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
 
         verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
@@ -125,7 +125,7 @@
         }
         addPendingUids(testUids, testProcStates);
 
-        mBatteryStatsImpl.updateProcStateCpuTimes();
+        mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
 
         verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
@@ -157,7 +157,7 @@
         }
         addPendingUids(testUids, testProcStates);
 
-        mBatteryStatsImpl.updateProcStateCpuTimes();
+        mBatteryStatsImpl.updateProcStateCpuTimes(true, true);
 
         verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
@@ -196,7 +196,7 @@
         final long[] isolatedUidCpuTimes = {495784, 398473, 4895, 4905, 30984093};
         when(mKernelSingleUidTimeReader.readDeltaMs(childUid)).thenReturn(isolatedUidCpuTimes);
 
-        mBatteryStatsImpl.updateProcStateCpuTimes();
+        mBatteryStatsImpl.updateProcStateCpuTimes(true, true);
 
         verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
@@ -227,8 +227,8 @@
 
     @Test
     public void testCopyFromAllUidsCpuTimes() {
-        mBatteryStatsImpl.setOnBatteryInternal(true);
-        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+        mBatteryStatsImpl.setOnBatteryInternal(false);
+        mBatteryStatsImpl.updateTimeBasesLocked(false, Display.STATE_ON, 0, 0);
 
         final int[] testUids = {10032, 10048, 10145, 10139};
         final int[] testProcStates = {
@@ -264,7 +264,7 @@
                     .thenReturn(expectedCpuTimes[i]);
         }
 
-        mBatteryStatsImpl.copyFromAllUidsCpuTimes();
+        mBatteryStatsImpl.copyFromAllUidsCpuTimes(true, false);
 
         verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
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 0afec34..a55563a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -15,16 +15,19 @@
  */
 package com.android.internal.os;
 
+import static android.os.BatteryStats.STATS_CURRENT;
 import static android.os.BatteryStats.STATS_SINCE_CHARGED;
 import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
 
 import android.app.ActivityManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
+import android.os.BatteryStats.HistoryItem;
 import android.os.WorkSource;
 import android.support.test.filters.SmallTest;
 import android.view.Display;
 
+import com.android.internal.os.BatteryStatsImpl.Uid;
 import junit.framework.TestCase;
 
 import java.util.ArrayList;
@@ -44,11 +47,14 @@
  * Run: adb shell am instrument -e class com.android.internal.os.BatteryStatsNoteTest -w \
  *      com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
  */
-public class BatteryStatsNoteTest extends TestCase{
+public class BatteryStatsNoteTest extends TestCase {
+
     private static final int UID = 10500;
     private static final WorkSource WS = new WorkSource(UID);
 
-    /** Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked. */
+    /**
+     * Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked.
+     */
     @SmallTest
     public void testNoteBluetoothScanResultLocked() throws Exception {
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClocks());
@@ -75,7 +81,9 @@
                         .getCountLocked(STATS_SINCE_CHARGED));
     }
 
-    /** Test BatteryStatsImpl.Uid.noteStartWakeLocked. */
+    /**
+     * Test BatteryStatsImpl.Uid.noteStartWakeLocked.
+     */
     @SmallTest
     public void testNoteStartWakeLocked() throws Exception {
         final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
@@ -86,7 +94,8 @@
 
         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);
+        bi.getUidStatsLocked(UID)
+                .noteStartWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime);
 
         clocks.realtime = clocks.uptime = 100;
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
@@ -94,7 +103,8 @@
         clocks.realtime = clocks.uptime = 220;
         bi.getUidStatsLocked(UID).noteStopWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime);
 
-        BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID).getAggregatedPartialWakelockTimer();
+        BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+                .getAggregatedPartialWakelockTimer();
         long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
         long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
         assertEquals(220_000, actualTime);
@@ -102,7 +112,9 @@
     }
 
 
-    /** Test BatteryStatsImpl.noteUidProcessStateLocked. */
+    /**
+     * Test BatteryStatsImpl.noteUidProcessStateLocked.
+     */
     @SmallTest
     public void testNoteUidProcessStateLocked() throws Exception {
         final MockClocks clocks = new MockClocks();
@@ -145,7 +157,6 @@
         expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TOP);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
-
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
                 elapsedTimeUs, STATS_SINCE_CHARGED);
         expectedRunTimeMs = stateRuntimeMap.get(
@@ -153,19 +164,16 @@
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
-
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING,
                 elapsedTimeUs, STATS_SINCE_CHARGED);
         expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
-
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND,
                 elapsedTimeUs, STATS_SINCE_CHARGED);
         expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
-
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
                 elapsedTimeUs, STATS_SINCE_CHARGED);
         expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
@@ -174,7 +182,6 @@
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
-
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_CACHED,
                 elapsedTimeUs, STATS_SINCE_CHARGED);
         expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_HOME)
@@ -191,7 +198,9 @@
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
     }
 
-    /** Test BatteryStatsImpl.updateTimeBasesLocked. */
+    /**
+     * Test BatteryStatsImpl.updateTimeBasesLocked.
+     */
     @SmallTest
     public void testUpdateTimeBasesLocked() throws Exception {
         final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
@@ -213,7 +222,9 @@
         assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
     }
 
-    /** Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly. */
+    /**
+     * 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
@@ -232,15 +243,13 @@
         assertEquals(bi.getScreenState(), Display.STATE_OFF);
     }
 
-    /** Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly.
+    /**
+     * Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly.
      *
-     *  Unknown and doze should both be subset of off state
+     * Unknown and doze should both be subset of off state
      *
-     *  Timeline 0----100----200----310----400------------1000
-     *  Unknown         -------
-     *  On                     -------
-     *  Off             -------       ----------------------
-     *  Doze                                ----------------
+     * Timeline 0----100----200----310----400------------1000 Unknown         ------- On ------- Off
+     * -------       ---------------------- Doze ----------------
      */
     @SmallTest
     public void testNoteScreenStateTimersLocked() throws Exception {
@@ -280,4 +289,161 @@
         assertEquals(600_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED));
     }
 
+    @SmallTest
+    public void testAlarmStartAndFinishLocked() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setRecordAllHistoryLocked(true);
+        bi.forceRecordAllHistory();
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+        clocks.realtime = clocks.uptime = 100;
+        bi.noteAlarmStartLocked("foo", null, UID);
+        clocks.realtime = clocks.uptime = 5000;
+        bi.noteAlarmFinishLocked("foo", null, UID);
+
+        HistoryItem item = new HistoryItem();
+        assertTrue(bi.startIteratingHistoryLocked());
+
+        assertTrue(bi.getNextHistoryLocked(item));
+        assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
+        assertEquals("foo", item.eventTag.string);
+        assertEquals(UID, item.eventTag.uid);
+
+        // TODO(narayan): Figure out why this event is written to the history buffer. See
+        // test below where it is being interspersed between multiple START events too.
+        assertTrue(bi.getNextHistoryLocked(item));
+        assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
+
+        assertTrue(bi.getNextHistoryLocked(item));
+        assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
+        assertTrue(item.isDeltaData());
+        assertEquals("foo", item.eventTag.string);
+        assertEquals(UID, item.eventTag.uid);
+
+        assertFalse(bi.getNextHistoryLocked(item));
+    }
+
+    @SmallTest
+    public void testAlarmStartAndFinishLocked_workSource() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setRecordAllHistoryLocked(true);
+        bi.forceRecordAllHistory();
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+        WorkSource ws = new WorkSource();
+        ws.add(100);
+        ws.createWorkChain().addNode(500, "tag");
+        bi.noteAlarmStartLocked("foo", ws, UID);
+        clocks.realtime = clocks.uptime = 5000;
+        bi.noteAlarmFinishLocked("foo", ws, UID);
+
+        HistoryItem item = new HistoryItem();
+        assertTrue(bi.startIteratingHistoryLocked());
+
+        assertTrue(bi.getNextHistoryLocked(item));
+        assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
+        assertEquals("foo", item.eventTag.string);
+        assertEquals(100, item.eventTag.uid);
+
+        assertTrue(bi.getNextHistoryLocked(item));
+        assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
+
+        assertTrue(bi.getNextHistoryLocked(item));
+        assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
+        assertEquals("foo", item.eventTag.string);
+        assertEquals(500, item.eventTag.uid);
+
+        assertTrue(bi.getNextHistoryLocked(item));
+        assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
+        assertEquals("foo", item.eventTag.string);
+        assertEquals(100, item.eventTag.uid);
+
+        assertTrue(bi.getNextHistoryLocked(item));
+        assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
+        assertEquals("foo", item.eventTag.string);
+        assertEquals(500, item.eventTag.uid);
+    }
+
+    @SmallTest
+    public void testNoteWakupAlarmLocked() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setRecordAllHistoryLocked(true);
+        bi.forceRecordAllHistory();
+        bi.mForceOnBattery = true;
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+        bi.noteWakupAlarmLocked("com.foo.bar", UID, null, "tag");
+
+        Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar");
+        assertEquals(1, pkg.getWakeupAlarmStats().get("tag").getCountLocked(STATS_CURRENT));
+        assertEquals(1, pkg.getWakeupAlarmStats().size());
+    }
+
+    @SmallTest
+    public void testNoteWakupAlarmLocked_workSource_uid() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setRecordAllHistoryLocked(true);
+        bi.forceRecordAllHistory();
+        bi.mForceOnBattery = true;
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+        WorkSource ws = new WorkSource();
+        ws.add(100);
+
+        // When a WorkSource is present, "UID" should not be used - only the uids present in the
+        // WorkSource should be reported.
+        bi.noteWakupAlarmLocked("com.foo.bar", UID, ws, "tag");
+        Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar");
+        assertEquals(0, pkg.getWakeupAlarmStats().size());
+        pkg = bi.getPackageStatsLocked(100, "com.foo.bar");
+        assertEquals(1, pkg.getWakeupAlarmStats().size());
+
+        // If the WorkSource contains a "name", it should be interpreted as a package name and
+        // the packageName supplied as an argument must be ignored.
+        ws = new WorkSource();
+        ws.add(100, "com.foo.baz_alternate");
+        bi.noteWakupAlarmLocked("com.foo.baz", UID, ws, "tag");
+        pkg = bi.getPackageStatsLocked(100, "com.foo.baz");
+        assertEquals(0, pkg.getWakeupAlarmStats().size());
+        pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate");
+        assertEquals(1, pkg.getWakeupAlarmStats().size());
+    }
+
+    @SmallTest
+    public void testNoteWakupAlarmLocked_workSource_workChain() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setRecordAllHistoryLocked(true);
+        bi.forceRecordAllHistory();
+        bi.mForceOnBattery = true;
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+        WorkSource ws = new WorkSource();
+        ws.createWorkChain().addNode(100, "com.foo.baz_alternate");
+        bi.noteWakupAlarmLocked("com.foo.bar", UID, ws, "tag");
+
+        // For WorkChains, again we must only attribute to the uids present in the WorkSource
+        // (and not to "UID"). However, unlike the older "tags" we do not change the packagename
+        // supplied as an argument, given that we're logging the entire attribution chain.
+        Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar");
+        assertEquals(0, pkg.getWakeupAlarmStats().size());
+        pkg = bi.getPackageStatsLocked(100, "com.foo.bar");
+        assertEquals(1, pkg.getWakeupAlarmStats().size());
+        pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate");
+        assertEquals(0, pkg.getWakeupAlarmStats().size());
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index e54fe7d..4d34721 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -25,6 +25,8 @@
 import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
 import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
 
+import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_TRACK_CPU_TIMES_BY_PROC_STATE;
+
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
@@ -48,15 +50,19 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.SystemClock;
+import android.provider.Settings;
 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.text.TextUtils;
 import android.util.DebugUtils;
 import android.util.Log;
 
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
@@ -86,6 +92,9 @@
     private static final int START_SERVICE_TIMEOUT_MS = 2000;
     private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;
 
+    private static final int SETTING_UPDATE_TIMEOUT_MS = 2000;
+    private static final int SETTING_UPDATE_CHECK_INTERVAL_MS = 200;
+
     private static final int GENERAL_TIMEOUT_MS = 4000;
     private static final int GENERAL_INTERVAL_MS = 200;
 
@@ -97,6 +106,8 @@
     private static boolean sCpuFreqTimesAvailable;
     private static boolean sPerProcStateTimesAvailable;
 
+    @Rule public TestName testName = new TestName();
+
     @BeforeClass
     public static void setupOnce() throws Exception {
         sContext = InstrumentationRegistry.getContext();
@@ -123,6 +134,9 @@
     @Test
     public void testCpuFreqTimes() throws Exception {
         if (!sCpuFreqTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -148,6 +162,9 @@
     @Test
     public void testCpuFreqTimes_screenOff() throws Exception {
         if (!sCpuFreqTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -179,6 +196,9 @@
     @Test
     public void testCpuFreqTimes_isolatedProcess() throws Exception {
         if (!sCpuFreqTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -204,6 +224,9 @@
     @Test
     public void testCpuFreqTimes_stateTop() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -234,6 +257,9 @@
     @Test
     public void testIsolatedCpuFreqTimes_stateService() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -272,6 +298,9 @@
     @Test
     public void testCpuFreqTimes_stateTopSleeping() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -302,6 +331,9 @@
     @Test
     public void testCpuFreqTimes_stateFgService() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -332,6 +364,9 @@
     @Test
     public void testCpuFreqTimes_stateFg() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -362,6 +397,9 @@
     @Test
     public void testCpuFreqTimes_stateBg() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -392,6 +430,9 @@
     @Test
     public void testCpuFreqTimes_stateCached() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -419,6 +460,124 @@
         batteryOffScreenOn();
     }
 
+    @Test
+    public void testCpuFreqTimes_trackingDisabled() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
+            return;
+        }
+
+        final String bstatsConstants = Settings.Global.getString(sContext.getContentResolver(),
+                Settings.Global.BATTERY_STATS_CONSTANTS);
+        try {
+            batteryOnScreenOn();
+            forceStop();
+            resetBatteryStats();
+            final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+            assertNull("Initial snapshot should be null, initial="
+                    + Arrays.toString(initialSnapshot), initialSnapshot);
+            assertNull("Initial top state snapshot should be null",
+                    getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+            doSomeWork(PROCESS_STATE_TOP);
+            forceStop();
+
+            final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+            final String msgCpuTimes = getAllCpuTimesMsg();
+            assertCpuTimesValid(cpuTimesMs);
+            long actualCpuTimeMs = 0;
+            for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+                actualCpuTimeMs += cpuTimesMs[i];
+            }
+            assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                    WORK_DURATION_MS, actualCpuTimeMs);
+
+            updateTrackPerProcStateCpuTimesSetting(bstatsConstants, false);
+
+            doSomeWork(PROCESS_STATE_TOP);
+            forceStop();
+
+            final long[] cpuTimesMs2 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+            assertCpuTimesValid(cpuTimesMs2);
+            assertCpuTimesEqual(cpuTimesMs2, cpuTimesMs, 20,
+                    "Unexpected cpu times with tracking off");
+
+            updateTrackPerProcStateCpuTimesSetting(bstatsConstants, true);
+
+            final long[] cpuTimesMs3 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+            assertCpuTimesValid(cpuTimesMs3);
+            assertCpuTimesEqual(cpuTimesMs3, cpuTimesMs, 20,
+                    "Unexpected cpu times after turning on tracking");
+
+            doSomeWork(PROCESS_STATE_TOP);
+            forceStop();
+
+            final long[] cpuTimesMs4 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+            assertCpuTimesValid(cpuTimesMs4);
+            actualCpuTimeMs = 0;
+            for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+                actualCpuTimeMs += cpuTimesMs[i];
+            }
+            assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                    2 * WORK_DURATION_MS, actualCpuTimeMs);
+
+            batteryOffScreenOn();
+        } finally {
+            Settings.Global.putString(sContext.getContentResolver(),
+                    Settings.Global.BATTERY_STATS_CONSTANTS, bstatsConstants);
+        }
+    }
+
+    private void assertCpuTimesEqual(long[] actual, long[] expected, long delta, String errMsg) {
+        for (int i = actual.length - 1; i >= 0; --i) {
+            if (actual[i] > expected[i] + delta || actual[i] < expected[i]) {
+                fail(errMsg + ", actual=" + actual + ", expected=" + expected + ", delta=" + delta);
+            }
+        }
+    }
+
+    private void updateTrackPerProcStateCpuTimesSetting(String originalConstants, boolean enabled)
+            throws Exception {
+        final String newConstants;
+        final String setting = KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=" + enabled;
+        if (originalConstants == null || "null".equals(originalConstants)) {
+            newConstants = setting;
+        } else if (originalConstants.contains(KEY_TRACK_CPU_TIMES_BY_PROC_STATE)) {
+            newConstants = originalConstants.replaceAll(
+                    KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=(true|false)", setting);
+        } else {
+            newConstants = originalConstants + "," + setting;
+        }
+        Settings.Global.putString(sContext.getContentResolver(),
+                Settings.Global.BATTERY_STATS_CONSTANTS, newConstants);
+        assertTrackPerProcStateCpuTimesSetting(enabled);
+    }
+
+    private void assertTrackPerProcStateCpuTimesSetting(boolean enabled) throws Exception {
+        final String expectedValue = Boolean.toString(enabled);
+        assertDelayedCondition("Unexpected value for " + KEY_TRACK_CPU_TIMES_BY_PROC_STATE, () -> {
+            final String actualValue = getSettingValueFromDump(KEY_TRACK_CPU_TIMES_BY_PROC_STATE);
+            return expectedValue.equals(actualValue)
+                    ? null : "expected=" + expectedValue + ", actual=" + actualValue;
+        }, SETTING_UPDATE_TIMEOUT_MS, SETTING_UPDATE_CHECK_INTERVAL_MS);
+    }
+
+    private String getSettingValueFromDump(String key) throws Exception {
+        final String settingsDump = executeCmdSilent("dumpsys batterystats --settings");
+        final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
+        splitter.setString(settingsDump);
+        String next;
+        while (splitter.hasNext()) {
+            next = splitter.next();
+            if (next.startsWith(key)) {
+                return next.split("=")[1];
+            }
+        }
+        return null;
+    }
+
     private void assertCpuTimesValid(long[] cpuTimes) {
         assertNotNull(cpuTimes);
         for (int i = 0; i < cpuTimes.length; ++i) {
@@ -750,13 +909,18 @@
     }
 
     private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition)
-            throws Exception {
-        final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
+        throws Exception {
+        assertDelayedCondition(errMsgPrefix, condition, GENERAL_TIMEOUT_MS, GENERAL_INTERVAL_MS);
+    }
+
+    private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition,
+            long timeoutMs, long checkIntervalMs) throws Exception {
+        final long endTime = SystemClock.uptimeMillis() + timeoutMs;
         while (SystemClock.uptimeMillis() <= endTime) {
             if (condition.getErrIfNotTrue() == null) {
                 return;
             }
-            SystemClock.sleep(GENERAL_INTERVAL_MS);
+            SystemClock.sleep(checkIntervalMs);
         }
         final String errMsg = condition.getErrIfNotTrue();
         if (errMsg != null) {
diff --git a/core/tests/coretests/src/com/android/internal/os/LongSamplingCounterArrayTest.java b/core/tests/coretests/src/com/android/internal/os/LongSamplingCounterArrayTest.java
index 27aec56..37b4e41a 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongSamplingCounterArrayTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongSamplingCounterArrayTest.java
@@ -16,7 +16,9 @@
 
 package com.android.internal.os;
 
+import static android.os.BatteryStats.STATS_CURRENT;
 import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+import static android.os.BatteryStats.STATS_SINCE_UNPLUGGED;
 
 import static com.android.internal.os.BatteryStatsImpl.LongSamplingCounterArray;
 import static com.android.internal.os.BatteryStatsImpl.TimeBase;
@@ -61,7 +63,6 @@
 
     private static final long[] COUNTS = {1111, 2222, 3333, 4444};
     private static final long[] LOADED_COUNTS = {5555, 6666, 7777, 8888};
-    private static final long[] PLUGGED_COUNTS = {9999, 11111, 22222, 33333};
     private static final long[] UNPLUGGED_COUNTS = {44444, 55555, 66666, 77777};
     private static final long[] ZEROES = {0, 0, 0, 0};
 
@@ -83,11 +84,10 @@
         parcel.setDataPosition(0);
 
         // Now clear counterArray and verify values are read from parcel correctly.
-        updateCounts(null, null, null, null);
+        updateCounts(null, null, null);
         mCounterArray = LongSamplingCounterArray.readFromParcel(parcel, mTimeBase);
         assertArrayEquals(COUNTS, mCounterArray.mCounts, "Unexpected counts");
         assertArrayEquals(LOADED_COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
-        assertArrayEquals(COUNTS, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts");
         assertArrayEquals(UNPLUGGED_COUNTS, mCounterArray.mUnpluggedCounts,
                 "Unexpected unpluggedCounts");
         parcel.recycle();
@@ -101,11 +101,10 @@
         parcel.setDataPosition(0);
 
         // Now clear counterArray and verify values are read from parcel correctly.
-        updateCounts(null, null, null, null);
+        updateCounts(null, null, null);
         mCounterArray = LongSamplingCounterArray.readSummaryFromParcelLocked(parcel, mTimeBase);
         assertArrayEquals(COUNTS, mCounterArray.mCounts, "Unexpected counts");
         assertArrayEquals(COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
-        assertArrayEquals(COUNTS, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts");
         assertArrayEquals(COUNTS, mCounterArray.mUnpluggedCounts, "Unexpected unpluggedCounts");
         parcel.recycle();
     }
@@ -116,8 +115,7 @@
         mCounterArray.onTimeStarted(0, 0, 0);
         assertArrayEquals(COUNTS, mCounterArray.mCounts, "Unexpected counts");
         assertArrayEquals(LOADED_COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
-        assertArrayEquals(PLUGGED_COUNTS, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts");
-        assertArrayEquals(PLUGGED_COUNTS, mCounterArray.mUnpluggedCounts,
+        assertArrayEquals(COUNTS, mCounterArray.mUnpluggedCounts,
                 "Unexpected unpluggedCounts");
     }
 
@@ -127,7 +125,6 @@
         mCounterArray.onTimeStopped(0, 0, 0);
         assertArrayEquals(COUNTS, mCounterArray.mCounts, "Unexpected counts");
         assertArrayEquals(LOADED_COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
-        assertArrayEquals(COUNTS, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts");
         assertArrayEquals(UNPLUGGED_COUNTS, mCounterArray.mUnpluggedCounts,
                 "Unexpected unpluggedCounts");
     }
@@ -137,24 +134,50 @@
         initializeCounterArrayWithDefaultValues();
 
         when(mTimeBase.isRunning()).thenReturn(false);
-        long[] actualVal = mCounterArray.getCountsLocked(STATS_SINCE_CHARGED);
-        long[] expectedVal = PLUGGED_COUNTS;
-        assertArrayEquals(expectedVal, actualVal, "Unexpected values");
+        assertArrayEquals(COUNTS, mCounterArray.getCountsLocked(STATS_SINCE_CHARGED),
+                "Unexpected values");
+        assertArrayEquals(subtract(COUNTS, LOADED_COUNTS),
+                mCounterArray.getCountsLocked(STATS_CURRENT), "Unexpected values");
+        assertArrayEquals(subtract(COUNTS, UNPLUGGED_COUNTS),
+                mCounterArray.getCountsLocked(STATS_SINCE_UNPLUGGED), "Unexpected values");
 
         when(mTimeBase.isRunning()).thenReturn(true);
-        actualVal = mCounterArray.getCountsLocked(STATS_SINCE_CHARGED);
-        expectedVal = COUNTS;
-        assertArrayEquals(expectedVal, actualVal, "Unexpected values");
+        assertArrayEquals(COUNTS, mCounterArray.getCountsLocked(STATS_SINCE_CHARGED),
+                "Unexpected values");
+        assertArrayEquals(subtract(COUNTS, LOADED_COUNTS),
+                mCounterArray.getCountsLocked(STATS_CURRENT), "Unexpected values");
+        assertArrayEquals(subtract(COUNTS, UNPLUGGED_COUNTS),
+                mCounterArray.getCountsLocked(STATS_SINCE_UNPLUGGED), "Unexpected values");
+    }
+
+    private long[] subtract(long[] val, long[] toSubtract) {
+        final long[] result = val.clone();
+        if (toSubtract != null) {
+            for (int i = val.length - 1; i >= 0; --i) {
+                result[i] -= toSubtract[i];
+            }
+        }
+        return result;
     }
 
     @Test
     public void testAddCountLocked() {
+        updateCounts(null, null, null);
         final long[] deltas = {123, 234, 345, 456};
         when(mTimeBase.isRunning()).thenReturn(true);
         mCounterArray.addCountLocked(deltas);
         assertArrayEquals(deltas, mCounterArray.mCounts, "Unexpected counts");
         assertArrayEquals(null, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
-        assertArrayEquals(null, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts");
+        assertArrayEquals(null, mCounterArray.mUnpluggedCounts, "Unexpected unpluggedCounts");
+
+        updateCounts(null, null, null);
+        mCounterArray.addCountLocked(deltas, false);
+        assertArrayEquals(null, mCounterArray.mCounts, "Unexpected counts");
+        assertArrayEquals(null, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
+        assertArrayEquals(null, mCounterArray.mUnpluggedCounts, "Unexpected unpluggedCounts");
+        mCounterArray.addCountLocked(deltas, true);
+        assertArrayEquals(deltas, mCounterArray.mCounts, "Unexpected counts");
+        assertArrayEquals(null, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
         assertArrayEquals(null, mCounterArray.mUnpluggedCounts, "Unexpected unpluggedCounts");
 
         initializeCounterArrayWithDefaultValues();
@@ -165,7 +188,18 @@
         mCounterArray.addCountLocked(deltas);
         assertArrayEquals(newCounts, mCounterArray.mCounts, "Unexpected counts");
         assertArrayEquals(LOADED_COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
-        assertArrayEquals(PLUGGED_COUNTS, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts");
+        assertArrayEquals(UNPLUGGED_COUNTS, mCounterArray.mUnpluggedCounts,
+                "Unexpected unpluggedCounts");
+
+        initializeCounterArrayWithDefaultValues();
+        mCounterArray.addCountLocked(deltas, false);
+        assertArrayEquals(COUNTS, mCounterArray.mCounts, "Unexpected counts");
+        assertArrayEquals(LOADED_COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
+        assertArrayEquals(UNPLUGGED_COUNTS, mCounterArray.mUnpluggedCounts,
+                "Unexpected unpluggedCounts");
+        mCounterArray.addCountLocked(deltas, true);
+        assertArrayEquals(newCounts, mCounterArray.mCounts, "Unexpected counts");
+        assertArrayEquals(LOADED_COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
         assertArrayEquals(UNPLUGGED_COUNTS, mCounterArray.mUnpluggedCounts,
                 "Unexpected unpluggedCounts");
     }
@@ -177,7 +211,6 @@
         mCounterArray.reset(false /* detachIfReset */);
         assertArrayEquals(ZEROES, mCounterArray.mCounts, "Unexpected counts");
         assertArrayEquals(ZEROES, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
-        assertArrayEquals(ZEROES, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts");
         assertArrayEquals(ZEROES, mCounterArray.mUnpluggedCounts, "Unexpected unpluggedCounts");
         verifyZeroInteractions(mTimeBase);
 
@@ -186,7 +219,6 @@
         mCounterArray.reset(true /* detachIfReset */);
         assertArrayEquals(ZEROES, mCounterArray.mCounts, "Unexpected counts");
         assertArrayEquals(ZEROES, mCounterArray.mLoadedCounts, "Unexpected loadedCounts");
-        assertArrayEquals(ZEROES, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts");
         assertArrayEquals(ZEROES, mCounterArray.mUnpluggedCounts, "Unexpected unpluggedCounts");
         verify(mTimeBase).remove(mCounterArray);
         verifyNoMoreInteractions(mTimeBase);
@@ -200,7 +232,7 @@
     }
 
     private void initializeCounterArrayWithDefaultValues() {
-        updateCounts(COUNTS, LOADED_COUNTS, PLUGGED_COUNTS, UNPLUGGED_COUNTS);
+        updateCounts(COUNTS, LOADED_COUNTS, UNPLUGGED_COUNTS);
     }
 
     private void assertArrayEquals(long[] expected, long[] actual, String msg) {
@@ -208,11 +240,9 @@
                 + ", actual: " + Arrays.toString(actual), Arrays.equals(expected, actual));
     }
 
-    private void updateCounts(long[] counts, long[] loadedCounts,
-            long[] pluggedCounts, long[] unpluggedCounts) {
-        mCounterArray.mCounts = counts;
-        mCounterArray.mLoadedCounts = loadedCounts;
-        mCounterArray.mPluggedCounts = pluggedCounts;
-        mCounterArray.mUnpluggedCounts = unpluggedCounts;
+    private void updateCounts(long[] counts, long[] loadedCounts, long[] unpluggedCounts) {
+        mCounterArray.mCounts = counts == null ? null : counts.clone();
+        mCounterArray.mLoadedCounts = loadedCounts == null ? null : loadedCounts.clone();
+        mCounterArray.mUnpluggedCounts = unpluggedCounts == null ? null : unpluggedCounts.clone();
     }
 }
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 de2fd12..6c5a2aa 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.os;
 
+import android.os.Handler;
+import android.os.Looper;
 import android.util.SparseIntArray;
 
 import java.util.ArrayList;
@@ -37,6 +39,9 @@
                 mOnBatteryTimeBase);
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
         setExternalStatsSyncLocked(new DummyExternalStatsSync());
+
+        // A no-op handler.
+        mHandler = new Handler(Looper.getMainLooper()) {};
     }
 
     MockBatteryStatsImpl() {
@@ -59,6 +64,12 @@
         return mForceOnBattery ? true : super.isOnBattery();
     }
 
+    public void forceRecordAllHistory() {
+        mHaveBatteryLevel = true;
+        mRecordingHistory = true;
+        mRecordAllHistory = true;
+    }
+
     public TimeBase getOnBatteryBackgroundTimeBase(int uid) {
         return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase;
     }
@@ -128,12 +139,19 @@
         }
 
         @Override
-        public Future<?> scheduleReadProcStateCpuTimes() {
+        public Future<?> scheduleCpuSyncDueToSettingChange() {
             return null;
         }
 
         @Override
-        public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+        public Future<?> scheduleReadProcStateCpuTimes(
+                boolean onBattery, boolean onBatteryScreenOff) {
+            return null;
+        }
+
+        @Override
+        public Future<?> scheduleCopyFromAllUidsCpuTimes(
+                boolean onBattery, boolean onBatteryScreenOff) {
             return null;
         }
 
diff --git a/core/tests/privacytests/Android.mk b/core/tests/privacytests/Android.mk
index 7b11225..7bba417 100644
--- a/core/tests/privacytests/Android.mk
+++ b/core/tests/privacytests/Android.mk
@@ -13,4 +13,7 @@
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := FrameworksPrivacyLibraryTests
 
+LOCAL_CERTIFICATE := platform
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
 include $(BUILD_PACKAGE)
diff --git a/core/tests/privacytests/AndroidTest.xml b/core/tests/privacytests/AndroidTest.xml
new file mode 100644
index 0000000..a43f48e
--- /dev/null
+++ b/core/tests/privacytests/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs Privacy Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="FrameworksPrivacyLibraryTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.coretests.privacy" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 2333fec..d2c855b 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -112,6 +112,10 @@
         <group gid="media" />
     </permission>
 
+    <permission name="android.permission.USE_RESERVED_DISK">
+        <group gid="reserved_disk" />
+    </permission>
+
     <!-- These are permissions that were mapped to gids but we need
          to keep them here until an upgrade from L to the current
          version is to be supported. These permissions are built-in
@@ -150,6 +154,7 @@
     <assign-permission name="android.permission.WAKE_LOCK" uid="audioserver" />
     <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
     <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
+    <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
 
     <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 7bb2859..4732bec 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -136,6 +136,7 @@
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.phone">
@@ -181,6 +182,7 @@
     <privapp-permissions package="com.android.providers.calendar">
         <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.contacts">
@@ -189,6 +191,7 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.downloads">
@@ -203,12 +206,14 @@
         <permission name="android.permission.ACCESS_MTP"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.telephony">
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.provision">
@@ -253,6 +258,7 @@
         <permission name="android.permission.SET_TIME"/>
         <permission name="android.permission.STATUS_BAR"/>
         <permission name="android.permission.TETHER_PRIVILEGED"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.USER_ACTIVITY"/>
         <permission name="android.permission.WRITE_APN_SETTINGS"/>
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
@@ -316,6 +322,7 @@
         <permission name="android.permission.STOP_APP_SWITCHES"/>
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
@@ -329,6 +336,7 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 
@@ -365,6 +373,7 @@
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.TETHER_PRIVILEGED"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.WRITE_DREAM_STATE"/>
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
diff --git a/data/sounds/AudioPackageGo.mk b/data/sounds/AudioPackageGo.mk
index ae742df..0296219 100644
--- a/data/sounds/AudioPackageGo.mk
+++ b/data/sounds/AudioPackageGo.mk
@@ -35,6 +35,7 @@
     $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \
     $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:system/media/audio/ringtones/Kuma.ogg \
     $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \
+    $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \
     $(LOCAL_PATH)/alarms/ogg/Argon.ogg:system/media/audio/alarms/Argon.ogg \
     $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
     $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \
diff --git a/docs/html/_shared/_reference-head-tags.html b/docs/html/_shared/_reference-head-tags.html
new file mode 100644
index 0000000..f66a7b7
--- /dev/null
+++ b/docs/html/_shared/_reference-head-tags.html
@@ -0,0 +1,8 @@
+  <!-- Added to Gerrit so we can stage reference docs directly from
+       Android builds; this file should *not* be migrated to Piper -->
+
+  <meta name="top_category" value="develop" />
+  <meta name="subcategory" value="reference" />
+  <meta name="book_path" value="{% if book_path %}{{ book_path }}{% else %}/reference/_book.yaml{% endif %}" />
+  <meta name="project_path" value="/reference/_project.yaml" />
+  <meta name="page_type" value="reference" />
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 3d65bd2..68b7ac2 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -429,7 +429,7 @@
         }
 
         /**
-         * Sets an index of the font collection.
+         * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
          *
          * Can not be used for Typeface source. build() method will return null for invalid index.
          * @param ttcIndex An index of the font collection. If the font source is not font
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index c329918..749b7594 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -819,8 +819,10 @@
         if (bitmapWidth > maxWidth || bitmapHeight > maxHeight) {
             float scale = Math.min((float) maxWidth / bitmapWidth,
                     (float) maxHeight / bitmapHeight);
-            bitmap = Bitmap.createScaledBitmap(bitmap, (int) (scale * bitmapWidth),
-                    (int) (scale * bitmapHeight), true /* filter */);
+            bitmap = Bitmap.createScaledBitmap(bitmap,
+                    Math.max(1, (int) (scale * bitmapWidth)),
+                    Math.max(1, (int) (scale * bitmapHeight)),
+                    true /* filter */);
         }
         return bitmap;
     }
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index 7c7417d..5a8fa07 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -34,7 +34,8 @@
     void setUserSelectable(String alias, boolean isUserSelectable);
 
     boolean generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
-    boolean attestKey(in String alias, in byte[] challenge, out KeymasterCertificateChain chain);
+    boolean attestKey(in String alias, in byte[] challenge, in int[] idAttestationFlags,
+            out KeymasterCertificateChain chain);
     boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain);
 
     // APIs used by CertInstaller and DevicePolicyManager
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 399dddd..fabcdf0 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -95,6 +95,16 @@
     public static final int FLAG_ENCRYPTED = 1;
 
     /**
+     * Select Software keymaster device, which as of this writing is the lowest security
+     * level available on an android device. If neither FLAG_STRONGBOX nor FLAG_SOFTWARE is provided
+     * A TEE based keymaster implementation is implied.
+     *
+     * Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h
+     * For historical reasons this corresponds to the KEYSTORE_FLAG_FALLBACK flag.
+     */
+    public static final int FLAG_SOFTWARE = 1 << 1;
+
+    /**
      * A private flag that's only available to system server to indicate that this key is part of
      * device encryption flow so it receives special treatment from keystore. For example this key
      * will not be super encrypted, and it will be stored separately under an unique UID instead
@@ -104,6 +114,16 @@
      */
     public static final int FLAG_CRITICAL_TO_DEVICE_ENCRYPTION = 1 << 3;
 
+    /**
+     * Select Strongbox keymaster device, which as of this writing the the highest security level
+     * available an android devices. If neither FLAG_STRONGBOX nor FLAG_SOFTWARE is provided
+     * A TEE based keymaster implementation is implied.
+     *
+     * Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h
+     */
+    public static final int FLAG_STRONGBOX = 1 << 4;
+
+
     // States
     public enum State { UNLOCKED, LOCKED, UNINITIALIZED };
 
@@ -440,9 +460,9 @@
         return mError;
     }
 
-    public boolean addRngEntropy(byte[] data) {
+    public boolean addRngEntropy(byte[] data, int flags) {
         try {
-            return mBinder.addRngEntropy(data) == NO_ERROR;
+            return mBinder.addRngEntropy(data, flags) == NO_ERROR;
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return false;
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
index 0811100..efee8b4 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -99,48 +99,35 @@
         }
     }
 
-    /**
-     * Performs attestation of the device's identifiers. This method returns a certificate chain
-     * whose first element contains the requested device identifiers in an extension. The device's
-     * manufacturer, model, brand, device and product are always also included in the attestation.
-     * If the device supports attestation in secure hardware, the chain will be rooted at a
-     * trustworthy CA key. Otherwise, the chain will be rooted at an untrusted certificate. See
-     * <a href="https://developer.android.com/training/articles/security-key-attestation.html">
-     * Key Attestation</a> for the format of the certificate extension.
-     * <p>
-     * Attestation will only be successful when all of the following are true:
-     * 1) The device has been set up to support device identifier attestation at the factory.
-     * 2) The user has not permanently disabled device identifier attestation.
-     * 3) You have permission to access the device identifiers you are requesting attestation for.
-     * <p>
-     * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is
-     * unsuccessful, the device may not support it in general or the user may have permanently
-     * disabled it.
-     *
-     * @param context the context to use for retrieving device identifiers.
-     * @param idTypes the types of device identifiers to attest.
-     * @param attestationChallenge a blob to include in the certificate alongside the device
-     * identifiers.
-     *
-     * @return a certificate chain containing the requested device identifiers in the first element
-     *
-     * @exception SecurityException if you are not permitted to obtain an attestation of the
-     * device's identifiers.
-     * @exception DeviceIdAttestationException if the attestation operation fails.
-     */
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    @NonNull public static X509Certificate[] attestDeviceIds(Context context,
-            @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
+    @NonNull private static KeymasterArguments prepareAttestationArgumentsForDeviceId(
+            Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
             DeviceIdAttestationException {
-        // Check method arguments, retrieve requested device IDs and prepare attestation arguments.
+        // Verify that device ID attestation types are provided.
         if (idTypes == null) {
             throw new NullPointerException("Missing id types");
         }
+
+        return prepareAttestationArguments(context, idTypes, attestationChallenge);
+    }
+
+    /**
+     * Prepares Keymaster Arguments with attestation data.
+     * @hide should only be used by KeyChain.
+     */
+    @NonNull public static KeymasterArguments prepareAttestationArguments(Context context,
+            @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
+            DeviceIdAttestationException {
+        // Check method arguments, retrieve requested device IDs and prepare attestation arguments.
         if (attestationChallenge == null) {
             throw new NullPointerException("Missing attestation challenge");
         }
         final KeymasterArguments attestArgs = new KeymasterArguments();
         attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, attestationChallenge);
+        // Return early if the caller did not request any device identifiers to be included in the
+        // attestation record.
+        if (idTypes == null) {
+            return attestArgs;
+        }
         final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
         for (int idType : idTypes) {
             idTypesSet.add(idType);
@@ -191,6 +178,44 @@
                 Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8));
         attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL,
                 Build.MODEL.getBytes(StandardCharsets.UTF_8));
+        return attestArgs;
+    }
+
+    /**
+     * Performs attestation of the device's identifiers. This method returns a certificate chain
+     * whose first element contains the requested device identifiers in an extension. The device's
+     * manufacturer, model, brand, device and product are always also included in the attestation.
+     * If the device supports attestation in secure hardware, the chain will be rooted at a
+     * trustworthy CA key. Otherwise, the chain will be rooted at an untrusted certificate. See
+     * <a href="https://developer.android.com/training/articles/security-key-attestation.html">
+     * Key Attestation</a> for the format of the certificate extension.
+     * <p>
+     * Attestation will only be successful when all of the following are true:
+     * 1) The device has been set up to support device identifier attestation at the factory.
+     * 2) The user has not permanently disabled device identifier attestation.
+     * 3) You have permission to access the device identifiers you are requesting attestation for.
+     * <p>
+     * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is
+     * unsuccessful, the device may not support it in general or the user may have permanently
+     * disabled it.
+     *
+     * @param context the context to use for retrieving device identifiers.
+     * @param idTypes the types of device identifiers to attest.
+     * @param attestationChallenge a blob to include in the certificate alongside the device
+     * identifiers.
+     *
+     * @return a certificate chain containing the requested device identifiers in the first element
+     *
+     * @exception SecurityException if you are not permitted to obtain an attestation of the
+     * device's identifiers.
+     * @exception DeviceIdAttestationException if the attestation operation fails.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @NonNull public static X509Certificate[] attestDeviceIds(Context context,
+            @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
+            DeviceIdAttestationException {
+        final KeymasterArguments attestArgs = prepareAttestationArgumentsForDeviceId(
+                context, idTypes, attestationChallenge);
 
         // Perform attestation.
         final KeymasterCertificateChain outChain = new KeymasterCertificateChain();
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 87edd69..c7a3014 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -64,7 +64,6 @@
     size_t keySize = key.size();
     std::lock_guard<std::mutex> lock(mMutex);
     if (!mInitialized) {
-        ALOGE("ShaderCache::load not initialized");
         return nullptr;
     }
 
@@ -103,7 +102,6 @@
     std::lock_guard<std::mutex> lock(mMutex);
 
     if (!mInitialized) {
-        ALOGE("ShaderCache::store not initialized");
         return;
     }
 
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 1e42425..4a0d6ee 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -122,15 +122,19 @@
 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, minikin::Bidi::LTR, paint,
-                     nullptr);
+    SkPaint glyphPaint(paint);
+    glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+    canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR,
+            glyphPaint, 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), minikin::Bidi::LTR, path, 0, 0, paint,
-                           nullptr);
+    SkPaint glyphPaint(paint);
+    glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+    canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint,
+            nullptr);
 }
 
 void TestUtils::TestTask::run() {
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index bf0aed9..38999cb 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -40,22 +40,18 @@
     }
 
     void doFrame(int frameNr) override {
-        std::unique_ptr<uint16_t[]> text =
-                TestUtils::asciiToUtf16("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
-        ssize_t textLength = 26 * 2;
+        const char* text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
         std::unique_ptr<Canvas> canvas(
                 Canvas::create_recording_canvas(container->stagingProperties().getWidth(),
                                                 container->stagingProperties().getHeight()));
 
         Paint paint;
-        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         paint.setAntiAlias(true);
         paint.setColor(Color::Black);
         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::Bidi::FORCE_LTR, paint, nullptr);
+            TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2));
         }
 
         container->setStagingDisplayList(canvas->finishRecording());
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 6bae80c..58c9980 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -36,7 +36,6 @@
         SkPaint textPaint;
         textPaint.setTextSize(dp(20));
         textPaint.setAntiAlias(true);
-        textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30));
 
         SkPoint pts[2];
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index d7ec288..fd8c252 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -83,7 +83,6 @@
         canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint);
 
         SkPaint textPaint;
-        textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500);
         textPaint.setTextSize(dp(20));
         textPaint.setAntiAlias(true);
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index 9eddc20..aa537b4 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -38,7 +38,6 @@
         card = TestUtils::createNode(
                 0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) {
                     SkPaint paint;
-                    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
                     paint.setAntiAlias(true);
                     paint.setTextSize(50);
 
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index fee0659..3befce4 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -42,10 +42,8 @@
 
         mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255));
         mBluePaint.setTextSize(padding);
-        mBluePaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0));
         mGreenPaint.setTextSize(padding);
-        mGreenPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
 
         // interleave drawText and drawRect with saveLayer ops
         for (int i = 0; i < regions; i++, top += smallRectHeight) {
@@ -54,18 +52,15 @@
             canvas.drawColor(SkColorSetARGB(255, 255, 255, 0), SkBlendMode::kSrcOver);
             std::string stri = std::to_string(i);
             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::Bidi::FORCE_LTR, mBluePaint, nullptr);
+            TestUtils::drawUtf8ToCanvas(&canvas, offscreen.c_str(), mBluePaint, bounds.fLeft,
+                    top + padding);
             canvas.restore();
 
             canvas.drawRect(bounds.fLeft, top + padding, bounds.fRight,
                             top + smallRectHeight - padding, mBluePaint);
             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::Bidi::FORCE_LTR, mGreenPaint,
-                            nullptr);
+            TestUtils::drawUtf8ToCanvas(&canvas, onscreen.c_str(), mGreenPaint, bounds.fLeft,
+                    top + smallRectHeight - padding);
         }
     }
     void doFrame(int frameNr) override {}
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index a502116..a16b1784 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -29,7 +29,6 @@
         card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props,
                                                              Canvas& canvas) {
             SkPaint paint;
-            paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
             paint.setAntiAlias(true);
             paint.setTextSize(50);
 
diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp
index c845e6c..003d8e9 100644
--- a/libs/hwui/tests/common/scenes/TvApp.cpp
+++ b/libs/hwui/tests/common/scenes/TvApp.cpp
@@ -117,7 +117,6 @@
                                          canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver);
 
                                          SkPaint paint;
-                                         paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
                                          paint.setAntiAlias(true);
                                          paint.setTextSize(24);
 
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index e56d2f8..4eb7751 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -537,7 +537,6 @@
                 canvas.save(SaveFlags::MatrixClip);
                 canvas.clipPath(&path, SkClipOp::kIntersect);
                 SkPaint paint;
-                paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
                 paint.setAntiAlias(true);
                 paint.setTextSize(50);
                 TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100);
@@ -569,7 +568,6 @@
     auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 400, 400, [](RenderProperties& props,
                                                                           RecordingCanvas& canvas) {
         SkPaint paint;
-        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         paint.setAntiAlias(true);
         paint.setTextSize(50);
         TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0);  // will be top clipped
@@ -603,7 +601,6 @@
                 textPaint.setAntiAlias(true);
                 textPaint.setTextSize(20);
                 textPaint.setFlags(textPaint.getFlags() | SkPaint::kStrikeThruText_ReserveFlag);
-                textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
                 for (int i = 0; i < LOOPS; i++) {
                     TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
                 }
@@ -654,7 +651,6 @@
     auto node = TestUtils::createNode<RecordingCanvas>(
             0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) {
                 SkPaint paint;
-                paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
                 paint.setAntiAlias(true);
                 paint.setTextSize(50);
                 paint.setStrokeWidth(10);
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 5aae15f..8a9e34f 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -175,7 +175,6 @@
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setTextSize(20);
-        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
     });
 
@@ -196,7 +195,6 @@
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setTextSize(20);
-        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         for (int i = 0; i < 2; i++) {
             for (int j = 0; j < 2; j++) {
                 uint32_t flags = paint.getFlags();
@@ -238,7 +236,6 @@
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setTextSize(20);
-        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         paint.setTextAlign(SkPaint::kLeft_Align);
         TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
         paint.setTextAlign(SkPaint::kCenter_Align);
@@ -805,9 +802,7 @@
         Paint paint;
         paint.setAntiAlias(true);
         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::Bidi::FORCE_LTR, paint, NULL);
+        TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25);
     });
 
     int count = 0;
@@ -829,9 +824,7 @@
         paint.setColor(SK_ColorWHITE);
         paint.setAntiAlias(true);
         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::Bidi::FORCE_LTR, paint, NULL);
+        TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25);
     });
     Properties::enableHighContrastText = false;
 
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 4138f59..1d7dc3d 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -36,7 +36,6 @@
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setTextSize(20);
-        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         static const char* text = "testing text bounds";
 
         // draw text directly into Recording canvas
diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
index 78d75d6..92d05e4 100644
--- a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
+++ b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
@@ -29,6 +29,7 @@
 RENDERTHREAD_OPENGL_PIPELINE_TEST(TextDropShadowCache, addRemove) {
     SkPaint paint;
     paint.setTextSize(20);
+    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
 
     GammaFontRenderer gammaFontRenderer;
     FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
diff --git a/libs/incident/proto/android/section.proto b/libs/incident/proto/android/section.proto
index d268cf4..49bfe1e 100644
--- a/libs/incident/proto/android/section.proto
+++ b/libs/incident/proto/android/section.proto
@@ -37,6 +37,9 @@
 
     // incidentd calls dumpsys for annotated field
     SECTION_DUMPSYS = 3;
+
+    // incidentd calls logs for annotated field
+    SECTION_LOG = 4;
 }
 
 message SectionFlags {
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 8f341a8..0990dcc 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -71,6 +71,7 @@
     void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
 
     int getGnssYearOfHardware();
+    String getGnssHardwareModelName();
 
     int getGnssBatchSize(String packageName);
     boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName);
@@ -78,6 +79,7 @@
     boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName);
     void flushGnssBatch(String packageName);
     boolean stopGnssBatch();
+    boolean injectLocation(in Location location);
 
     // --- deprecated ---
     List<String> getAllProviders();
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d15ab33..f0b2774 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -19,6 +19,7 @@
 import com.android.internal.location.ProviderProperties;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -41,6 +42,7 @@
 
 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.LOCATION_HARDWARE;
 
 /**
  * This class provides access to the system location services.  These
@@ -225,6 +227,12 @@
     public static final String HIGH_POWER_REQUEST_CHANGE_ACTION =
         "android.location.HIGH_POWER_REQUEST_CHANGE";
 
+    /**
+     * The value returned by {@link LocationManager#getGnssHardwareModelName()} when the hardware
+     * does not support providing the actual value.
+     */
+    public static final String GNSS_HARDWARE_MODEL_NAME_UNKNOWN = "Model Name Unknown";
+
     // Map from LocationListeners to their associated ListenerTransport objects
     private HashMap<LocationListener,ListenerTransport> mListeners =
         new HashMap<LocationListener,ListenerTransport>();
@@ -875,6 +883,34 @@
         requestLocationUpdates(request, null, null, intent);
     }
 
+    /**
+     * Set the last known location with a new location.
+     *
+     * <p>A privileged client can inject a {@link Location} if it has a better estimate of what
+     * the recent location is.  This is especially useful when the device boots up and the GPS
+     * chipset is in the process of getting the first fix.  If the client has cached the location,
+     * it can inject the {@link Location}, so if an app requests for a {@link Location} from {@link
+     * #getLastKnownLocation(String)}, the location information is still useful before getting
+     * the first fix.</p>
+     *
+     * <p> Useful in products like Auto.
+     *
+     * @param newLocation newly available {@link Location} object
+     * @return true if update was successful, false if not
+     *
+     * @throws SecurityException if no suitable permission is present
+     *
+     * @hide
+     */
+    @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_FINE_LOCATION})
+    public boolean injectLocation(Location newLocation) {
+        try {
+            return mService.injectLocation(newLocation);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private ListenerTransport wrapListener(LocationListener listener, Looper looper) {
         if (listener == null) return null;
         synchronized (mListeners) {
@@ -1969,11 +2005,10 @@
     }
 
     /**
-     * Returns the system information of the GPS hardware.
-     * May return 0 if GPS hardware is earlier than 2016.
-     * @hide
+     * Returns the model year of the GNSS hardware and software build.
+     *
+     * May return 0 if the model year is less than 2016.
      */
-    @TestApi
     public int getGnssYearOfHardware() {
         try {
             return mService.getGnssYearOfHardware();
@@ -1983,6 +2018,22 @@
     }
 
     /**
+     * Returns the Model Name (including Vendor and Hardware/Software Version) of the GNSS hardware
+     * driver.
+     *
+     * Will return {@link LocationManager#GNSS_HARDWARE_MODEL_NAME_UNKNOWN} when the GNSS hardware
+     * abstraction layer does not support providing this value.
+     */
+    @NonNull
+    public String getGnssHardwareModelName() {
+        try {
+            return mService.getGnssHardwareModelName();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the batch size (in number of Location objects) that are supported by the batching
      * interface.
      *
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 19d467a..1a97b6b 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -16,9 +16,12 @@
 
 package android.media;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.util.SparseIntArray;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.TreeSet;
 
 /**
@@ -120,6 +123,57 @@
      */
     public static final int TYPE_USB_HEADSET       = 22;
 
+    /** @hide */
+    @IntDef(flag = false, prefix = "TYPE", value = {
+            TYPE_BUILTIN_EARPIECE,
+            TYPE_BUILTIN_SPEAKER,
+            TYPE_WIRED_HEADSET,
+            TYPE_WIRED_HEADPHONES,
+            TYPE_BLUETOOTH_SCO,
+            TYPE_BLUETOOTH_A2DP,
+            TYPE_HDMI,
+            TYPE_DOCK,
+            TYPE_USB_ACCESSORY,
+            TYPE_USB_DEVICE,
+            TYPE_USB_HEADSET,
+            TYPE_TELEPHONY,
+            TYPE_LINE_ANALOG,
+            TYPE_HDMI_ARC,
+            TYPE_LINE_DIGITAL,
+            TYPE_FM,
+            TYPE_AUX_LINE,
+            TYPE_IP }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AudioDeviceTypeOut {}
+
+    /** @hide */
+    /*package*/ static boolean isValidAudioDeviceTypeOut(int type) {
+        switch (type) {
+            case TYPE_BUILTIN_EARPIECE:
+            case TYPE_BUILTIN_SPEAKER:
+            case TYPE_WIRED_HEADSET:
+            case TYPE_WIRED_HEADPHONES:
+            case TYPE_BLUETOOTH_SCO:
+            case TYPE_BLUETOOTH_A2DP:
+            case TYPE_HDMI:
+            case TYPE_DOCK:
+            case TYPE_USB_ACCESSORY:
+            case TYPE_USB_DEVICE:
+            case TYPE_USB_HEADSET:
+            case TYPE_TELEPHONY:
+            case TYPE_LINE_ANALOG:
+            case TYPE_HDMI_ARC:
+            case TYPE_LINE_DIGITAL:
+            case TYPE_FM:
+            case TYPE_AUX_LINE:
+            case TYPE_IP:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     private final AudioDevicePort mPort;
 
     AudioDeviceInfo(AudioDevicePort port) {
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index 6d9c5e2..5d0c8e2 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -130,13 +130,11 @@
         dest.writeInt(mSdkTarget);
     }
 
-    @SystemApi
     @Override
     public int hashCode() {
         return Objects.hash(mAttributes, mClientUid, mClientId, mPackageName, mGainRequest, mFlags);
     }
 
-    @SystemApi
     @Override
     public boolean equals(Object obj) {
         if (this == obj)
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 58976ca..913b5e8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -52,6 +53,8 @@
 import android.util.Slog;
 import android.view.KeyEvent;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -911,13 +914,28 @@
 
     /**
      * Returns the minimum volume index for a particular stream.
-     *
+     * @param streamType The stream type whose minimum volume index is returned. Must be one of
+     *     {@link #STREAM_VOICE_CALL}, {@link #STREAM_SYSTEM},
+     *     {@link #STREAM_RING}, {@link #STREAM_MUSIC}, {@link #STREAM_ALARM},
+     *     {@link #STREAM_NOTIFICATION}, {@link #STREAM_DTMF} or {@link #STREAM_ACCESSIBILITY}.
+     * @return The minimum valid volume index for the stream.
+     * @see #getStreamVolume(int)
+     */
+    public int getStreamMinVolume(int streamType) {
+        if (!isPublicStreamType(streamType)) {
+            throw new IllegalArgumentException("Invalid stream type " + streamType);
+        }
+        return getStreamMinVolumeInt(streamType);
+    }
+
+    /**
+     * @hide
+     * Same as {@link #getStreamMinVolume(int)} but without the check on the public stream type.
      * @param streamType The stream type whose minimum volume index is returned.
      * @return The minimum valid volume index for the stream.
      * @see #getStreamVolume(int)
-     * @hide
      */
-    public int getStreamMinVolume(int streamType) {
+    public int getStreamMinVolumeInt(int streamType) {
         final IAudioService service = getService();
         try {
             return service.getStreamMinVolume(streamType);
@@ -943,6 +961,72 @@
         }
     }
 
+    // keep in sync with frameworks/av/services/audiopolicy/common/include/Volume.h
+    private static final float VOLUME_MIN_DB = -758.0f;
+
+    /** @hide */
+    @IntDef(flag = false, prefix = "STREAM", value = {
+            STREAM_VOICE_CALL,
+            STREAM_SYSTEM,
+            STREAM_RING,
+            STREAM_MUSIC,
+            STREAM_ALARM,
+            STREAM_NOTIFICATION,
+            STREAM_DTMF,
+            STREAM_ACCESSIBILITY }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PublicStreamTypes {}
+
+    /**
+     * Returns the volume in dB (decibel) for the given stream type at the given volume index, on
+     * the given type of audio output device.
+     * @param streamType stream type for which the volume is queried.
+     * @param index the volume index for which the volume is queried. The index value must be
+     *     between the minimum and maximum index values for the given stream type (see
+     *     {@link #getStreamMinVolume(int)} and {@link #getStreamMaxVolume(int)}).
+     * @param deviceType the type of audio output device for which volume is queried.
+     * @return a volume expressed in dB.
+     *     A negative value indicates the audio signal is attenuated. A typical maximum value
+     *     at the maximum volume index is 0 dB (no attenuation nor amplification). Muting is
+     *     reflected by a value of {@link Float#NEGATIVE_INFINITY}.
+     */
+    public float getStreamVolumeDb(@PublicStreamTypes int streamType, int index,
+            @AudioDeviceInfo.AudioDeviceTypeOut int deviceType) {
+        if (!isPublicStreamType(streamType)) {
+            throw new IllegalArgumentException("Invalid stream type " + streamType);
+        }
+        if (index > getStreamMaxVolume(streamType) || index < getStreamMinVolume(streamType)) {
+            throw new IllegalArgumentException("Invalid stream volume index " + index);
+        }
+        if (!AudioDeviceInfo.isValidAudioDeviceTypeOut(deviceType)) {
+            throw new IllegalArgumentException("Invalid audio output device type " + deviceType);
+        }
+        final float gain = AudioSystem.getStreamVolumeDB(streamType, index,
+                AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType));
+        if (gain <= VOLUME_MIN_DB) {
+            return Float.NEGATIVE_INFINITY;
+        } else {
+            return gain;
+        }
+    }
+
+    private static boolean isPublicStreamType(int streamType) {
+        switch (streamType) {
+            case STREAM_VOICE_CALL:
+            case STREAM_SYSTEM:
+            case STREAM_RING:
+            case STREAM_MUSIC:
+            case STREAM_ALARM:
+            case STREAM_NOTIFICATION:
+            case STREAM_DTMF:
+            case STREAM_ACCESSIBILITY:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     /**
      * Get last audible volume before stream was muted.
      *
@@ -1551,6 +1635,21 @@
     }
 
     /**
+     * Broadcast Action: microphone muting state changed.
+     *
+     * You <em>cannot</em> receive this through components declared
+     * in manifests, only by explicitly registering for it with
+     * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+     * Context.registerReceiver()}.
+     *
+     * <p>The intent has no extra values, use {@link #isMicrophoneMute} to check whether the
+     * microphone is muted.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MICROPHONE_MUTE_CHANGED =
+            "android.media.action.MICROPHONE_MUTE_CHANGED";
+
+    /**
      * Sets the audio mode.
      * <p>
      * The audio mode encompasses audio routing AND the behavior of
diff --git a/media/java/android/media/update/ApiLoader.java b/media/java/android/media/update/ApiLoader.java
new file mode 100644
index 0000000..b57e02d
--- /dev/null
+++ b/media/java/android/media/update/ApiLoader.java
@@ -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.
+ */
+
+package android.media.update;
+
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+/**
+ * @hide
+ */
+public final class ApiLoader {
+    private static Object sMediaLibrary;
+
+    private static final String UPDATE_PACKAGE = "com.android.media.update";
+    private static final String UPDATE_CLASS = "com.android.media.update.ApiFactory";
+    private static final String UPDATE_METHOD = "initialize";
+
+    private ApiLoader() { }
+
+    public static StaticProvider getProvider(Context context) {
+        try {
+            return (StaticProvider) getMediaLibraryImpl(context);
+        } catch (NameNotFoundException | ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // TODO This method may do I/O; Ensure it does not violate (emit warnings in) strict mode.
+    private static synchronized Object getMediaLibraryImpl(Context appContext)
+            throws NameNotFoundException, ReflectiveOperationException {
+        if (sMediaLibrary != null) return sMediaLibrary;
+
+        // TODO Dynamically find the package name
+        Context libContext = appContext.createPackageContext(UPDATE_PACKAGE,
+                Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+        sMediaLibrary = libContext.getClassLoader()
+                .loadClass(UPDATE_CLASS)
+                .getMethod(UPDATE_METHOD, Context.class)
+                .invoke(null, appContext);
+        return sMediaLibrary;
+    }
+}
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
new file mode 100644
index 0000000..71fbd08
--- /dev/null
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.media.update;
+
+import android.annotation.SystemApi;
+import android.media.session.MediaController;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+/**
+ * Interface for connecting the public API to an updatable implementation.
+ *
+ * Each instance object is connected to one corresponding updatable object which implements the
+ * runtime behavior of that class. There should a corresponding provider method for all public
+ * methods.
+ *
+ * All methods behave as per their namesake in the public API.
+ *
+ * @see android.widget.MediaController2
+ *
+ * @hide
+ */
+// TODO @SystemApi
+public interface MediaController2Provider extends ViewProvider {
+    void setController_impl(MediaController controller);
+    void setAnchorView_impl(View view);
+    void show_impl();
+    void show_impl(int timeout);
+    boolean isShowing_impl();
+    void hide_impl();
+    void setPrevNextListeners_impl(OnClickListener next, OnClickListener prev);
+    void showCCButton_impl();
+    boolean isPlaying_impl();
+    int getCurrentPosition_impl();
+    int getBufferPercentage_impl();
+    boolean canPause_impl();
+    boolean canSeekBackward_impl();
+    boolean canSeekForward_impl();
+    void showSubtitle_impl();
+    void hideSubtitle_impl();
+}
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
new file mode 100644
index 0000000..19f01c2
--- /dev/null
+++ b/media/java/android/media/update/StaticProvider.java
@@ -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.
+ */
+
+package android.media.update;
+
+import android.annotation.SystemApi;
+import android.widget.MediaController2;
+
+/**
+ * Interface for connecting the public API to an updatable implementation.
+ *
+ * This interface provides access to constructors and static methods that are otherwise not directly
+ * accessible via an implementation object.
+ *
+ * @hide
+ */
+// TODO @SystemApi
+public interface StaticProvider {
+    MediaController2Provider createMediaController2(
+            MediaController2 instance, ViewProvider superProvider);
+}
diff --git a/media/java/android/media/update/ViewProvider.java b/media/java/android/media/update/ViewProvider.java
new file mode 100644
index 0000000..bc8f203
--- /dev/null
+++ b/media/java/android/media/update/ViewProvider.java
@@ -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.
+ */
+
+package android.media.update;
+
+import android.annotation.SystemApi;
+import android.graphics.Canvas;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * Interface for connecting the public API to an updatable implementation.
+ *
+ * Each instance object is connected to one corresponding updatable object which implements the
+ * runtime behavior of that class. There should a corresponding provider method for all public
+ * methods.
+ *
+ * All methods behave as per their namesake in the public API.
+ *
+ * @see android.view.View
+ *
+ * @hide
+ */
+// TODO @SystemApi
+public interface ViewProvider {
+    // TODO Add more (all?) methods from View
+    void onAttachedToWindow_impl();
+    void onDetachedFromWindow_impl();
+    void onLayout_impl(boolean changed, int left, int top, int right, int bottom);
+    void draw_impl(Canvas canvas);
+    CharSequence getAccessibilityClassName_impl();
+    boolean onTouchEvent_impl(MotionEvent ev);
+    boolean onTrackballEvent_impl(MotionEvent ev);
+    boolean onKeyDown_impl(int keyCode, KeyEvent event);
+    void onFinishInflate_impl();
+    boolean dispatchKeyEvent_impl(KeyEvent event);
+    void setEnabled_impl(boolean enabled);
+}
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 0704e35..61164e0 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -29,13 +29,6 @@
     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.
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index f844cc1..2a82fc9 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.app.usage.StorageStatsManager;
 import android.content.ContentResolver;
-import android.content.Context;
 import android.content.UriPermission;
 import android.database.Cursor;
 import android.database.MatrixCursor;
@@ -28,7 +27,9 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.IBinder;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.storage.DiskInfo;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
@@ -95,6 +96,7 @@
     private static final String ROOT_ID_HOME = "home";
 
     private StorageManager mStorageManager;
+    private UserManager mUserManager;
 
     private final Object mRootsLock = new Object();
 
@@ -105,12 +107,35 @@
     public boolean onCreate() {
         super.onCreate(DEFAULT_DOCUMENT_PROJECTION);
 
-        mStorageManager = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
+        mStorageManager = getContext().getSystemService(StorageManager.class);
+        mUserManager = getContext().getSystemService(UserManager.class);
 
         updateVolumes();
         return true;
     }
 
+    private void enforceShellRestrictions() {
+        if (UserHandle.getCallingAppId() == android.os.Process.SHELL_UID
+                && mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
+            throw new SecurityException(
+                    "Shell user cannot access files for user " + UserHandle.myUserId());
+        }
+    }
+
+    @Override
+    protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken)
+            throws SecurityException {
+        enforceShellRestrictions();
+        return super.enforceReadPermissionInner(uri, callingPkg, callerToken);
+    }
+
+    @Override
+    protected int enforceWritePermissionInner(Uri uri, String callingPkg, IBinder callerToken)
+            throws SecurityException {
+        enforceShellRestrictions();
+        return super.enforceWritePermissionInner(uri, callingPkg, callerToken);
+    }
+
     public void updateVolumes() {
         synchronized (mRootsLock) {
             updateVolumesLocked();
diff --git a/packages/PrintSpooler/Android.mk b/packages/PrintSpooler/Android.mk
index 19e44e3..6feb8a6 100644
--- a/packages/PrintSpooler/Android.mk
+++ b/packages/PrintSpooler/Android.mk
@@ -18,17 +18,26 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res frameworks/support/v7/recyclerview/res
-LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages android.support.v7.recyclerview
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_USE_AAPT2 := true
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += \
-        src/com/android/printspooler/renderer/IPdfRenderer.aidl \
-        src/com/android/printspooler/renderer/IPdfEditor.aidl
+    src/com/android/printspooler/renderer/IPdfRenderer.aidl \
+    src/com/android/printspooler/renderer/IPdfEditor.aidl
 
 LOCAL_PACKAGE_NAME := PrintSpooler
 
 LOCAL_JNI_SHARED_LIBRARIES := libprintspooler_jni
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v7-recyclerview
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    android-support-v7-recyclerview \
+    android-support-compat \
+    android-support-media-compat \
+    android-support-core-utils \
+    android-support-core-ui \
+    android-support-fragment
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-annotations
 
 include $(BUILD_PACKAGE)
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java
index 7a80a8b..24cf218 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java
@@ -21,6 +21,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+
 import com.android.printspooler.R;
 
 /**
@@ -126,6 +127,7 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         final int childCount = getChildCount();
         final int rowCount = childCount / mColumnCount + childCount % mColumnCount;
+        final boolean isLayoutRtl = isLayoutRtl();
 
         int cellStart = getPaddingStart();
         int cellTop = getPaddingTop();
@@ -134,7 +136,13 @@
             int rowHeight = 0;
 
             for (int col = 0; col < mColumnCount; col++) {
-                final int childIndex = row * mColumnCount + col;
+                final int childIndex;
+                if (isLayoutRtl) {
+                    // if RTL, layout the right most child first
+                    childIndex = row * mColumnCount + (mColumnCount - col - 1);
+                } else {
+                    childIndex = row * mColumnCount + col;
+                }
 
                 if (childIndex >= childCount) {
                     break;
@@ -148,14 +156,14 @@
 
                 MarginLayoutParams childParams = (MarginLayoutParams) child.getLayoutParams();
 
-                final int childLeft = cellStart + childParams.getMarginStart();
+                final int childStart = cellStart + childParams.getMarginStart();
                 final int childTop = cellTop + childParams.topMargin;
-                final int childRight = childLeft + child.getMeasuredWidth();
+                final int childEnd = childStart + child.getMeasuredWidth();
                 final int childBottom = childTop + child.getMeasuredHeight();
 
-                child.layout(childLeft, childTop, childRight, childBottom);
+                child.layout(childStart, childTop, childEnd, childBottom);
 
-                cellStart = childRight + childParams.getMarginEnd();
+                cellStart = childEnd + childParams.getMarginEnd();
 
                 rowHeight = Math.max(rowHeight, child.getMeasuredHeight()
                         + childParams.topMargin + childParams.bottomMargin);
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index d82db41..b035b70 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -510,8 +510,6 @@
     <string name="bluetooth_show_devices_without_names">Show Bluetooth devices without names</string>
     <!-- Setting Checkbox title for disabling Bluetooth absolute volume -->
     <string name="bluetooth_disable_absolute_volume">Disable absolute volume</string>
-    <!-- Setting Checkbox title for enabling Bluetooth inband ringing -->
-    <string name="bluetooth_enable_inband_ringing">Enable in-band ringing</string>
 
     <!-- UI debug setting: Select Bluetooth AVRCP Version -->
     <string name="bluetooth_select_avrcp_version_string">Bluetooth AVRCP Version</string>
@@ -604,8 +602,6 @@
     <string name="bluetooth_show_devices_without_names_summary">Bluetooth devices without names (MAC addresses only) will be displayed</string>
     <!-- Summary of checkbox for disabling Bluetooth absolute volume -->
     <string name="bluetooth_disable_absolute_volume_summary">Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control.</string>
-    <!-- Summary of checkbox for enabling Bluetooth inband ringing -->
-    <string name="bluetooth_enable_inband_ringing_summary">Allow ringtones on the phone to be played on Bluetooth headsets</string>
 
     <!-- Title of checkbox setting that enables the terminal app. [CHAR LIMIT=32] -->
     <string name="enable_terminal_title">Local terminal</string>
diff --git a/packages/SettingsLib/res/values/styles_support_preference.xml b/packages/SettingsLib/res/values/styles_support_preference.xml
index cf9f3c6..59de6c9 100644
--- a/packages/SettingsLib/res/values/styles_support_preference.xml
+++ b/packages/SettingsLib/res/values/styles_support_preference.xml
@@ -66,6 +66,14 @@
         <item name="singleLineTitle">false</item>
     </style>
 
+    <!-- CheckBox Preferences -->
+    <style name="Preference.CheckBoxPreference.SettingsBase" parent="@style/Preference.CheckBoxPreference.Material">
+        <item name="allowDividerAbove">false</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">true</item>
+        <item name="singleLineTitle">false</item>
+    </style>
+
     <!-- EditText Preferences -->
     <style name="Preference.EditTextPreference.SettingsBase"
            parent="@style/Preference.DialogPreference.EditTextPreference.Material">
@@ -86,6 +94,7 @@
         <item name="editTextPreferenceStyle">@style/Preference.EditTextPreference.SettingsBase</item>
         <item name="footerPreferenceStyle">@style/Preference.FooterPreference.SettingsBase</item>
         <item name="switchPreferenceStyle">@style/Preference.SwitchPreference.SettingsBase</item>
+        <item name="checkBoxPreferenceStyle">@style/Preference.CheckBoxPreference.SettingsBase</item>
         <item name="dropdownPreferenceStyle">@style/Preference.DropdownPreference.SettingsBase</item>
     </style>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
index 1993b45..3732471 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -73,6 +73,7 @@
                 mCachedDeviceManager, context);
         mProfileManager = new LocalBluetoothProfileManager(context,
                 mLocalAdapter, mCachedDeviceManager, mEventManager);
+        mEventManager.readPairedDevices();
     }
 
     public LocalBluetoothAdapter getBluetoothAdapter() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 88f0d2b..d14b53b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -69,4 +69,12 @@
             pref.setVisible(isVisible);
         }
     }
+
+
+    /**
+     * @return a String for the summary of the preference.
+     */
+    public String getSummary() {
+        return null;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 58f1226..754b881 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -622,6 +622,14 @@
         return mRssi;
     }
 
+    public ConcurrentHashMap<String, ScanResult> getScanResults() {
+        return mScanResultCache;
+    }
+
+    public Map<String, TimestampedScoredNetwork> getScoredNetworkCache() {
+        return mScoredNetworkCache;
+    }
+
     /**
      * Updates {@link #mRssi}.
      *
@@ -845,41 +853,8 @@
         }
 
         if (WifiTracker.sVerboseLogging) {
-            // Add RSSI/band information for this config, what was seen up to 6 seconds ago
-            // verbose WiFi Logging is only turned on thru developers settings
-            if (isActive() && mInfo != null) {
-                summary.append(" f=" + Integer.toString(mInfo.getFrequency()));
-            }
-            summary.append(" " + getVisibilityStatus());
-            if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
-                summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
-                if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
-                    long now = System.currentTimeMillis();
-                    long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000;
-                    long sec = diff%60; //seconds
-                    long min = (diff/60)%60; //minutes
-                    long hour = (min/60)%60; //hours
-                    summary.append(", ");
-                    if (hour > 0) summary.append(Long.toString(hour) + "h ");
-                    summary.append( Long.toString(min) + "m ");
-                    summary.append( Long.toString(sec) + "s ");
-                }
-                summary.append(")");
-            }
-
-            if (config != null) {
-                WifiConfiguration.NetworkSelectionStatus networkStatus =
-                        config.getNetworkSelectionStatus();
-                for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
-                        index < WifiConfiguration.NetworkSelectionStatus
-                        .NETWORK_SELECTION_DISABLED_MAX; index++) {
-                    if (networkStatus.getDisableReasonCounter(index) != 0) {
-                        summary.append(" " + WifiConfiguration.NetworkSelectionStatus
-                                .getNetworkDisableReasonString(index) + "="
-                                + networkStatus.getDisableReasonCounter(index));
-                    }
-                }
-            }
+            evictOldScanResults();
+            summary.append(WifiUtils.buildLoggingSummary(this, config));
         }
 
         // If Speed label and summary are both present, use the preference combination to combine
@@ -897,127 +872,6 @@
     }
 
     /**
-     * Returns the visibility status of the WifiConfiguration.
-     *
-     * @return autojoin debugging information
-     * TODO: use a string formatter
-     * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
-     * For instance [-40,5/-30,2]
-     */
-    private String getVisibilityStatus() {
-        StringBuilder visibility = new StringBuilder();
-        StringBuilder scans24GHz = new StringBuilder();
-        StringBuilder scans5GHz = new StringBuilder();
-        String bssid = null;
-
-        long now = System.currentTimeMillis();
-
-        if (isActive() && mInfo != null) {
-            bssid = mInfo.getBSSID();
-            if (bssid != null) {
-                visibility.append(" ").append(bssid);
-            }
-            visibility.append(" rssi=").append(mInfo.getRssi());
-            visibility.append(" ");
-            visibility.append(" score=").append(mInfo.score);
-            if (mSpeed != Speed.NONE) {
-                visibility.append(" speed=").append(getSpeedLabel());
-            }
-            visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
-            visibility.append(String.format("%.1f,", mInfo.txRetriesRate));
-            visibility.append(String.format("%.1f ", mInfo.txBadRate));
-            visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate));
-        }
-
-        int maxRssi5 = WifiConfiguration.INVALID_RSSI;
-        int maxRssi24 = WifiConfiguration.INVALID_RSSI;
-        final int maxDisplayedScans = 4;
-        int num5 = 0; // number of scanned BSSID on 5GHz band
-        int num24 = 0; // number of scanned BSSID on 2.4Ghz band
-        int numBlackListed = 0;
-        evictOldScanResults();
-
-        // TODO: sort list by RSSI or age
-        long nowMs = SystemClock.elapsedRealtime();
-        for (ScanResult result : mScanResultCache.values()) {
-            if (result.frequency >= LOWER_FREQ_5GHZ
-                    && result.frequency <= HIGHER_FREQ_5GHZ) {
-                // Strictly speaking: [4915, 5825]
-                num5++;
-
-                if (result.level > maxRssi5) {
-                    maxRssi5 = result.level;
-                }
-                if (num5 <= maxDisplayedScans) {
-                    scans5GHz.append(verboseScanResultSummary(result, bssid, nowMs));
-                }
-            } else if (result.frequency >= LOWER_FREQ_24GHZ
-                    && result.frequency <= HIGHER_FREQ_24GHZ) {
-                // Strictly speaking: [2412, 2482]
-                num24++;
-
-                if (result.level > maxRssi24) {
-                    maxRssi24 = result.level;
-                }
-                if (num24 <= maxDisplayedScans) {
-                    scans24GHz.append(verboseScanResultSummary(result, bssid, nowMs));
-                }
-            }
-        }
-        visibility.append(" [");
-        if (num24 > 0) {
-            visibility.append("(").append(num24).append(")");
-            if (num24 > maxDisplayedScans) {
-                visibility.append("max=").append(maxRssi24).append(",");
-            }
-            visibility.append(scans24GHz.toString());
-        }
-        visibility.append(";");
-        if (num5 > 0) {
-            visibility.append("(").append(num5).append(")");
-            if (num5 > maxDisplayedScans) {
-                visibility.append("max=").append(maxRssi5).append(",");
-            }
-            visibility.append(scans5GHz.toString());
-        }
-        if (numBlackListed > 0)
-            visibility.append("!").append(numBlackListed);
-        visibility.append("]");
-
-        return visibility.toString();
-    }
-
-    @VisibleForTesting
-    /* package */ String verboseScanResultSummary(ScanResult result, String bssid, long nowMs) {
-        StringBuilder stringBuilder = new StringBuilder();
-        stringBuilder.append(" \n{").append(result.BSSID);
-        if (result.BSSID.equals(bssid)) {
-            stringBuilder.append("*");
-        }
-        stringBuilder.append("=").append(result.frequency);
-        stringBuilder.append(",").append(result.level);
-        int speed = getSpecificApSpeed(result);
-        if (speed != Speed.NONE) {
-            stringBuilder.append(",")
-                    .append(getSpeedLabel(speed));
-        }
-        int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000;
-        stringBuilder.append(",").append(ageSeconds).append("s");
-        stringBuilder.append("}");
-        return stringBuilder.toString();
-    }
-
-    @Speed private int getSpecificApSpeed(ScanResult result) {
-        TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID);
-        if (timedScore == null) {
-            return Speed.NONE;
-        }
-        // For debugging purposes we may want to use mRssi rather than result.level as the average
-        // speed wil be determined by mRssi
-        return timedScore.getScore().calculateBadge(result.level);
-    }
-
-    /**
      * Return whether this is the active connection.
      * For ephemeral connections (networkId is invalid), this returns false if the network is
      * disconnected.
@@ -1275,7 +1129,7 @@
     }
 
     @Nullable
-    private String getSpeedLabel(@Speed int speed) {
+    String getSpeedLabel(@Speed int speed) {
         switch (speed) {
             case Speed.VERY_FAST:
                 return mContext.getString(R.string.speed_label_very_fast);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
new file mode 100644
index 0000000..932c6fd
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wifi;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.os.SystemClock;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.Map;
+
+public class WifiUtils {
+
+    public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) {
+        final StringBuilder summary = new StringBuilder();
+        final WifiInfo info = accessPoint.getInfo();
+        // Add RSSI/band information for this config, what was seen up to 6 seconds ago
+        // verbose WiFi Logging is only turned on thru developers settings
+        if (accessPoint.isActive() && info != null) {
+            summary.append(" f=" + Integer.toString(info.getFrequency()));
+        }
+        summary.append(" " + getVisibilityStatus(accessPoint));
+        if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
+            summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
+            if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
+                long now = System.currentTimeMillis();
+                long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000;
+                long sec = diff % 60; //seconds
+                long min = (diff / 60) % 60; //minutes
+                long hour = (min / 60) % 60; //hours
+                summary.append(", ");
+                if (hour > 0) summary.append(Long.toString(hour) + "h ");
+                summary.append(Long.toString(min) + "m ");
+                summary.append(Long.toString(sec) + "s ");
+            }
+            summary.append(")");
+        }
+
+        if (config != null) {
+            WifiConfiguration.NetworkSelectionStatus networkStatus =
+                    config.getNetworkSelectionStatus();
+            for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
+                    index < WifiConfiguration.NetworkSelectionStatus
+                            .NETWORK_SELECTION_DISABLED_MAX; index++) {
+                if (networkStatus.getDisableReasonCounter(index) != 0) {
+                    summary.append(" " + WifiConfiguration.NetworkSelectionStatus
+                            .getNetworkDisableReasonString(index) + "="
+                            + networkStatus.getDisableReasonCounter(index));
+                }
+            }
+        }
+
+        return summary.toString();
+    }
+
+    /**
+     * Returns the visibility status of the WifiConfiguration.
+     *
+     * @return autojoin debugging information
+     * TODO: use a string formatter
+     * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
+     * For instance [-40,5/-30,2]
+     */
+    private static String getVisibilityStatus(AccessPoint accessPoint) {
+        final WifiInfo info = accessPoint.getInfo();
+        StringBuilder visibility = new StringBuilder();
+        StringBuilder scans24GHz = new StringBuilder();
+        StringBuilder scans5GHz = new StringBuilder();
+        String bssid = null;
+
+        if (accessPoint.isActive() && info != null) {
+            bssid = info.getBSSID();
+            if (bssid != null) {
+                visibility.append(" ").append(bssid);
+            }
+            visibility.append(" rssi=").append(info.getRssi());
+            visibility.append(" ");
+            visibility.append(" score=").append(info.score);
+            if (accessPoint.getSpeed() != AccessPoint.Speed.NONE) {
+                visibility.append(" speed=").append(accessPoint.getSpeedLabel());
+            }
+            visibility.append(String.format(" tx=%.1f,", info.txSuccessRate));
+            visibility.append(String.format("%.1f,", info.txRetriesRate));
+            visibility.append(String.format("%.1f ", info.txBadRate));
+            visibility.append(String.format("rx=%.1f", info.rxSuccessRate));
+        }
+
+        int maxRssi5 = WifiConfiguration.INVALID_RSSI;
+        int maxRssi24 = WifiConfiguration.INVALID_RSSI;
+        final int maxDisplayedScans = 4;
+        int num5 = 0; // number of scanned BSSID on 5GHz band
+        int num24 = 0; // number of scanned BSSID on 2.4Ghz band
+        int numBlackListed = 0;
+
+        // TODO: sort list by RSSI or age
+        long nowMs = SystemClock.elapsedRealtime();
+        for (ScanResult result : accessPoint.getScanResults().values()) {
+            if (result.frequency >= AccessPoint.LOWER_FREQ_5GHZ
+                    && result.frequency <= AccessPoint.HIGHER_FREQ_5GHZ) {
+                // Strictly speaking: [4915, 5825]
+                num5++;
+
+                if (result.level > maxRssi5) {
+                    maxRssi5 = result.level;
+                }
+                if (num5 <= maxDisplayedScans) {
+                    scans5GHz.append(
+                            verboseScanResultSummary(accessPoint, result, bssid,
+                                    nowMs));
+                }
+            } else if (result.frequency >= AccessPoint.LOWER_FREQ_24GHZ
+                    && result.frequency <= AccessPoint.HIGHER_FREQ_24GHZ) {
+                // Strictly speaking: [2412, 2482]
+                num24++;
+
+                if (result.level > maxRssi24) {
+                    maxRssi24 = result.level;
+                }
+                if (num24 <= maxDisplayedScans) {
+                    scans24GHz.append(
+                            verboseScanResultSummary(accessPoint, result, bssid,
+                                    nowMs));
+                }
+            }
+        }
+        visibility.append(" [");
+        if (num24 > 0) {
+            visibility.append("(").append(num24).append(")");
+            if (num24 > maxDisplayedScans) {
+                visibility.append("max=").append(maxRssi24).append(",");
+            }
+            visibility.append(scans24GHz.toString());
+        }
+        visibility.append(";");
+        if (num5 > 0) {
+            visibility.append("(").append(num5).append(")");
+            if (num5 > maxDisplayedScans) {
+                visibility.append("max=").append(maxRssi5).append(",");
+            }
+            visibility.append(scans5GHz.toString());
+        }
+        if (numBlackListed > 0) {
+            visibility.append("!").append(numBlackListed);
+        }
+        visibility.append("]");
+
+        return visibility.toString();
+    }
+
+    @VisibleForTesting
+    /* package */ static String verboseScanResultSummary(AccessPoint accessPoint, ScanResult result,
+            String bssid, long nowMs) {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append(" \n{").append(result.BSSID);
+        if (result.BSSID.equals(bssid)) {
+            stringBuilder.append("*");
+        }
+        stringBuilder.append("=").append(result.frequency);
+        stringBuilder.append(",").append(result.level);
+        int speed = getSpecificApSpeed(result, accessPoint.getScoredNetworkCache());
+        if (speed != AccessPoint.Speed.NONE) {
+            stringBuilder.append(",")
+                    .append(accessPoint.getSpeedLabel(speed));
+        }
+        int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000;
+        stringBuilder.append(",").append(ageSeconds).append("s");
+        stringBuilder.append("}");
+        return stringBuilder.toString();
+    }
+
+    @AccessPoint.Speed
+    private static int getSpecificApSpeed(ScanResult result,
+            Map<String, TimestampedScoredNetwork> scoredNetworkCache) {
+        TimestampedScoredNetwork timedScore = scoredNetworkCache.get(result.BSSID);
+        if (timedScore == null) {
+            return AccessPoint.Speed.NONE;
+        }
+        // For debugging purposes we may want to use mRssi rather than result.level as the average
+        // speed wil be determined by mRssi
+        return timedScore.getScore().calculateBadge(result.level);
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 66f4a01..ec594a6 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -66,7 +66,6 @@
 public class AccessPointTest {
 
     private static final String TEST_SSID = "\"test_ssid\"";
-    private static final int NUM_SCAN_RESULTS = 5;
 
     private static final ArrayList<ScanResult> SCAN_RESULTS = buildScanResultCache();
 
@@ -439,26 +438,6 @@
     }
 
     @Test
-    public void testVerboseSummaryString_showsScanResultSpeedLabel() {
-        WifiTracker.sVerboseLogging = true;
-
-        Bundle bundle = new Bundle();
-        ArrayList<ScanResult> scanResults = buildScanResultCache();
-        bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, scanResults);
-        AccessPoint ap = new AccessPoint(mContext, bundle);
-
-        when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
-                .thenReturn(buildScoredNetworkWithMockBadgeCurve());
-        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST);
-
-        ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */,
-                MAX_SCORE_CACHE_AGE_MILLIS);
-        String summary = ap.verboseScanResultSummary(scanResults.get(0), null, 0);
-
-        assertThat(summary.contains(mContext.getString(R.string.speed_label_very_fast))).isTrue();
-    }
-
-    @Test
     public void testSummaryString_concatenatesSpeedLabel() {
         AccessPoint ap = createAccessPointWithScanResultCache();
         ap.update(new WifiConfiguration());
@@ -559,7 +538,6 @@
 
     private ScoredNetwork buildScoredNetworkWithMockBadgeCurve() {
         return buildScoredNetworkWithGivenBadgeCurve(mockBadgeCurve);
-
     }
 
     private ScoredNetwork buildScoredNetworkWithGivenBadgeCurve(RssiCurve badgeCurve) {
@@ -570,7 +548,6 @@
                 badgeCurve,
                 false /* meteredHint */,
                 attr1);
-
     }
 
     private AccessPoint createAccessPointWithScanResultCache() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
new file mode 100644
index 0000000..c5795d3
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.NetworkKey;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.net.WifiKey;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+
+import com.android.settingslib.R;
+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.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WifiUtilsTest {
+    private static final String TEST_SSID = "\"test_ssid\"";
+    private static final String TEST_BSSID = "00:00:00:00:00:00";
+    private static final long MAX_SCORE_CACHE_AGE_MILLIS =
+            20 * DateUtils.MINUTE_IN_MILLIS;
+
+    private Context mContext;
+    @Mock
+    private RssiCurve mockBadgeCurve;
+    @Mock
+    private WifiNetworkScoreCache mockWifiNetworkScoreCache;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @Test
+    public void testVerboseSummaryString_showsScanResultSpeedLabel() {
+        WifiTracker.sVerboseLogging = true;
+
+        Bundle bundle = new Bundle();
+        ArrayList<ScanResult> scanResults = buildScanResultCache();
+        bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, scanResults);
+        AccessPoint ap = new AccessPoint(mContext, bundle);
+
+        when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+                .thenReturn(buildScoredNetworkWithGivenBadgeCurve(mockBadgeCurve));
+        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST);
+
+        ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */,
+                MAX_SCORE_CACHE_AGE_MILLIS);
+        String summary = WifiUtils.verboseScanResultSummary(ap, scanResults.get(0), null, 0);
+
+        assertThat(summary.contains(mContext.getString(R.string.speed_label_very_fast))).isTrue();
+    }
+
+    private static ArrayList<ScanResult> buildScanResultCache() {
+        ArrayList<ScanResult> scanResults = new ArrayList<>();
+        for (int i = 0; i < 5; i++) {
+            ScanResult scanResult = createScanResult(TEST_SSID, "bssid-" + i, i);
+            scanResults.add(scanResult);
+        }
+        return scanResults;
+    }
+
+    private static ScanResult createScanResult(String ssid, String bssid, int rssi) {
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = ssid;
+        scanResult.level = rssi;
+        scanResult.BSSID = bssid;
+        scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
+        scanResult.capabilities = "";
+        return scanResult;
+    }
+
+    private ScoredNetwork buildScoredNetworkWithGivenBadgeCurve(RssiCurve badgeCurve) {
+        Bundle attr1 = new Bundle();
+        attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, badgeCurve);
+        return new ScoredNetwork(
+                new NetworkKey(new WifiKey(TEST_SSID, TEST_BSSID)),
+                badgeCurve,
+                false /* meteredHint */,
+                attr1);
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index f1fb208..ae88227 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -288,65 +288,9 @@
 
     @Override
     public void onFullBackup(FullBackupDataOutput data)  throws IOException {
-        byte[] systemSettingsData = getSystemSettings();
-        byte[] secureSettingsData = getSecureSettings();
-        byte[] globalSettingsData = getGlobalSettings();
-        byte[] lockSettingsData   = getLockSettings(UserHandle.myUserId());
-        byte[] locale = mSettingsHelper.getLocaleData();
-        byte[] softApConfigData = getSoftAPConfiguration();
-        byte[] netPoliciesData = getNetworkPolicies();
-        byte[] wifiFullConfigData = getNewWifiConfigData();
-
-        // Write the data to the staging file, then emit that as our tarfile
-        // representation of the backed-up settings.
-        String root = getFilesDir().getAbsolutePath();
-        File stage = new File(root, STAGE_FILE);
-        try {
-            FileOutputStream filestream = new FileOutputStream(stage);
-            BufferedOutputStream bufstream = new BufferedOutputStream(filestream);
-            DataOutputStream out = new DataOutputStream(bufstream);
-
-            if (DEBUG_BACKUP) Log.d(TAG, "Writing flattened data version " + FULL_BACKUP_VERSION);
-            out.writeInt(FULL_BACKUP_VERSION);
-
-            if (DEBUG_BACKUP) Log.d(TAG, systemSettingsData.length + " bytes of settings data");
-            out.writeInt(systemSettingsData.length);
-            out.write(systemSettingsData);
-            if (DEBUG_BACKUP) {
-                Log.d(TAG, secureSettingsData.length + " bytes of secure settings data");
-            }
-            out.writeInt(secureSettingsData.length);
-            out.write(secureSettingsData);
-            if (DEBUG_BACKUP) {
-                Log.d(TAG, globalSettingsData.length + " bytes of global settings data");
-            }
-            out.writeInt(globalSettingsData.length);
-            out.write(globalSettingsData);
-            if (DEBUG_BACKUP) Log.d(TAG, locale.length + " bytes of locale data");
-            out.writeInt(locale.length);
-            out.write(locale);
-            if (DEBUG_BACKUP) Log.d(TAG, lockSettingsData.length + " bytes of lock settings data");
-            out.writeInt(lockSettingsData.length);
-            out.write(lockSettingsData);
-            if (DEBUG_BACKUP) Log.d(TAG, softApConfigData.length + " bytes of softap config data");
-            out.writeInt(softApConfigData.length);
-            out.write(softApConfigData);
-            if (DEBUG_BACKUP) Log.d(TAG, netPoliciesData.length + " bytes of net policies data");
-            out.writeInt(netPoliciesData.length);
-            out.write(netPoliciesData);
-            if (DEBUG_BACKUP) {
-                Log.d(TAG, wifiFullConfigData.length + " bytes of wifi config data");
-            }
-            out.writeInt(wifiFullConfigData.length);
-            out.write(wifiFullConfigData);
-
-            out.flush();    // also flushes downstream
-
-            // now we're set to emit the tar stream
-            fullBackupFile(stage, data);
-        } finally {
-            stage.delete();
-        }
+        // Full backup of SettingsBackupAgent support was removed in Android P. If you want to adb
+        // backup com.android.providers.settings package use \"-keyvalue\" flag.
+        // Full restore of SettingsBackupAgent is still available for backwards compatibility.
     }
 
     @Override
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1d3f26ee..48de1c9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1720,9 +1720,6 @@
                 Settings.Secure.QS_TILES,
                 SecureSettingsProto.QS_TILES);
         dumpSetting(s, p,
-                Settings.Secure.DEMO_USER_SETUP_COMPLETE,
-                SecureSettingsProto.DEMO_USER_SETUP_COMPLETE);
-        dumpSetting(s, p,
                 Settings.Secure.INSTANT_APPS_ENABLED,
                 SecureSettingsProto.INSTANT_APPS_ENABLED);
         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 f4ec936..1167d69 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1710,18 +1710,9 @@
     }
 
     private List<String> getSettingsNamesLocked(int settingsType, int userId) {
-        boolean instantApp;
-        if (UserHandle.getAppId(Binder.getCallingUid()) < Process.FIRST_APPLICATION_UID) {
-            instantApp = false;
-        } else {
-            ApplicationInfo ai = getCallingApplicationInfoOrThrow();
-            instantApp = ai.isInstantApp();
-        }
-        if (instantApp) {
-            return new ArrayList<String>(getInstantAppAccessibleSettings(settingsType));
-        } else {
-            return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
-        }
+        // Don't enforce the instant app whitelist for now -- its too prone to unintended breakage
+        // in the current form.
+        return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
     }
 
     private void enforceSettingReadable(String settingName, int settingsType, int userId) {
@@ -1734,8 +1725,10 @@
         }
         if (!getInstantAppAccessibleSettings(settingsType).contains(settingName)
                 && !getOverlayInstantAppAccessibleSettings(settingsType).contains(settingName)) {
-            throw new SecurityException("Setting " + settingName + " is not accessible from"
-                    + " ephemeral package " + getCallingPackage());
+            // Don't enforce the instant app whitelist for now -- its too prone to unintended
+            // breakage in the current form.
+            Slog.w(LOG_TAG, "Instant App " + ai.packageName
+                    + " trying to access unexposed setting, this will be an error in the future.");
         }
     }
 
@@ -2599,7 +2592,9 @@
         public void onUidRemovedLocked(int uid) {
             final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID,
                     UserHandle.getUserId(uid));
-            ssaidSettings.deleteSettingLocked(Integer.toString(uid));
+            if (ssaidSettings != null) {
+                ssaidSettings.deleteSettingLocked(Integer.toString(uid));
+            }
         }
 
         @Nullable
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eab42da..b3d6357 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -43,6 +43,7 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
+    <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
     <!-- System tool permissions granted to the shell. -->
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
@@ -132,6 +133,7 @@
     <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" />
     <!-- Permission needed to access privileged VR APIs -->
     <uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" />
+    <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE" />
 
     <application android:label="@string/app_label"
                  android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 2c5eb27..73fcdd7 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -39,7 +39,12 @@
     android-support-v7-mediarouter \
     android-support-v7-palette \
     android-support-v14-preference \
-    android-support-v17-leanback
+    android-support-v17-leanback \
+    android-slices-core \
+    android-slices-view \
+    android-slices-builders \
+    apptoolkit-arch-core-runtime \
+    apptoolkit-lifecycle-extensions \
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     SystemUI-tags \
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 29ecac0..aa2cdbb 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -49,6 +49,7 @@
     <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
+    <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
 
     <!-- Networking and telephony -->
     <uses-permission android:name="android.permission.BLUETOOTH" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 020cfee..5e09e75 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -24,31 +24,22 @@
     android:layout_marginEnd="16dp"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:layout_marginTop="@dimen/date_owner_info_margin"
     android:layout_gravity="center_horizontal"
-    android:paddingTop="4dp"
     android:clipToPadding="false"
     android:orientation="vertical"
     android:layout_centerHorizontal="true">
     <TextView android:id="@+id/title"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
-              android:singleLine="true"
-              android:ellipsize="end"
-              android:fadingEdge="horizontal"
-              android:gravity="center"
-              android:textSize="22sp"
-              android:textColor="?attr/wallpaperTextColor"
+              android:layout_marginBottom="@dimen/widget_vertical_padding"
+              android:paddingStart="64dp"
+              android:paddingEnd="64dp"
+              android:theme="@style/TextAppearance.Keyguard"
     />
-    <TextView android:id="@+id/text"
+    <LinearLayout android:id="@+id/row"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
-              android:singleLine="true"
+              android:orientation="horizontal"
               android:gravity="center"
-              android:visibility="gone"
-              android:textSize="16sp"
-              android:textColor="?attr/wallpaperTextColor"
-              android:layout_marginTop="4dp"
-              android:ellipsize="end"
     />
 </com.android.keyguard.KeyguardSliceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 138733e..c97cfc4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -34,6 +34,7 @@
         android:orientation="vertical">
         <RelativeLayout
             android:id="@+id/keyguard_clock_container"
+            android:animateLayoutChanges="true"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal|top">
@@ -59,14 +60,23 @@
                 android:layout_toEndOf="@id/clock_view"
                 android:visibility="invisible"
                 android:src="@drawable/ic_aod_charging_24dp"
-                android:contentDescription="@string/accessibility_ambient_display_charging"
-            />
+                android:contentDescription="@string/accessibility_ambient_display_charging" />
+            <View
+                android:id="@+id/clock_separator"
+                android:layout_width="16dp"
+                android:layout_height="1dp"
+                android:layout_marginTop="10dp"
+                android:layout_below="@id/clock_view"
+                android:background="#f00"
+                android:layout_centerHorizontal="true" />
 
             <include layout="@layout/keyguard_status_area"
                 android:id="@+id/keyguard_status_area"
+                android:layout_marginTop="10dp"
+                android:layout_marginBottom="@dimen/widget_vertical_padding"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_below="@id/clock_view" />
+                android:layout_below="@id/clock_separator" />
         </RelativeLayout>
 
         <TextView
@@ -83,6 +93,5 @@
             android:letterSpacing="0.05"
             android:ellipsize="marquee"
             android:singleLine="true" />
-
     </LinearLayout>
 </com.android.keyguard.KeyguardStatusView>
diff --git a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
index 1b6fa4c..3fb86d0 100644
--- a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
@@ -16,5 +16,5 @@
   -->
 
 <resources>
-    <dimen name="widget_big_font_size">72dp</dimen>
+    <dimen name="widget_big_font_size">64dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
index 1b6fa4c..3fb86d0 100644
--- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
@@ -16,5 +16,5 @@
   -->
 
 <resources>
-    <dimen name="widget_big_font_size">72dp</dimen>
+    <dimen name="widget_big_font_size">64dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index bcac072..04cf6b0 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -42,9 +42,22 @@
     <dimen name="eca_overlap">-10dip</dimen>
 
     <!-- Default clock parameters -->
-    <dimen name="bottom_text_spacing_digital">-1dp</dimen>
-    <dimen name="widget_label_font_size">14sp</dimen>
-    <dimen name="widget_big_font_size">72dp</dimen>
+    <dimen name="bottom_text_spacing_digital">-10dp</dimen>
+    <!-- Slice header -->
+    <dimen name="widget_title_font_size">28sp</dimen>
+    <!-- Slice subtitle  -->
+    <dimen name="widget_label_font_size">16sp</dimen>
+    <!-- Clock without header -->
+    <dimen name="widget_big_font_size">64dp</dimen>
+    <!-- Clock with header -->
+    <dimen name="widget_small_font_size">22dp</dimen>
+    <!-- Dash between clock and header -->
+    <dimen name="widget_vertical_padding">16dp</dimen>
+    <!-- Subtitle paddings -->
+    <dimen name="widget_separator_thickness">2dp</dimen>
+    <dimen name="widget_horizontal_padding">8dp</dimen>
+    <dimen name="widget_icon_size">16dp</dimen>
+    <dimen name="widget_icon_padding">8dp</dimen>
 
     <!-- The y translation to apply at the start in appear animations. -->
     <dimen name="appear_y_translation_start">32dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 826e3ea..5f52e2a 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -78,4 +78,18 @@
         <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
     </style>
 
+    <style name="TextAppearance.Keyguard" parent="Theme.SystemUI">
+        <item name="android:textSize">@dimen/widget_title_font_size</item>
+        <item name="android:gravity">center</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:maxLines">2</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyLight</item>
+    </style>
+
+    <style name="TextAppearance.Keyguard.Secondary">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textSize">@dimen/widget_label_font_size</item>
+    </style>
+
 </resources>
diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml
index 8b7f692..03d587b 100644
--- a/packages/SystemUI/res/layout/pip_menu_activity.xml
+++ b/packages/SystemUI/res/layout/pip_menu_activity.xml
@@ -60,14 +60,24 @@
       </FrameLayout>
   </FrameLayout>
 
-  <ImageView
-      android:id="@+id/dismiss"
-      android:layout_width="@dimen/pip_action_size"
-      android:layout_height="@dimen/pip_action_size"
-      android:layout_gravity="top|end"
-      android:padding="@dimen/pip_action_padding"
-      android:contentDescription="@string/pip_phone_close"
-      android:src="@drawable/ic_close_white"
-      android:background="?android:selectableItemBackgroundBorderless" />
+    <ImageView
+        android:id="@+id/settings"
+        android:layout_width="@dimen/pip_action_size"
+        android:layout_height="@dimen/pip_action_size"
+        android:layout_gravity="top|start"
+        android:padding="@dimen/pip_action_padding"
+        android:contentDescription="@string/pip_phone_settings"
+        android:src="@drawable/ic_settings"
+        android:background="?android:selectableItemBackgroundBorderless" />
+
+    <ImageView
+        android:id="@+id/dismiss"
+        android:layout_width="@dimen/pip_action_size"
+        android:layout_height="@dimen/pip_action_size"
+        android:layout_gravity="top|end"
+        android:padding="@dimen/pip_action_padding"
+        android:contentDescription="@string/pip_phone_close"
+        android:src="@drawable/ic_close_white"
+        android:background="?android:selectableItemBackgroundBorderless" />
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e313e86..b33f857 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -76,8 +76,11 @@
     <!-- Vibration duration for GlowPadView used in SearchPanelView -->
     <integer translatable="false" name="config_search_panel_view_vibration_duration">20</integer>
 
+    <!-- Show mic or phone affordance on Keyguard -->
+    <bool name="config_keyguardShowLeftAffordance">false</bool>
+
     <!-- Show camera affordance on Keyguard -->
-    <bool name="config_keyguardShowCameraAffordance">true</bool>
+    <bool name="config_keyguardShowCameraAffordance">false</bool>
 
     <!-- Whether we should use SRC drawing mode when drawing the scrim behind. If this flag is set,
          we change the canvas opacity so libhwui doesn't call glClear on our surface, and then we
@@ -95,6 +98,12 @@
 
     <bool name="config_dead_zone_flash">false</bool>
 
+    <!-- Whether to enable dimming navigation buttons when wallpaper is not visible, should be
+         enabled for OLED devices to reduce/prevent burn in on the navigation bar (because of the
+         black background and static button placements) and disabled for all other devices to
+         prevent wasting cpu cycles on the dimming animation -->
+    <bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">true</bool>
+
     <!-- Whether QuickSettings is in a phone landscape -->
     <bool name="quick_settings_wide">false</bool>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fd205dd..98537a1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1895,6 +1895,9 @@
     <!-- Label for PIP close button [CHAR LIMIT=NONE]-->
     <string name="pip_phone_close">Close</string>
 
+    <!-- Label for PIP settings button [CHAR LIMIT=NONE]-->
+    <string name="pip_phone_settings">Settings</string>
+
     <!-- Label for PIP the drag to dismiss hint [CHAR LIMIT=NONE]-->
     <string name="pip_phone_dismiss_hint">Drag down to dismiss</string>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index eb2d12e..1c99d38 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -413,15 +413,4 @@
             Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
         }
     }
-
-    /**
-     * Cancels the current thumbnail transtion to/from Recents for the given task id.
-     */
-    public void cancelThumbnailTransition(int taskId) {
-        try {
-            ActivityManager.getService().cancelTaskThumbnailTransition(taskId);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index 13c48d0..45d1aad8 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -268,6 +268,18 @@
         }
     }
 
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+
+        // Only show marquee when visible
+        if (visibility == VISIBLE) {
+            setEllipsize(TextUtils.TruncateAt.MARQUEE);
+        } else {
+            setEllipsize(TextUtils.TruncateAt.END);
+        }
+    }
+
     /**
      * Top-level function for creating carrier text. Makes text based on simState, PLMN
      * and SPN as well as device capabilities, such as being emergency call capable.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index cb3d59c..8135c61 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -17,38 +17,60 @@
 package com.android.keyguard;
 
 import android.app.PendingIntent;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Observer;
 import android.content.Context;
-import android.database.ContentObserver;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Handler;
+import android.provider.Settings;
+import android.text.Layout;
+import android.text.TextUtils;
+import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.internal.graphics.ColorUtils;
+import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.tuner.TunerService;
 
-import java.util.Collections;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.widget.SliceLiveData;
 
 /**
  * View visible under the clock on the lock screen and AoD.
  */
-public class KeyguardSliceView extends LinearLayout {
+public class KeyguardSliceView extends LinearLayout implements View.OnClickListener,
+        Observer<Slice>, TunerService.Tunable {
 
-    private final Uri mKeyguardSliceUri;
+    private static final String TAG = "KeyguardSliceView";
+    private final HashMap<View, PendingIntent> mClickActions;
+    private Uri mKeyguardSliceUri;
     private TextView mTitle;
-    private TextView mText;
-    private Slice mSlice;
-    private PendingIntent mSliceAction;
+    private LinearLayout mRow;
     private int mTextColor;
     private float mDarkAmount = 0;
 
-    private final ContentObserver mObserver;
+    private LiveData<Slice> mLiveData;
+    private int mIconSize;
+    private Consumer<Boolean> mListener;
+    private boolean mHasHeader;
 
     public KeyguardSliceView(Context context) {
         this(context, null, 0);
@@ -60,74 +82,174 @@
 
     public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        mObserver = new KeyguardSliceObserver(new Handler());
-        mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);;
+
+        TunerService tunerService = Dependency.get(TunerService.class);
+        tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
+
+        mClickActions = new HashMap<>();
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mTitle = findViewById(R.id.title);
-        mText = findViewById(R.id.text);
-        mTextColor = mTitle.getCurrentTextColor();
+        mRow = findViewById(R.id.row);
+        mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
+        mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size);
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        // Set initial content
-        showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
-                Collections.emptyList()));
-
         // Make sure we always have the most current slice
-        getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
-                false /* notifyDescendants */, mObserver);
+        mLiveData.observeForever(this);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
-        getContext().getContentResolver().unregisterContentObserver(mObserver);
+        mLiveData.removeObserver(this);
     }
 
     private void showSlice(Slice slice) {
-        // Items will be wrapped into an action when they have tap targets.
-        SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION);
-        if (actionSlice != null) {
-            mSlice = actionSlice.getSlice();
-            mSliceAction = actionSlice.getAction();
-        } else {
-            mSlice = slice;
-            mSliceAction = null;
-        }
 
-        if (mSlice == null) {
-            setVisibility(GONE);
-            return;
-        }
+        // Main area
+        SliceItem mainItem = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_SLICE,
+                null /* hints */, new String[]{android.app.slice.Slice.HINT_LIST_ITEM});
+        mHasHeader = mainItem != null;
 
-        SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null);
-        if (title == null) {
+        List<SliceItem> subItems = SliceQuery.findAll(slice,
+                android.app.slice.SliceItem.FORMAT_SLICE,
+                new String[]{android.app.slice.Slice.HINT_LIST_ITEM},
+                null /* nonHints */);
+
+        if (!mHasHeader) {
             mTitle.setVisibility(GONE);
         } else {
             mTitle.setVisibility(VISIBLE);
-            mTitle.setText(title.getText());
+            SliceItem mainTitle = SliceQuery.find(mainItem.getSlice(),
+                    android.app.slice.SliceItem.FORMAT_TEXT,
+                    new String[]{android.app.slice.Slice.HINT_TITLE},
+                    null /* nonHints */);
+            CharSequence title = mainTitle.getText();
+            mTitle.setText(title);
+
+            // Check if we're already ellipsizing the text.
+            // We're going to figure out the best possible line break if not.
+            Layout layout = mTitle.getLayout();
+            if (layout != null){
+                final int lineCount = layout.getLineCount();
+                if (lineCount > 0) {
+                    if (layout.getEllipsisCount(lineCount - 1) == 0) {
+                        mTitle.setText(findBestLineBreak(title));
+                    }
+                }
+            }
         }
 
-        SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE);
-        if (text == null) {
-            mText.setVisibility(GONE);
-        } else {
-            mText.setVisibility(VISIBLE);
-            mText.setText(text.getText());
+        mClickActions.clear();
+        final int subItemsCount = subItems.size();
+
+        for (int i = 0; i < subItemsCount; i++) {
+            SliceItem item = subItems.get(i);
+            final Uri itemTag = item.getSlice().getUri();
+            // Try to reuse the view if already exists in the layout
+            KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
+            if (button == null) {
+                button = new KeyguardSliceButton(mContext);
+                button.setTextColor(mTextColor);
+                button.setTag(itemTag);
+            } else {
+                mRow.removeView(button);
+            }
+            button.setHasDivider(i < subItemsCount - 1);
+            mRow.addView(button, i);
+
+            PendingIntent pendingIntent;
+            try {
+                pendingIntent = item.getAction();
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Cannot retrieve action from keyguard slice", e);
+                pendingIntent = null;
+            }
+            mClickActions.put(button, pendingIntent);
+
+            SliceItem title = SliceQuery.find(item.getSlice(),
+                    android.app.slice.SliceItem.FORMAT_TEXT,
+                    new String[]{android.app.slice.Slice.HINT_TITLE},
+                    null /* nonHints */);
+            button.setText(title.getText());
+
+            Drawable iconDrawable = null;
+            SliceItem icon = SliceQuery.find(item.getSlice(),
+                    android.app.slice.SliceItem.FORMAT_IMAGE);
+            if (icon != null) {
+                iconDrawable = icon.getIcon().loadDrawable(mContext);
+                final int width = (int) (iconDrawable.getIntrinsicWidth()
+                        / (float) iconDrawable.getIntrinsicHeight() * mIconSize);
+                iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize);
+            }
+            button.setCompoundDrawablesRelative(iconDrawable, null, null, null);
+            button.setOnClickListener(this);
         }
 
-        final int visibility = title == null && text == null ? GONE : VISIBLE;
+        // Removing old views
+        for (int i = 0; i < mRow.getChildCount(); i++) {
+            View child = mRow.getChildAt(i);
+            if (!mClickActions.containsKey(child)) {
+                mRow.removeView(child);
+                i--;
+            }
+        }
+
+        final int visibility = mHasHeader || subItemsCount > 0 ? VISIBLE : GONE;
         if (visibility != getVisibility()) {
             setVisibility(visibility);
         }
+
+        mListener.accept(mHasHeader);
+    }
+
+    /**
+     * Breaks a string in 2 lines where both have similar character count
+     * but first line is always longer.
+     *
+     * @param charSequence Original text.
+     * @return Optimal string.
+     */
+    private CharSequence findBestLineBreak(CharSequence charSequence) {
+        if (TextUtils.isEmpty(charSequence)) {
+            return charSequence;
+        }
+
+        String source = charSequence.toString();
+        // Ignore if there is only 1 word,
+        // or if line breaks were manually set.
+        if (source.contains("\n") || !source.contains(" ")) {
+            return source;
+        }
+
+        final String[] words = source.split(" ");
+        final StringBuilder optimalString = new StringBuilder(source.length());
+        int current = 0;
+        while (optimalString.length() < source.length() - optimalString.length()) {
+            optimalString.append(words[current]);
+            if (current < words.length - 1) {
+                optimalString.append(" ");
+            }
+            current++;
+        }
+        optimalString.append("\n");
+        for (int i = current; i < words.length; i++) {
+            optimalString.append(words[i]);
+            if (current < words.length - 1) {
+                optimalString.append(" ");
+            }
+        }
+
+        return optimalString.toString();
     }
 
     public void setDark(float darkAmount) {
@@ -135,30 +257,115 @@
         updateTextColors();
     }
 
-    public void setTextColor(int textColor) {
-        mTextColor = textColor;
-    }
-
     private void updateTextColors() {
         final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
         mTitle.setTextColor(blendedColor);
-        mText.setTextColor(blendedColor);
+        int childCount = mRow.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View v = mRow.getChildAt(i);
+            if (v instanceof Button) {
+                ((Button) v).setTextColor(blendedColor);
+            }
+        }
     }
 
-    private class KeyguardSliceObserver extends ContentObserver {
-        KeyguardSliceObserver(Handler handler) {
-            super(handler);
+    @Override
+    public void onClick(View v) {
+        final PendingIntent action = mClickActions.get(v);
+        if (action != null) {
+            try {
+                action.send();
+            } catch (PendingIntent.CanceledException e) {
+                Log.i(TAG, "Pending intent cancelled, nothing to launch", e);
+            }
+        }
+    }
+
+    public void setListener(Consumer<Boolean> listener) {
+        mListener = listener;
+    }
+
+    public boolean hasHeader() {
+        return mHasHeader;
+    }
+
+    /**
+     * LiveData observer lifecycle.
+     * @param slice the new slice content.
+     */
+    @Override
+    public void onChanged(Slice slice) {
+        showSlice(slice);
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        setupUri(newValue);
+    }
+
+    public void setupUri(String uriString) {
+        if (uriString == null) {
+            uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
+        }
+
+        boolean wasObserving = false;
+        if (mLiveData != null && mLiveData.hasActiveObservers()) {
+            wasObserving = true;
+            mLiveData.removeObserver(this);
+        }
+
+        mKeyguardSliceUri = Uri.parse(uriString);
+        mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
+
+        if (wasObserving) {
+            mLiveData.observeForever(this);
+        }
+    }
+
+    /**
+     * Representation of an item that appears under the clock on main keyguard message.
+     * Shows optional separator.
+     */
+    private class KeyguardSliceButton extends Button {
+
+        private final Paint mPaint;
+        private boolean mHasDivider;
+
+        public KeyguardSliceButton(Context context) {
+            super(context, null /* attrs */,
+                    com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary);
+            mPaint = new Paint();
+            mPaint.setStyle(Paint.Style.STROKE);
+            float dividerWidth = context.getResources()
+                    .getDimension(R.dimen.widget_separator_thickness);
+            mPaint.setStrokeWidth(dividerWidth);
+            int horizontalPadding = (int) context.getResources()
+                    .getDimension(R.dimen.widget_horizontal_padding);
+            setPadding(horizontalPadding, 0, horizontalPadding, 0);
+            setCompoundDrawablePadding((int) context.getResources()
+                    .getDimension(R.dimen.widget_icon_padding));
+            setMaxWidth(KeyguardSliceView.this.getWidth() / 2);
+            setMaxLines(1);
+            setEllipsize(TruncateAt.END);
+        }
+
+        public void setHasDivider(boolean hasDivider) {
+            mHasDivider = hasDivider;
         }
 
         @Override
-        public void onChange(boolean selfChange) {
-            this.onChange(selfChange, null);
+        public void setTextColor(int color) {
+            super.setTextColor(color);
+            mPaint.setColor(color);
         }
 
         @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
-                    Collections.emptyList()));
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            if (mHasDivider) {
+                final int lineX = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : getWidth();
+                canvas.drawLine(lineX, 0, lineX, getHeight(), mPaint);
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 78cf2b9..4b9a874 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -28,6 +28,7 @@
 import android.support.v4.graphics.ColorUtils;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Slog;
@@ -38,11 +39,11 @@
 import android.widget.TextClock;
 import android.widget.TextView;
 
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.Utils;
 import com.android.systemui.ChargingView;
 
+import com.google.android.collect.Sets;
+
 import java.util.Locale;
 
 public class KeyguardStatusView extends GridLayout {
@@ -52,8 +53,11 @@
 
     private final LockPatternUtils mLockPatternUtils;
     private final AlarmManager mAlarmManager;
+    private final float mSmallClockScale;
+    private final float mWidgetPadding;
 
     private TextClock mClockView;
+    private View mClockSeparator;
     private TextView mOwnerInfo;
     private ViewGroup mClockContainer;
     private ChargingView mBatteryDoze;
@@ -61,7 +65,7 @@
     private Runnable mPendingMarqueeStart;
     private Handler mHandler;
 
-    private View[] mVisibleInDoze;
+    private ArraySet<View> mVisibleInDoze;
     private boolean mPulsing;
     private float mDarkAmount = 0;
     private int mTextColor;
@@ -112,6 +116,9 @@
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mLockPatternUtils = new LockPatternUtils(getContext());
         mHandler = new Handler(Looper.myLooper());
+        mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size)
+                / getResources().getDimension(R.dimen.widget_big_font_size);
+        mWidgetPadding = getResources().getDimension(R.dimen.widget_vertical_padding);
     }
 
     private void setEnableMarquee(boolean enabled) {
@@ -150,9 +157,14 @@
         mOwnerInfo = findViewById(R.id.owner_info);
         mBatteryDoze = findViewById(R.id.battery_doze);
         mKeyguardSlice = findViewById(R.id.keyguard_status_area);
-        mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice};
+        mClockSeparator = findViewById(R.id.clock_separator);
+        mVisibleInDoze = Sets.newArraySet(mBatteryDoze, mClockView, mKeyguardSlice,
+                mClockSeparator);
         mTextColor = mClockView.getCurrentTextColor();
 
+        mKeyguardSlice.setListener(this::onSliceContentChanged);
+        onSliceContentChanged(mKeyguardSlice.hasHeader());
+
         boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
         setEnableMarquee(shouldMarquee);
         refresh();
@@ -163,6 +175,22 @@
         mClockView.setElegantTextHeight(false);
     }
 
+    private void onSliceContentChanged(boolean hasHeader) {
+        final float clockScale = hasHeader ? mSmallClockScale : 1;
+        float translation = (mClockView.getHeight() - (mClockView.getHeight() * clockScale)) / 2f;
+        if (hasHeader) {
+            translation -= mWidgetPadding;
+        }
+        mClockView.setTranslationY(translation);
+        mClockView.setScaleX(clockScale);
+        mClockView.setScaleY(clockScale);
+        final float batteryTranslation =
+                -(mClockView.getWidth() - (mClockView.getWidth() * clockScale)) / 2;
+        mBatteryDoze.setTranslationX(batteryTranslation);
+        mBatteryDoze.setTranslationY(translation);
+        mClockSeparator.setVisibility(hasHeader ? VISIBLE : GONE);
+    }
+
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -201,17 +229,6 @@
         return mClockView.getTextSize();
     }
 
-    public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
-        if (info == null) {
-            return "";
-        }
-        String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser())
-                ? "EHm"
-                : "Ehma";
-        String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
-        return DateFormat.format(pattern, info.getTriggerTime()).toString();
-    }
-
     private void updateOwnerInfo() {
         if (mOwnerInfo == null) return;
         String ownerInfo = getOwnerInfo();
@@ -303,7 +320,7 @@
         final int N = mClockContainer.getChildCount();
         for (int i = 0; i < N; i++) {
             View child = mClockContainer.getChildAt(i);
-            if (ArrayUtils.contains(mVisibleInDoze, child)) {
+            if (mVisibleInDoze.contains(child)) {
                 continue;
             }
             child.setAlpha(dark ? 0 : 1);
@@ -312,10 +329,12 @@
             mOwnerInfo.setAlpha(dark ? 0 : 1);
         }
 
+        final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount);
         updateDozeVisibleViews();
         mBatteryDoze.setDark(dark);
         mKeyguardSlice.setDark(darkAmount);
-        mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
+        mClockView.setTextColor(blendedTextColor);
+        mClockSeparator.setBackgroundColor(blendedTextColor);
     }
 
     public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 593bb50..a59c97e 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -494,7 +494,8 @@
                     }
                     if (mBackground != null) {
                         RectF dest = new RectF(left, top, right, bottom);
-                        // add a filter bitmap?
+                        Log.i(TAG, "Redrawing in rect: " + dest + " with surface size: "
+                                + mLastRequestedWidth + "x" + mLastRequestedHeight);
                         c.drawBitmap(mBackground, null, dest, null);
                     }
                 } finally {
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 22922e7..a7d1f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -146,6 +146,7 @@
         filter.addDataScheme("package");
         filter.addDataSchemeSpecificPart(mLauncherComponentName.getPackageName(),
                 PatternMatcher.PATTERN_LITERAL);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         mContext.registerReceiver(mLauncherAddedReceiver, filter);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 3177c03..0f3daf5 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -23,18 +23,23 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Dependency.DependencyProvider;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationEntryManager;
 import com.android.systemui.statusbar.NotificationGutsManager;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -46,6 +51,7 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import java.util.function.Consumer;
 
@@ -118,16 +124,16 @@
             Context context) {
         providers.put(NotificationLockscreenUserManager.class,
                 () -> new NotificationLockscreenUserManager(context));
+        providers.put(VisualStabilityManager.class, VisualStabilityManager::new);
         providers.put(NotificationGroupManager.class, NotificationGroupManager::new);
-        providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(
-                Dependency.get(NotificationLockscreenUserManager.class), context));
+        providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context));
+        providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context));
         providers.put(NotificationRemoteInputManager.class,
-                () -> new NotificationRemoteInputManager(
-                        Dependency.get(NotificationLockscreenUserManager.class), context));
-        providers.put(NotificationListener.class, () -> new NotificationListener(
-                Dependency.get(NotificationRemoteInputManager.class), context));
-        providers.put(NotificationLogger.class, () -> new NotificationLogger(
-                Dependency.get(NotificationListener.class),
-                Dependency.get(UiOffloadThread.class)));
+                () -> new NotificationRemoteInputManager(context));
+        providers.put(NotificationListener.class, () -> new NotificationListener(context));
+        providers.put(NotificationLogger.class, NotificationLogger::new);
+        providers.put(NotificationViewHierarchyManager.class,
+                () -> new NotificationViewHierarchyManager(context));
+        providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
new file mode 100644
index 0000000..a89a8ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.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.systemui.car;
+
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationEntryManager;
+
+public class CarNotificationEntryManager extends NotificationEntryManager {
+    public CarNotificationEntryManager(Context context) {
+        super(context);
+    }
+
+    /**
+     * Returns the
+     * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
+     * be triggered when a notification card is long-pressed.
+     */
+    @Override
+    public 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.
+        return null;
+    }
+
+    @Override
+    public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+        // Because space is usually constrained in the auto use-case, there should not be a
+        // pinned notification when the shade has been expanded. Ensure this by not pinning any
+        // notification if the shade is already opened.
+        if (!mPresenter.isPresenterFullyCollapsed()) {
+            return false;
+        }
+
+        return super.shouldPeek(entry, sbn);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
index 5a19e7d..174584d 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
@@ -18,9 +18,22 @@
 import android.content.Context;
 import android.util.ArrayMap;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
 import com.android.systemui.Dependency.DependencyProvider;
+import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.SystemUIFactory;
+import com.android.systemui.UiOffloadThread;
 import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.volume.car.CarVolumeDialogController;
 
 /**
@@ -32,5 +45,7 @@
             Context context) {
         super.injectDependencies(providers, context);
         providers.put(VolumeDialogController.class, () -> new CarVolumeDialogController(context));
+        providers.put(NotificationEntryManager.class,
+                () -> new CarNotificationEntryManager(context));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 0f0402d..bfb3a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -66,7 +66,7 @@
                 createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params),
                 new DozeScreenState(wrappedService, handler),
                 createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler),
-                new DozeWallpaperState()
+                new DozeWallpaperState(context)
         });
 
         return machine;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index b352ec9..75f1b50 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.doze;
 
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+
 import android.app.AlarmManager;
 import android.content.Context;
 import android.os.Handler;
@@ -79,6 +81,11 @@
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         switch (newState) {
             case DOZE_AOD:
+                if (oldState == DOZE_AOD_PAUSED) {
+                    mHost.dozeTimeTick();
+                }
+                scheduleTimeTick();
+                break;
             case DOZE_AOD_PAUSING:
                 scheduleTimeTick();
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index ee41001..5156272 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -23,6 +23,10 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 
 import java.io.PrintWriter;
 
@@ -34,18 +38,28 @@
     private static final String TAG = "DozeWallpaperState";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    @VisibleForTesting
-    final IWallpaperManager mWallpaperManagerService;
+    private final IWallpaperManager mWallpaperManagerService;
+    private boolean mKeyguardVisible;
     private boolean mIsAmbientMode;
+    private final DozeParameters mDozeParameters;
 
-    public DozeWallpaperState() {
+    public DozeWallpaperState(Context context) {
         this(IWallpaperManager.Stub.asInterface(
-                ServiceManager.getService(Context.WALLPAPER_SERVICE)));
+                ServiceManager.getService(Context.WALLPAPER_SERVICE)),
+                new DozeParameters(context), KeyguardUpdateMonitor.getInstance(context));
     }
 
     @VisibleForTesting
-    DozeWallpaperState(IWallpaperManager wallpaperManagerService) {
+    DozeWallpaperState(IWallpaperManager wallpaperManagerService, DozeParameters parameters,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mWallpaperManagerService = wallpaperManagerService;
+        mDozeParameters = parameters;
+        keyguardUpdateMonitor.registerCallback(new KeyguardUpdateMonitorCallback() {
+            @Override
+            public void onKeyguardVisibilityChanged(boolean showing) {
+                mKeyguardVisible = showing;
+            }
+        });
     }
 
     @Override
@@ -58,17 +72,25 @@
             case DOZE_REQUEST_PULSE:
             case DOZE_PULSING:
             case DOZE_PULSE_DONE:
-                isAmbientMode = true;
+                isAmbientMode = mDozeParameters.getAlwaysOn();
                 break;
             default:
                 isAmbientMode = false;
         }
 
+        final boolean animated;
+        if (isAmbientMode) {
+            animated = mDozeParameters.getCanControlScreenOffAnimation() && !mKeyguardVisible;
+        } else {
+            animated = !mDozeParameters.getDisplayNeedsBlanking();
+        }
+
         if (isAmbientMode != mIsAmbientMode) {
             mIsAmbientMode = isAmbientMode;
             try {
-                Log.i(TAG, "AoD wallpaper state changed to: " + mIsAmbientMode);
-                mWallpaperManagerService.setInAmbientMode(mIsAmbientMode);
+                Log.i(TAG, "AoD wallpaper state changed to: " + mIsAmbientMode
+                        + ", animated: " + animated);
+                mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, animated);
             } catch (RemoteException e) {
                 // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
                 Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 5c8c3f3..e008148 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -14,10 +14,13 @@
 
 package com.android.systemui.globalactions;
 
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 
 import android.app.ActivityManager;
 import android.app.Dialog;
+import android.app.KeyguardManager;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -120,6 +123,8 @@
     private final AudioManager mAudioManager;
     private final IDreamManager mDreamManager;
     private final DevicePolicyManager mDevicePolicyManager;
+    private final LockPatternUtils mLockPatternUtils;
+    private final KeyguardManager mKeyguardManager;
 
     private ArrayList<Action> mItems;
     private ActionsDialog mDialog;
@@ -150,6 +155,8 @@
                 ServiceManager.getService(DreamService.DREAM_SERVICE));
         mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
+        mLockPatternUtils = new LockPatternUtils(mContext);
+        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -323,7 +330,8 @@
                 mItems.add(getSettingsAction());
             } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
                 if (Settings.Secure.getInt(mContext.getContentResolver(),
-                            Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0) {
+                            Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0
+                        && shouldDisplayLockdown()) {
                     mItems.add(getLockdownAction());
                 }
             } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
@@ -372,6 +380,19 @@
         return dialog;
     }
 
+    private boolean shouldDisplayLockdown() {
+        int userId = getCurrentUser().id;
+        // Lockdown is meaningless without a place to go.
+        if (!mKeyguardManager.isDeviceSecure(userId)) {
+            return false;
+        }
+
+        // Only show the lockdown button if the device isn't locked down (for whatever reason).
+        int state = mLockPatternUtils.getStrongAuthForUser(userId);
+        return (state == STRONG_AUTH_NOT_REQUIRED
+                || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST);
+    }
+
     private final class PowerAction extends SinglePressAction implements LongPressAction {
         private PowerAction() {
             super(R.drawable.ic_lock_power_off,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 6ddc76b..bd46c5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -16,38 +16,55 @@
 
 package com.android.systemui.keyguard;
 
+import android.app.ActivityManager;
+import android.app.AlarmManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
 import android.icu.text.DateFormat;
 import android.icu.text.DisplayContext;
 import android.net.Uri;
 import android.os.Handler;
-import android.app.slice.Slice;
-import android.app.slice.SliceProvider;
+import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
 
 import java.util.Date;
 import java.util.Locale;
 
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.builders.ListBuilder;
+import androidx.app.slice.builders.ListBuilder.RowBuilder;
+
 /**
  * Simple Slice provider that shows the current date.
  */
-public class KeyguardSliceProvider extends SliceProvider {
+public class KeyguardSliceProvider extends SliceProvider implements
+        NextAlarmController.NextAlarmChangeCallback {
 
     public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+    public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date";
+    public static final String KEYGUARD_NEXT_ALARM_URI =
+            "content://com.android.systemui.keyguard/alarm";
 
     private final Date mCurrentTime = new Date();
     protected final Uri mSliceUri;
+    protected final Uri mDateUri;
+    protected final Uri mAlarmUri;
     private final Handler mHandler;
     private String mDatePattern;
     private DateFormat mDateFormat;
     private String mLastText;
     private boolean mRegistered;
     private boolean mRegisteredEveryMinute;
+    private String mNextAlarm;
+    private NextAlarmController mNextAlarmController;
 
     /**
      * Receiver responsible for time ticking and updating the date format.
@@ -80,23 +97,49 @@
     KeyguardSliceProvider(Handler handler) {
         mHandler = handler;
         mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+        mDateUri = Uri.parse(KEYGUARD_DATE_URI);
+        mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI);
     }
 
     @Override
     public Slice onBindSlice(Uri sliceUri) {
-        return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build();
+        ListBuilder builder = new ListBuilder(mSliceUri)
+                .addRow(new RowBuilder(mDateUri).setTitle(mLastText));
+        if (!TextUtils.isEmpty(mNextAlarm)) {
+            Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
+            builder.addRow(new RowBuilder(mAlarmUri).setTitle(mNextAlarm).addEndItem(icon));
+        }
+
+        return builder.build();
     }
 
     @Override
-    public boolean onCreate() {
-
+    public boolean onCreateSliceProvider() {
+        mNextAlarmController = new NextAlarmControllerImpl(getContext());
+        mNextAlarmController.addCallback(this);
         mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
-
         registerClockUpdate(false /* everyMinute */);
         updateClock();
         return true;
     }
 
+    public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
+        if (info == null) {
+            return "";
+        }
+        String skeleton = android.text.format.DateFormat
+                .is24HourFormat(context, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
+        String pattern = android.text.format.DateFormat
+                .getBestDateTimePattern(Locale.getDefault(), skeleton);
+        return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
+    }
+
+    /**
+     * Registers a broadcast receiver for clock updates, include date, time zone and manually
+     * changing the date/time via the settings app.
+     *
+     * @param everyMinute {@code true} if you also want updates every minute.
+     */
     protected void registerClockUpdate(boolean everyMinute) {
         if (mRegistered) {
             if (mRegisteredEveryMinute == everyMinute) {
@@ -156,4 +199,10 @@
     void cleanDateFormat() {
         mDateFormat = null;
     }
+
+    @Override
+    public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
+        mNextAlarm = formatNextAlarm(getContext(), nextAlarm);
+        getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index b5c0d53..f5f06db 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -133,12 +133,19 @@
                             + mNotificationRampTimeMs + "ms"); }
                     try {
                         Thread.sleep(mNotificationRampTimeMs);
-                        player.start();
                     } catch (InterruptedException e) {
                         Log.e(mTag, "Exception while sleeping to sync notification playback"
                                 + " with ducking", e);
                     }
-                    if (DEBUG) { Log.d(mTag, "player.start"); }
+                    try {
+                        player.start();
+                        if (DEBUG) { Log.d(mTag, "player.start"); }
+                    } catch (Exception e) {
+                        player.release();
+                        player = null;
+                        // playing the notification didn't work, revert the focus request
+                        abandonAudioFocusAfterError();
+                    }
                     if (mPlayer != null) {
                         if (DEBUG) { Log.d(mTag, "mPlayer.release"); }
                         mPlayer.release();
@@ -147,6 +154,8 @@
                 }
                 catch (Exception e) {
                     Log.w(mTag, "error loading sound for " + mCmd.uri, e);
+                    // playing the notification didn't work, revert the focus request
+                    abandonAudioFocusAfterError();
                 }
                 this.notify();
             }
@@ -154,6 +163,16 @@
         }
     };
 
+    private void abandonAudioFocusAfterError() {
+        synchronized (mQueueAudioFocusLock) {
+            if (mAudioManagerWithAudioFocus != null) {
+                if (DEBUG) Log.d(mTag, "abandoning focus after playback error");
+                mAudioManagerWithAudioFocus.abandonAudioFocus(null);
+                mAudioManagerWithAudioFocus = null;
+            }
+        }
+    }
+
     private void startSound(Command cmd) {
         // Preparing can be slow, so if there is something else
         // is playing, let it continue until we're done, so there
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
new file mode 100644
index 0000000..f0e4ccc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.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.pip.phone;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OnOpChangedListener;
+import android.app.IActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Pair;
+
+public class PipAppOpsListener {
+    private static final String TAG = PipAppOpsListener.class.getSimpleName();
+
+    private Context mContext;
+    private IActivityManager mActivityManager;
+    private AppOpsManager mAppOpsManager;
+
+    private PipMotionHelper mMotionHelper;
+
+    private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
+        @Override
+        public void onOpChanged(String op, String packageName) {
+            try {
+                // Dismiss the PiP once the user disables the app ops setting for that package
+                final Pair<ComponentName, Integer> topPipActivityInfo =
+                        PipUtils.getTopPinnedActivity(mContext, mActivityManager);
+                if (topPipActivityInfo.first != null) {
+                    final ApplicationInfo appInfo = mContext.getPackageManager()
+                            .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
+                    if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
+                            mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
+                                    packageName) != MODE_ALLOWED) {
+                        mMotionHelper.dismissPip();
+                    }
+                }
+            } catch (NameNotFoundException e) {
+                // Unregister the listener if the package can't be found
+                unregisterAppOpsListener();
+            }
+        }
+    };
+
+    public PipAppOpsListener(Context context, IActivityManager activityManager,
+            PipMotionHelper motionHelper) {
+        mContext = context;
+        mActivityManager = activityManager;
+        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+        mMotionHelper = motionHelper;
+    }
+
+    public void onActivityPinned(String packageName) {
+        // Register for changes to the app ops setting for this package while it is in PiP
+        registerAppOpsListener(packageName);
+    }
+
+    public void onActivityUnpinned() {
+        // Unregister for changes to the previously PiP'ed package
+        unregisterAppOpsListener();
+    }
+
+    private void registerAppOpsListener(String packageName) {
+        mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
+                mAppOpsChangedListener);
+    }
+
+    private void unregisterAppOpsListener() {
+        mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
+    }
+}
\ No newline at end of file
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 2963506..36531bb 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -64,8 +64,8 @@
     private InputConsumerController mInputConsumerController;
     private PipMenuActivityController mMenuController;
     private PipMediaController mMediaController;
-    private PipNotificationController mNotificationController;
     private PipTouchHandler mTouchHandler;
+    private PipAppOpsListener mAppOpsListener;
 
     /**
      * Handler for system task stack changes.
@@ -76,8 +76,7 @@
             mTouchHandler.onActivityPinned();
             mMediaController.onActivityPinned();
             mMenuController.onActivityPinned();
-            mNotificationController.onActivityPinned(packageName, userId,
-                    true /* deferUntilAnimationEnds */);
+            mAppOpsListener.onActivityPinned(packageName);
 
             SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
         }
@@ -90,7 +89,7 @@
             final int userId = topActivity != null ? topPipActivityInfo.second : 0;
             mMenuController.onActivityUnpinned();
             mTouchHandler.onActivityUnpinned(topActivity);
-            mNotificationController.onActivityUnpinned(topActivity, userId);
+            mAppOpsListener.onActivityUnpinned();
 
             SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null);
         }
@@ -107,7 +106,6 @@
             mTouchHandler.setTouchEnabled(true);
             mTouchHandler.onPinnedStackAnimationEnded();
             mMenuController.onPinnedStackAnimationEnded();
-            mNotificationController.onPinnedStackAnimationEnded();
         }
 
         @Override
@@ -182,7 +180,7 @@
                 mInputConsumerController);
         mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
                 mInputConsumerController);
-        mNotificationController = new PipNotificationController(context, mActivityManager,
+        mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
                 mTouchHandler.getMotionHelper());
         EventBus.getDefault().register(this);
     }
@@ -198,20 +196,6 @@
      * Expands the PIP.
      */
     public final void onBusEvent(ExpandPipEvent event) {
-        if (event.clearThumbnailWindows) {
-            try {
-                StackInfo stackInfo = mActivityManager.getStackInfo(
-                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
-                if (stackInfo != null && stackInfo.taskIds != null) {
-                    ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
-                    for (int taskId : stackInfo.taskIds) {
-                        am.cancelThumbnailTransition(taskId);
-                    }
-                }
-            } catch (RemoteException e) {
-                // Do nothing
-            }
-        }
         mTouchHandler.getMotionHelper().expandPip(false /* skipAnimation */);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 90f7b8d..bfe07a9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.pip.phone;
 
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
@@ -39,6 +43,7 @@
 import android.app.ActivityManager;
 import android.app.PendingIntent.CanceledException;
 import android.app.RemoteAction;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Color;
@@ -46,12 +51,15 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
+import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -105,6 +113,7 @@
     private Drawable mBackgroundDrawable;
     private View mMenuContainer;
     private LinearLayout mActionsGroup;
+    private View mSettingsButton;
     private View mDismissButton;
     private ImageView mExpandButton;
     private int mBetweenActionPaddingLand;
@@ -218,6 +227,11 @@
             }
             return true;
         });
+        mSettingsButton = findViewById(R.id.settings);
+        mSettingsButton.setAlpha(0);
+        mSettingsButton.setOnClickListener((v) -> {
+            showSettings();
+        });
         mDismissButton = findViewById(R.id.dismiss);
         mDismissButton.setAlpha(0);
         mDismissButton.setOnClickListener((v) -> {
@@ -352,12 +366,14 @@
             ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
                     mMenuContainer.getAlpha(), 1f);
             menuAnim.addUpdateListener(mMenuBgUpdateListener);
+            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+                    mSettingsButton.getAlpha(), 1f);
             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                     mDismissButton.getAlpha(), 1f);
             if (menuState == MENU_STATE_FULL) {
-                mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
+                mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
             } else {
-                mMenuContainerAnimator.play(dismissAnim);
+                mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim);
             }
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
             mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
@@ -394,9 +410,11 @@
             ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
                     mMenuContainer.getAlpha(), 0f);
             menuAnim.addUpdateListener(mMenuBgUpdateListener);
+            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+                    mSettingsButton.getAlpha(), 0f);
             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                     mDismissButton.getAlpha(), 0f);
-            mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
+            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
             mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
             mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@@ -526,12 +544,14 @@
         final float menuAlpha = 1 - fraction;
         if (mMenuState == MENU_STATE_FULL) {
             mMenuContainer.setAlpha(menuAlpha);
+            mSettingsButton.setAlpha(menuAlpha);
             mDismissButton.setAlpha(menuAlpha);
             final float interpolatedAlpha =
                     MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
             alpha = (int) (interpolatedAlpha * 255);
         } else {
             if (mMenuState == MENU_STATE_CLOSE) {
+                mSettingsButton.setAlpha(menuAlpha);
                 mDismissButton.setAlpha(menuAlpha);
             }
             alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
@@ -588,6 +608,19 @@
         sendMessage(m, "Could not notify controller to show PIP menu");
     }
 
+    private void showSettings() {
+        final Pair<ComponentName, Integer> topPipActivityInfo =
+                PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
+        if (topPipActivityInfo.first != null) {
+            final UserHandle user = UserHandle.of(topPipActivityInfo.second);
+            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+                    Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
+            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
+            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+            startActivity(settingsIntent);
+        }
+    }
+
     private void notifyActivityCallback(Messenger callback) {
         Message m = Message.obtain();
         m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
deleted file mode 100644
index 6d083e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.app.IActivityManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.util.IconDrawableFactory;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.systemui.R;
-import com.android.systemui.SystemUI;
-import com.android.systemui.util.NotificationChannels;
-
-/**
- * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture.
- */
-public class PipNotificationController {
-    private static final String TAG = PipNotificationController.class.getSimpleName();
-
-    private static final String NOTIFICATION_TAG = PipNotificationController.class.getName();
-    private static final int NOTIFICATION_ID = 0;
-
-    private Context mContext;
-    private IActivityManager mActivityManager;
-    private AppOpsManager mAppOpsManager;
-    private NotificationManager mNotificationManager;
-    private IconDrawableFactory mIconDrawableFactory;
-
-    private PipMotionHelper mMotionHelper;
-
-    // Used when building a deferred notification
-    private String mDeferredNotificationPackageName;
-    private int mDeferredNotificationUserId;
-
-    private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
-        @Override
-        public void onOpChanged(String op, String packageName) {
-            try {
-                // Dismiss the PiP once the user disables the app ops setting for that package
-                final Pair<ComponentName, Integer> topPipActivityInfo =
-                        PipUtils.getTopPinnedActivity(mContext, mActivityManager);
-                if (topPipActivityInfo.first != null) {
-                    final ApplicationInfo appInfo = mContext.getPackageManager()
-                            .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
-                    if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
-                                mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
-                                        packageName) != MODE_ALLOWED) {
-                        mMotionHelper.dismissPip();
-                    }
-                }
-            } catch (NameNotFoundException e) {
-                // Unregister the listener if the package can't be found
-                unregisterAppOpsListener();
-            }
-        }
-    };
-
-    public PipNotificationController(Context context, IActivityManager activityManager,
-            PipMotionHelper motionHelper) {
-        mContext = context;
-        mActivityManager = activityManager;
-        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        mNotificationManager = NotificationManager.from(context);
-        mMotionHelper = motionHelper;
-        mIconDrawableFactory = IconDrawableFactory.newInstance(context);
-    }
-
-    public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) {
-        // Clear any existing notification
-        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
-
-        if (deferUntilAnimationEnds) {
-            mDeferredNotificationPackageName = packageName;
-            mDeferredNotificationUserId = userId;
-        } else {
-            showNotificationForApp(packageName, userId);
-        }
-
-        // Register for changes to the app ops setting for this package while it is in PiP
-        registerAppOpsListener(packageName);
-    }
-
-    public void onPinnedStackAnimationEnded() {
-        if (mDeferredNotificationPackageName != null) {
-            showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId);
-            mDeferredNotificationPackageName = null;
-            mDeferredNotificationUserId = 0;
-        }
-    }
-
-    public void onActivityUnpinned(ComponentName topPipActivity, int userId) {
-        // Unregister for changes to the previously PiP'ed package
-        unregisterAppOpsListener();
-
-        // Reset the deferred notification package
-        mDeferredNotificationPackageName = null;
-        mDeferredNotificationUserId = 0;
-
-        if (topPipActivity != null) {
-            // onActivityUnpinned() is only called after the transition is complete, so we don't
-            // need to defer until the animation ends to update the notification
-            onActivityPinned(topPipActivity.getPackageName(), userId,
-                    false /* deferUntilAnimationEnds */);
-        } else {
-            mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
-        }
-    }
-
-    /**
-     * Builds and shows the notification for the given app.
-     */
-    private void showNotificationForApp(String packageName, int userId) {
-        // Build a new notification
-        try {
-            final UserHandle user = UserHandle.of(userId);
-            final Context userContext = mContext.createPackageContextAsUser(
-                    mContext.getPackageName(), 0, user);
-            final Notification.Builder builder =
-                    new Notification.Builder(userContext, NotificationChannels.GENERAL)
-                            .setLocalOnly(true)
-                            .setOngoing(true)
-                            .setSmallIcon(R.drawable.pip_notification_icon)
-                            .setColor(mContext.getColor(
-                                    com.android.internal.R.color.system_notification_accent_color));
-            if (updateNotificationForApp(builder, packageName, user)) {
-                SystemUI.overrideNotificationAppName(mContext, builder);
-
-                // Show the new notification
-                mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
-            }
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Could not show notification for application", e);
-        }
-    }
-
-    /**
-     * Updates the notification builder with app-specific information, returning whether it was
-     * successful.
-     */
-    private boolean updateNotificationForApp(Notification.Builder builder, String packageName,
-            UserHandle user) throws NameNotFoundException {
-        final PackageManager pm = mContext.getPackageManager();
-        final ApplicationInfo appInfo;
-        try {
-            appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier());
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Could not update notification for application", e);
-            return false;
-        }
-
-        if (appInfo != null) {
-            final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user)
-                    .toString();
-            final String message = mContext.getString(R.string.pip_notification_message, appName);
-            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
-                    Uri.fromParts("package", packageName, null));
-            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
-            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-
-            final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo);
-            builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
-                    .setContentText(message)
-                    .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(),
-                            settingsIntent, FLAG_CANCEL_CURRENT, null, user))
-                    .setStyle(new Notification.BigTextStyle().bigText(message))
-                    .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap());
-            return true;
-        }
-        return false;
-    }
-
-    private void registerAppOpsListener(String packageName) {
-        mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
-                mAppOpsChangedListener);
-    }
-
-    private void unregisterAppOpsListener() {
-        mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
-    }
-
-    /**
-     * Bakes a drawable into a bitmap.
-     */
-    private Bitmap createBitmap(Drawable d) {
-        Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
-                Config.ARGB_8888);
-        Canvas c = new Canvas(bitmap);
-        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
-        d.draw(c);
-        c.setBitmap(null);
-        return bitmap;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 0b7b6d5..927a49c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -18,11 +18,6 @@
 
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
 
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
-
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -31,7 +26,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
 import android.os.UserManager;
-import android.provider.AlarmClock;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.util.AttributeSet;
@@ -39,24 +33,19 @@
 import android.view.View.OnClickListener;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.settingslib.Utils;
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.systemui.Dependency;
-import com.android.systemui.FontSizeUtils;
 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.ListenerAdapter;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.ExpandableIndicator;
 import com.android.systemui.statusbar.phone.MultiUserSwitch;
@@ -65,8 +54,6 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
 import com.android.systemui.tuner.TunerService;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index bef1aff..9883da6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.service.quicksettings.Tile;
@@ -82,6 +83,7 @@
 
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
+        checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_AIRPLANE_MODE);
         final int value = arg instanceof Integer ? (Integer)arg : mSetting.getValue();
         final boolean airplaneMode = value != 0;
         state.value = airplaneMode;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index c35f591..8bdbf28 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -101,6 +101,9 @@
         // state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing());
         state.value = locationEnabled;
         checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_SHARE_LOCATION);
+        if (state.disabledByPolicy == false) {
+            checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_LOCATION_MODE);
+        }
         state.icon = mIcon;
         state.slash.isSlashed = !state.value;
         if (locationEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index ca9a553..06dfd18 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -709,7 +709,6 @@
                 (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
             ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
             am.cancelWindowTransition(launchState.launchedToTaskId);
-            am.cancelThumbnailTransition(getTaskId());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
index 8fe4975..37266f6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
@@ -22,5 +22,4 @@
  * This is sent when the PiP should be expanded due to being relaunched.
  */
 public class ExpandPipEvent extends EventBus.Event {
-    public final boolean clearThumbnailWindows = true;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
new file mode 100644
index 0000000..6bbd09f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
@@ -0,0 +1,960 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager
+        .FORCE_REMOTE_INPUT_HISTORY;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.os.Build;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.EventLogTags;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.R;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationInflater;
+import com.android.systemui.statusbar.notification.RowInflaterTask;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.leak.LeakDetector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
+ * It also handles tasks such as their inflation and their interaction with other
+ * Notification.*Manager objects.
+ */
+public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
+        ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
+        VisualStabilityManager.Callback {
+    private static final String TAG = "NotificationEntryManager";
+    protected static final boolean DEBUG = false;
+    protected static final boolean ENABLE_HEADS_UP = true;
+    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
+
+    protected final NotificationMessagingUtil mMessagingUtil;
+    protected final Context mContext;
+    protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
+    protected final NotificationClicker mNotificationClicker = new NotificationClicker();
+    protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch =
+            new ArraySet<>();
+
+    // Dependencies:
+    protected final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+    protected final NotificationGroupManager mGroupManager =
+            Dependency.get(NotificationGroupManager.class);
+    protected final NotificationGutsManager mGutsManager =
+            Dependency.get(NotificationGutsManager.class);
+    protected final NotificationRemoteInputManager mRemoteInputManager =
+            Dependency.get(NotificationRemoteInputManager.class);
+    protected final NotificationMediaManager mMediaManager =
+            Dependency.get(NotificationMediaManager.class);
+    protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    protected final DeviceProvisionedController mDeviceProvisionedController =
+            Dependency.get(DeviceProvisionedController.class);
+    protected final VisualStabilityManager mVisualStabilityManager =
+            Dependency.get(VisualStabilityManager.class);
+    protected final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+    protected final ForegroundServiceController mForegroundServiceController =
+            Dependency.get(ForegroundServiceController.class);
+    protected final NotificationListener mNotificationListener =
+            Dependency.get(NotificationListener.class);
+
+    protected IStatusBarService mBarService;
+    protected NotificationPresenter mPresenter;
+    protected Callback mCallback;
+    protected PowerManager mPowerManager;
+    protected SystemServicesProxy mSystemServicesProxy;
+    protected NotificationListenerService.RankingMap mLatestRankingMap;
+    protected HeadsUpManager mHeadsUpManager;
+    protected NotificationData mNotificationData;
+    protected ContentObserver mHeadsUpObserver;
+    protected boolean mUseHeadsUp = false;
+    protected boolean mDisableNotificationAlerts;
+    protected NotificationListContainer mListContainer;
+
+    private final class NotificationClicker implements View.OnClickListener {
+
+        @Override
+        public void onClick(final View v) {
+            if (!(v instanceof ExpandableNotificationRow)) {
+                Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
+                return;
+            }
+
+            mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v);
+
+            final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+            final StatusBarNotification sbn = row.getStatusBarNotification();
+            if (sbn == null) {
+                Log.e(TAG, "NotificationClicker called on an unclickable notification,");
+                return;
+            }
+
+            // Check if the notification is displaying the menu, if so slide notification back
+            if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
+                row.animateTranslateNotification(0);
+                return;
+            }
+
+            // Mark notification for one frame.
+            row.setJustClicked(true);
+            DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
+
+            mCallback.onNotificationClicked(sbn, row);
+        }
+
+        public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
+            Notification notification = sbn.getNotification();
+            if (notification.contentIntent != null || notification.fullScreenIntent != null) {
+                row.setOnClickListener(this);
+            } else {
+                row.setOnClickListener(null);
+            }
+        }
+    }
+
+    private final DeviceProvisionedController.DeviceProvisionedListener
+            mDeviceProvisionedListener =
+            new DeviceProvisionedController.DeviceProvisionedListener() {
+                @Override
+                public void onDeviceProvisionedChanged() {
+                    updateNotifications();
+                }
+            };
+
+    public NotificationListenerService.RankingMap getLatestRankingMap() {
+        return mLatestRankingMap;
+    }
+
+    public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) {
+        mLatestRankingMap = latestRankingMap;
+    }
+
+    public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
+        mDisableNotificationAlerts = disableNotificationAlerts;
+        mHeadsUpObserver.onChange(true);
+    }
+
+    public void destroy() {
+        mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+    }
+
+    public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+        if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+            removeNotification(entry.key, getLatestRankingMap());
+            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+            if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
+                setLatestRankingMap(null);
+            }
+        } else {
+            updateNotificationRanking(null);
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("NotificationEntryManager state:");
+        pw.print("  mPendingNotifications=");
+        if (mPendingNotifications.size() == 0) {
+            pw.println("null");
+        } else {
+            for (NotificationData.Entry entry : mPendingNotifications.values()) {
+                pw.println(entry.notification);
+            }
+        }
+        pw.print("  mUseHeadsUp=");
+        pw.println(mUseHeadsUp);
+    }
+
+    public NotificationEntryManager(Context context) {
+        mContext = context;
+        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        mMessagingUtil = new NotificationMessagingUtil(context);
+        mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationListContainer listContainer, Callback callback,
+            HeadsUpManager headsUpManager) {
+        mPresenter = presenter;
+        mCallback = callback;
+        mNotificationData = new NotificationData(presenter);
+        mHeadsUpManager = headsUpManager;
+        mNotificationData.setHeadsUpManager(mHeadsUpManager);
+        mListContainer = listContainer;
+
+        mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                boolean wasUsing = mUseHeadsUp;
+                mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+                        && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+                        Settings.Global.HEADS_UP_OFF);
+                Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+                if (wasUsing != mUseHeadsUp) {
+                    if (!mUseHeadsUp) {
+                        Log.d(TAG,
+                                "dismissing any existing heads up notification on disable event");
+                        mHeadsUpManager.releaseAllImmediately();
+                    }
+                }
+            }
+        };
+
+        if (ENABLE_HEADS_UP) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
+                    true,
+                    mHeadsUpObserver);
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
+                    mHeadsUpObserver);
+        }
+
+        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
+        mHeadsUpObserver.onChange(true); // set up
+    }
+
+    public NotificationData getNotificationData() {
+        return mNotificationData;
+    }
+
+    public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+        return mGutsManager::openGuts;
+    }
+
+    @Override
+    public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+        mUiOffloadThread.submit(() -> {
+            try {
+                mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+            } catch (RemoteException e) {
+                // Ignore.
+            }
+        });
+    }
+
+    @Override
+    public void onReorderingAllowed() {
+        updateNotifications();
+    }
+
+    private boolean shouldSuppressFullScreenIntent(String key) {
+        if (mPresenter.isDeviceInVrMode()) {
+            return true;
+        }
+
+        if (mPowerManager.isInteractive()) {
+            return mNotificationData.shouldSuppressScreenOn(key);
+        } else {
+            return mNotificationData.shouldSuppressScreenOff(key);
+        }
+    }
+
+    private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+        PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+                entry.notification.getUser().getIdentifier());
+
+        final StatusBarNotification sbn = entry.notification;
+        if (entry.row != null) {
+            entry.reset();
+            updateNotification(entry, pmUser, sbn, entry.row);
+        } else {
+            new RowInflaterTask().inflate(mContext, parent, entry,
+                    row -> {
+                        bindRow(entry, pmUser, sbn, row);
+                        updateNotification(entry, pmUser, sbn, row);
+                    });
+        }
+    }
+
+    private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
+        row.setExpansionLogger(this, entry.notification.getKey());
+        row.setGroupManager(mGroupManager);
+        row.setHeadsUpManager(mHeadsUpManager);
+        row.setOnExpandClickListener(mPresenter);
+        row.setInflationCallback(this);
+        row.setLongPressListener(getNotificationLongClicker());
+        mRemoteInputManager.bindRow(row);
+
+        // Get the app name.
+        // Note that Notification.Builder#bindHeaderAppName has similar logic
+        // but since this field is used in the guts, it must be accurate.
+        // Therefore we will only show the application label, or, failing that, the
+        // package name. No substitutions.
+        final String pkg = sbn.getPackageName();
+        String appname = pkg;
+        try {
+            final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS);
+            if (info != null) {
+                appname = String.valueOf(pmUser.getApplicationLabel(info));
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // Do nothing
+        }
+        row.setAppName(appname);
+        row.setOnDismissRunnable(() ->
+                performRemoveNotification(row.getStatusBarNotification()));
+        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        if (ENABLE_REMOTE_INPUT) {
+            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+        }
+
+        mCallback.onBindRow(entry, pmUser, sbn, row);
+    }
+
+    public void performRemoveNotification(StatusBarNotification n) {
+        NotificationData.Entry entry = mNotificationData.get(n.getKey());
+        mRemoteInputManager.onPerformRemoveNotification(n, entry);
+        final String pkg = n.getPackageName();
+        final String tag = n.getTag();
+        final int id = n.getId();
+        final int userId = n.getUserId();
+        try {
+            int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+            if (isHeadsUp(n.getKey())) {
+                dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+            } else if (mListContainer.hasPulsingNotifications()) {
+                dismissalSurface = NotificationStats.DISMISSAL_AOD;
+            }
+            mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
+            removeNotification(n.getKey(), null);
+
+        } catch (RemoteException ex) {
+            // system process is dead if we're here.
+        }
+
+        mCallback.onPerformRemoveNotification(n);
+    }
+
+    /**
+     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
+     * about the failure.
+     *
+     * WARNING: this will call back into us.  Don't hold any locks.
+     */
+    void handleNotificationError(StatusBarNotification n, String message) {
+        removeNotification(n.getKey(), null);
+        try {
+            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
+                    n.getInitialPid(), message, n.getUserId());
+        } catch (RemoteException ex) {
+            // The end is nigh.
+        }
+    }
+
+    private void abortExistingInflation(String key) {
+        if (mPendingNotifications.containsKey(key)) {
+            NotificationData.Entry entry = mPendingNotifications.get(key);
+            entry.abortTask();
+            mPendingNotifications.remove(key);
+        }
+        NotificationData.Entry addedEntry = mNotificationData.get(key);
+        if (addedEntry != null) {
+            addedEntry.abortTask();
+        }
+    }
+
+    @Override
+    public void handleInflationException(StatusBarNotification notification, Exception e) {
+        handleNotificationError(notification, e.getMessage());
+    }
+
+    private void addEntry(NotificationData.Entry shadeEntry) {
+        boolean isHeadsUped = shouldPeek(shadeEntry);
+        if (isHeadsUped) {
+            mHeadsUpManager.showNotification(shadeEntry);
+            // Mark as seen immediately
+            setNotificationShown(shadeEntry.notification);
+        }
+        addNotificationViews(shadeEntry);
+        mCallback.onNotificationAdded(shadeEntry);
+    }
+
+    @Override
+    public void onAsyncInflationFinished(NotificationData.Entry entry) {
+        mPendingNotifications.remove(entry.key);
+        // If there was an async task started after the removal, we don't want to add it back to
+        // the list, otherwise we might get leaks.
+        boolean isNew = mNotificationData.get(entry.key) == null;
+        if (isNew && !entry.row.isRemoved()) {
+            addEntry(entry);
+        } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
+            mVisualStabilityManager.onLowPriorityUpdated(entry);
+            mPresenter.updateNotificationViews();
+        }
+        entry.row.setLowPriorityStateUpdated(false);
+    }
+
+    @Override
+    public void removeNotification(String key, NotificationListenerService.RankingMap ranking) {
+        boolean deferRemoval = false;
+        abortExistingInflation(key);
+        if (mHeadsUpManager.isHeadsUp(key)) {
+            // 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.
+            boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
+                    && !FORCE_REMOTE_INPUT_HISTORY
+                    || !mVisualStabilityManager.isReorderingAllowed();
+            deferRemoval = !mHeadsUpManager.removeNotification(key,  ignoreEarliestRemovalTime);
+        }
+        mMediaManager.onNotificationRemoved(key);
+
+        NotificationData.Entry entry = mNotificationData.get(key);
+        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key)
+                && entry.row != null && !entry.row.isDismissed()) {
+            StatusBarNotification sbn = entry.notification;
+
+            Notification.Builder b = Notification.Builder
+                    .recoverBuilder(mContext, sbn.getNotification().clone());
+            CharSequence[] oldHistory = sbn.getNotification().extras
+                    .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
+            CharSequence[] newHistory;
+            if (oldHistory == null) {
+                newHistory = new CharSequence[1];
+            } else {
+                newHistory = new CharSequence[oldHistory.length + 1];
+                System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
+            }
+            newHistory[0] = String.valueOf(entry.remoteInputText);
+            b.setRemoteInputHistory(newHistory);
+
+            Notification newNotification = b.build();
+
+            // Undo any compatibility view inflation
+            newNotification.contentView = sbn.getNotification().contentView;
+            newNotification.bigContentView = sbn.getNotification().bigContentView;
+            newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
+
+            StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
+                    sbn.getOpPkg(),
+                    sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
+                    newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
+            boolean updated = false;
+            try {
+                updateNotificationInternal(newSbn, null);
+                updated = true;
+            } catch (InflationException e) {
+                deferRemoval = false;
+            }
+            if (updated) {
+                Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
+                mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
+                return;
+            }
+        }
+        if (deferRemoval) {
+            mLatestRankingMap = ranking;
+            mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
+            return;
+        }
+
+        if (mRemoteInputManager.onRemoveNotification(entry)) {
+            mLatestRankingMap = ranking;
+            return;
+        }
+
+        if (entry != null && mGutsManager.getExposedGuts() != null
+                && mGutsManager.getExposedGuts() == entry.row.getGuts()
+                && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
+            Log.w(TAG, "Keeping notification because it's showing guts. " + key);
+            mLatestRankingMap = ranking;
+            mGutsManager.setKeyToRemoveOnGutsClosed(key);
+            return;
+        }
+
+        if (entry != null) {
+            mForegroundServiceController.removeNotification(entry.notification);
+        }
+
+        if (entry != null && entry.row != null) {
+            entry.row.setRemoved();
+            mListContainer.cleanUpViewState(entry.row);
+        }
+        // Let's remove the children if this was a summary
+        handleGroupSummaryRemoved(key);
+        StatusBarNotification old = removeNotificationViews(key, ranking);
+
+        mCallback.onNotificationRemoved(key, old);
+    }
+
+    private StatusBarNotification removeNotificationViews(String key,
+            NotificationListenerService.RankingMap ranking) {
+        NotificationData.Entry entry = mNotificationData.remove(key, ranking);
+        if (entry == null) {
+            Log.w(TAG, "removeNotification for unknown key: " + key);
+            return null;
+        }
+        updateNotifications();
+        Dependency.get(LeakDetector.class).trackGarbage(entry);
+        return entry.notification;
+    }
+
+    /**
+     * Ensures that the group children are cancelled immediately when the group summary is cancelled
+     * instead of waiting for the notification manager to send all cancels. Otherwise this could
+     * lead to flickers.
+     *
+     * 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
+     *
+     */
+    private void handleGroupSummaryRemoved(String key) {
+        NotificationData.Entry entry = mNotificationData.get(key);
+        if (entry != null && entry.row != null
+                && entry.row.isSummaryWithChildren()) {
+            if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
+                // We don't want to remove children for autobundled notifications as they are not
+                // always cancelled. We only remove them if they were dismissed by the user.
+                return;
+            }
+            List<ExpandableNotificationRow> notificationChildren =
+                    entry.row.getNotificationChildren();
+            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 foreground service notification which we can't remove!
+                    continue;
+                }
+                row.setKeepInParent(true);
+                // we need to set this state earlier as otherwise we might generate some weird
+                // animations
+                row.setRemoved();
+            }
+        }
+    }
+
+    public void updateNotificationsOnDensityOrFontScaleChanged() {
+        ArrayList<NotificationData.Entry> activeNotifications =
+                mNotificationData.getActiveNotifications();
+        for (int i = 0; i < activeNotifications.size(); i++) {
+            NotificationData.Entry entry = activeNotifications.get(i);
+            boolean exposedGuts = mGutsManager.getExposedGuts() != null
+                    && entry.row.getGuts() == mGutsManager.getExposedGuts();
+            entry.row.onDensityOrFontScaleChanged();
+            if (exposedGuts) {
+                mGutsManager.setExposedGuts(entry.row.getGuts());
+                mGutsManager.bindGuts(entry.row);
+            }
+        }
+    }
+
+    private void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
+        row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
+        boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
+        boolean isUpdate = mNotificationData.get(entry.key) != null;
+        boolean wasLowPriority = row.isLowPriority();
+        row.setIsLowPriority(isLowPriority);
+        row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
+        // bind the click event to the content area
+        mNotificationClicker.register(row, sbn);
+
+        // Extract target SDK version.
+        try {
+            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
+            entry.targetSdk = info.targetSdkVersion;
+        } catch (PackageManager.NameNotFoundException ex) {
+            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
+        }
+        row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+                && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+        entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
+
+        entry.row = row;
+        entry.row.setOnActivatedListener(mPresenter);
+
+        boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
+                mNotificationData.getImportance(sbn.getKey()));
+        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
+                && !mPresenter.isPresenterFullyCollapsed();
+        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+        row.updateNotification(entry);
+    }
+
+
+    protected void addNotificationViews(NotificationData.Entry entry) {
+        if (entry == null) {
+            return;
+        }
+        // Add the expanded view and icon.
+        mNotificationData.add(entry);
+        updateNotifications();
+    }
+
+    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
+            throws InflationException {
+        if (DEBUG) {
+            Log.d(TAG, "createNotificationViews(notification=" + sbn);
+        }
+        NotificationData.Entry entry = new NotificationData.Entry(sbn);
+        Dependency.get(LeakDetector.class).trackInstance(entry);
+        entry.createIcons(mContext, sbn);
+        // Construct the expanded view.
+        inflateViews(entry, mListContainer.getViewParentForNotification(entry));
+        return entry;
+    }
+
+    private void addNotificationInternal(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) throws InflationException {
+        String key = notification.getKey();
+        if (DEBUG) Log.d(TAG, "addNotification key=" + key);
+
+        mNotificationData.updateRanking(ranking);
+        NotificationData.Entry shadeEntry = createNotificationViews(notification);
+        boolean isHeadsUped = shouldPeek(shadeEntry);
+        if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
+            if (shouldSuppressFullScreenIntent(key)) {
+                if (DEBUG) {
+                    Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
+                }
+            } else if (mNotificationData.getImportance(key)
+                    < NotificationManager.IMPORTANCE_HIGH) {
+                if (DEBUG) {
+                    Log.d(TAG, "No Fullscreen intent: not important enough: "
+                            + key);
+                }
+            } else {
+                // Stop screensaver if the notification has a fullscreen intent.
+                // (like an incoming phone call)
+                SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+
+                // not immersive & a fullscreen alert should be shown
+                if (DEBUG)
+                    Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
+                try {
+                    EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+                            key);
+                    notification.getNotification().fullScreenIntent.send();
+                    shadeEntry.notifyFullScreenIntentLaunched();
+                    mMetricsLogger.count("note_fullscreen", 1);
+                } catch (PendingIntent.CanceledException e) {
+                }
+            }
+        }
+        abortExistingInflation(key);
+
+        mForegroundServiceController.addNotification(notification,
+                mNotificationData.getImportance(key));
+
+        mPendingNotifications.put(key, shadeEntry);
+    }
+
+    @Override
+    public void addNotification(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) {
+        try {
+            addNotificationInternal(notification, ranking);
+        } catch (InflationException e) {
+            handleInflationException(notification, e);
+        }
+    }
+
+    private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) {
+        return oldEntry == null || !oldEntry.hasInterrupted()
+                || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
+    }
+
+    private void updateNotificationInternal(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) throws InflationException {
+        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
+
+        final String key = notification.getKey();
+        abortExistingInflation(key);
+        NotificationData.Entry entry = mNotificationData.get(key);
+        if (entry == null) {
+            return;
+        }
+        mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+        mRemoteInputManager.onUpdateNotification(entry);
+
+        if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
+            mGutsManager.setKeyToRemoveOnGutsClosed(null);
+            Log.w(TAG, "Notification that was kept for guts was updated. " + key);
+        }
+
+        Notification n = notification.getNotification();
+        mNotificationData.updateRanking(ranking);
+
+        final StatusBarNotification oldNotification = entry.notification;
+        entry.notification = notification;
+        mGroupManager.onEntryUpdated(entry, oldNotification);
+
+        entry.updateIcons(mContext, notification);
+        inflateViews(entry, mListContainer.getViewParentForNotification(entry));
+
+        mForegroundServiceController.updateNotification(notification,
+                mNotificationData.getImportance(key));
+
+        boolean shouldPeek = shouldPeek(entry, notification);
+        boolean alertAgain = alertAgain(entry, n);
+
+        updateHeadsUp(key, entry, shouldPeek, alertAgain);
+        updateNotifications();
+
+        if (!notification.isClearable()) {
+            // The user may have performed a dismiss action on the notification, since it's
+            // not clearable we should snap it back.
+            mListContainer.snapViewIfNeeded(entry.row);
+        }
+
+        if (DEBUG) {
+            // Is this for you?
+            boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification);
+            Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
+        }
+
+        mCallback.onNotificationUpdated(notification);
+    }
+
+    @Override
+    public void updateNotification(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) {
+        try {
+            updateNotificationInternal(notification, ranking);
+        } catch (InflationException e) {
+            handleInflationException(notification, e);
+        }
+    }
+
+    public void updateNotifications() {
+        mNotificationData.filterAndSort();
+
+        mPresenter.updateNotificationViews();
+    }
+
+    public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
+        mNotificationData.updateRanking(ranking);
+        updateNotifications();
+    }
+
+    protected boolean shouldPeek(NotificationData.Entry entry) {
+        return shouldPeek(entry, entry.notification);
+    }
+
+    public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+        if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+            if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
+            return false;
+        }
+
+        if (mNotificationData.shouldFilterOut(sbn)) {
+            if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
+            return false;
+        }
+
+        boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
+
+        if (!inUse && !mPresenter.isDozing()) {
+            if (DEBUG) {
+                Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (!mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
+            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+            return false;
+        }
+
+        if (mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) {
+            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+            return false;
+        }
+
+        if (entry.hasJustLaunchedFullScreenIntent()) {
+            if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
+            return false;
+        }
+
+        if (isSnoozedPackage(sbn)) {
+            if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
+            return false;
+        }
+
+        // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
+        int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
+                : NotificationManager.IMPORTANCE_HIGH;
+        if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
+            if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
+            return false;
+        }
+
+        // Don't peek notifications that are suppressed due to group alert behavior
+        if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
+            if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
+            return false;
+        }
+
+        if (!mCallback.shouldPeek(entry, sbn)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    protected void setNotificationShown(StatusBarNotification n) {
+        setNotificationsShown(new String[]{n.getKey()});
+    }
+
+    protected void setNotificationsShown(String[] keys) {
+        try {
+            mNotificationListener.setNotificationsShown(keys);
+        } catch (RuntimeException e) {
+            Log.d(TAG, "failed setNotificationsShown: ", e);
+        }
+    }
+
+    protected boolean isSnoozedPackage(StatusBarNotification sbn) {
+        return mHeadsUpManager.isSnoozed(sbn.getPackageName());
+    }
+
+    protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
+            boolean alertAgain) {
+        final boolean wasHeadsUp = isHeadsUp(key);
+        if (wasHeadsUp) {
+            if (!shouldPeek) {
+                // We don't want this to be interrupting anymore, lets remove it
+                mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
+            } else {
+                mHeadsUpManager.updateNotification(entry, alertAgain);
+            }
+        } else if (shouldPeek && alertAgain) {
+            // This notification was updated to be a heads-up, show it!
+            mHeadsUpManager.showNotification(entry);
+        }
+    }
+
+    protected boolean isHeadsUp(String key) {
+        return mHeadsUpManager.isHeadsUp(key);
+    }
+
+    /**
+     * Callback for NotificationEntryManager.
+     */
+    public interface Callback {
+
+        /**
+         * Called when a new entry is created.
+         *
+         * @param shadeEntry entry that was created
+         */
+        void onNotificationAdded(NotificationData.Entry shadeEntry);
+
+        /**
+         * Called when a notification was updated.
+         *
+         * @param notification notification that was updated
+         */
+        void onNotificationUpdated(StatusBarNotification notification);
+
+        /**
+         * Called when a notification was removed.
+         *
+         * @param key key of notification that was removed
+         * @param old StatusBarNotification of the notification before it was removed
+         */
+        void onNotificationRemoved(String key, StatusBarNotification old);
+
+
+        /**
+         * Called when a notification is clicked.
+         *
+         * @param sbn notification that was clicked
+         * @param row row for that notification
+         */
+        void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row);
+
+        /**
+         * Called when a new notification and row is created.
+         *
+         * @param entry entry for the notification
+         * @param pmUser package manager for user
+         * @param sbn notification
+         * @param row row for the notification
+         */
+        void onBindRow(NotificationData.Entry entry, PackageManager pmUser,
+                StatusBarNotification sbn, ExpandableNotificationRow row);
+
+        /**
+         * Removes a notification immediately.
+         *
+         * @param statusBarNotification notification that is being removed
+         */
+        void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
+
+        /**
+         * Returns true if NotificationEntryManager should peek this notification.
+         *
+         * @param entry entry of the notification that might be peeked
+         * @param sbn notification that might be peeked
+         * @return true if the notification should be peeked
+         */
+        boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
index 2e572e1..87ad6f6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -42,7 +42,6 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 import java.io.FileDescriptor;
@@ -67,24 +66,22 @@
     private final Set<String> mNonBlockablePkgs;
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
-    private final NotificationLockscreenUserManager mLockscreenUserManager;
+
+    // Dependencies:
+    private final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
 
     // which notification is currently being longpress-examined by the user
     private NotificationGuts mNotificationGutsExposed;
     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
-    private NotificationPresenter mPresenter;
-
-    // TODO: Create NotificationListContainer interface and use it instead of
-    // NotificationStackScrollLayout here
-    private NotificationStackScrollLayout mStackScroller;
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
+    private NotificationListContainer mListContainer;
     private NotificationInfo.CheckSaveListener mCheckSaveListener;
     private OnSettingsClickListener mOnSettingsClickListener;
     private String mKeyToRemoveOnGutsClosed;
 
-    public NotificationGutsManager(
-            NotificationLockscreenUserManager lockscreenUserManager,
-            Context context) {
-        mLockscreenUserManager = lockscreenUserManager;
+    public NotificationGutsManager(Context context) {
         mContext = context;
         Resources res = context.getResources();
 
@@ -96,12 +93,13 @@
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
     }
 
-    public void setUp(NotificationPresenter presenter,
-            NotificationStackScrollLayout stackScroller,
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager, NotificationListContainer listContainer,
             NotificationInfo.CheckSaveListener checkSaveListener,
             OnSettingsClickListener onSettingsClickListener) {
         mPresenter = presenter;
-        mStackScroller = stackScroller;
+        mEntryManager = entryManager;
+        mListContainer = listContainer;
         mCheckSaveListener = checkSaveListener;
         mOnSettingsClickListener = onSettingsClickListener;
     }
@@ -158,7 +156,7 @@
         final NotificationGuts guts = row.getGuts();
         guts.setClosedListener((NotificationGuts g) -> {
             if (!g.willBeRemoved() && !row.isRemoved()) {
-                mStackScroller.onHeightChanged(
+                mListContainer.onHeightChanged(
                         row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
             }
             if (mNotificationGutsExposed == g) {
@@ -168,18 +166,18 @@
             String key = sbn.getKey();
             if (key.equals(mKeyToRemoveOnGutsClosed)) {
                 mKeyToRemoveOnGutsClosed = null;
-                mPresenter.removeNotification(key, mPresenter.getLatestRankingMap());
+                mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap());
             }
         });
 
         View gutsView = item.getGutsView();
         if (gutsView instanceof NotificationSnooze) {
             NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
-            snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+            snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper());
             snoozeGuts.setStatusBarNotification(sbn);
             snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
             guts.setHeightChangedListener((NotificationGuts g) -> {
-                mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
+                mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
             });
         }
 
@@ -257,7 +255,7 @@
             mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
         }
         if (resetMenu) {
-            mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
+            mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
         }
     }
 
@@ -350,7 +348,7 @@
                                 !mAccessibilityManager.isTouchExplorationEnabled());
                 guts.setExposed(true /* exposed */, needsFalsingProtection);
                 row.closeRemoteInput();
-                mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+                mListContainer.onHeightChanged(row, true /* needsAnimation */);
                 mNotificationGutsExposed = guts;
                 mGutsMenuItem = item;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
new file mode 100644
index 0000000..43be44d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.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.systemui.statusbar;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+
+/**
+ * Interface representing the entity that contains notifications. It can have
+ * notification views added and removed from it, and will manage displaying them to the user.
+ */
+public interface NotificationListContainer {
+
+    /**
+     * Called when a child is being transferred.
+     *
+     * @param childTransferInProgress whether child transfer is in progress
+     */
+    void setChildTransferInProgress(boolean childTransferInProgress);
+
+    /**
+     * Change the position of child to a new location
+     *
+     * @param child the view to change the position for
+     * @param newIndex the new index
+     */
+    void changeViewPosition(View child, int newIndex);
+
+    /**
+     * Called when a child was added to a group.
+     *
+     * @param row row of the group child that was added
+     */
+    void notifyGroupChildAdded(View row);
+
+    /**
+     * Called when a child was removed from a group.
+     *
+     * @param row row of the child that was removed
+     * @param childrenContainer ViewGroup of the group that the child was removed from
+     */
+    void notifyGroupChildRemoved(View row, ViewGroup childrenContainer);
+
+    /**
+     * Generate an animation for an added child view.
+     *
+     * @param child The view to be added.
+     * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
+     */
+    void generateAddAnimation(View child, boolean fromMoreCard);
+
+    /**
+     * Generate a child order changed event.
+     */
+    void generateChildOrderChangedEvent();
+
+    /**
+     * Returns the number of children in the NotificationListContainer.
+     *
+     * @return the number of children in the NotificationListContainer
+     */
+    int getContainerChildCount();
+
+    /**
+     * Gets the ith child in the NotificationListContainer.
+     *
+     * @param i ith child to get
+     * @return the ith child in the list container
+     */
+    View getContainerChildAt(int i);
+
+    /**
+     * Remove a view from the container
+     *
+     * @param v view to remove
+     */
+    void removeContainerView(View v);
+
+    /**
+     * Add a view to the container
+     *
+     * @param v view to add
+     */
+    void addContainerView(View v);
+
+    /**
+     * Sets the maximum number of notifications to display.
+     *
+     * @param maxNotifications max number of notifications to display
+     */
+    void setMaxDisplayedNotifications(int maxNotifications);
+
+    /**
+     * Handle snapping a non-dismissable row back if the user tried to dismiss it.
+     *
+     * @param row row to snap back
+     */
+    void snapViewIfNeeded(ExpandableNotificationRow row);
+
+    /**
+     * Get the view parent for a notification entry. For example, NotificationStackScrollLayout.
+     *
+     * @param entry entry to get the view parent for
+     * @return the view parent for entry
+     */
+    ViewGroup getViewParentForNotification(NotificationData.Entry entry);
+
+    /**
+     * Called when the height of an expandable view changes.
+     *
+     * @param view view whose height changed
+     * @param animate whether this change should be animated
+     */
+    void onHeightChanged(ExpandableView view, boolean animate);
+
+    /**
+     * Resets the currently exposed menu view.
+     *
+     * @param animate whether to animate the closing/change of menu view
+     * @param force reset the menu view even if it looks like it is already reset
+     */
+    void resetExposedMenuView(boolean animate, boolean force);
+
+    /**
+     * Returns the NotificationSwipeActionHelper for the NotificationListContainer.
+     *
+     * @return swipe action helper for the list container
+     */
+    NotificationSwipeActionHelper getSwipeActionHelper();
+
+    /**
+     * Called when a notification is removed from the shade. This cleans up the state for a
+     * given view.
+     *
+     * @param view view to clean up view state for
+     */
+    void cleanUpViewState(View view);
+
+    /**
+     * Returns whether an ExpandableNotificationRow is in a visible location or not.
+     *
+     * @param row
+     * @return true if row is in a visible location
+     */
+    boolean isInVisibleLocation(ExpandableNotificationRow row);
+
+    /**
+     * Sets a listener to listen for changes in notification locations.
+     *
+     * @param listener listener to set
+     */
+    void setChildLocationsChangedListener(
+            NotificationLogger.OnChildLocationsChangedListener listener);
+
+    /**
+     * Called when an update to the notification view hierarchy is completed.
+     */
+    default void onNotificationViewUpdateFinished() {}
+
+    /**
+     * Returns true if there are pulsing notifications.
+     *
+     * @return true if has pulsing notifications
+     */
+    boolean hasPulsingNotifications();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index a72e8ac..0144f42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -27,6 +27,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 
 /**
@@ -36,14 +37,16 @@
 public class NotificationListener extends NotificationListenerWithPlugins {
     private static final String TAG = "NotificationListener";
 
-    private final NotificationRemoteInputManager mRemoteInputManager;
+    // Dependencies:
+    private final NotificationRemoteInputManager mRemoteInputManager =
+            Dependency.get(NotificationRemoteInputManager.class);
+
     private final Context mContext;
 
-    private NotificationPresenter mPresenter;
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
 
-    public NotificationListener(NotificationRemoteInputManager remoteInputManager,
-            Context context) {
-        mRemoteInputManager = remoteInputManager;
+    public NotificationListener(Context context) {
         mContext = context;
     }
 
@@ -59,7 +62,7 @@
         final RankingMap currentRanking = getCurrentRanking();
         mPresenter.getHandler().post(() -> {
             for (StatusBarNotification sbn : notifications) {
-                mPresenter.addNotification(sbn, currentRanking);
+                mEntryManager.addNotification(sbn, currentRanking);
             }
         });
     }
@@ -73,7 +76,8 @@
                 processForRemoteInput(sbn.getNotification(), mContext);
                 String key = sbn.getKey();
                 mRemoteInputManager.getKeysKeptForRemoteInput().remove(key);
-                boolean isUpdate = mPresenter.getNotificationData().get(key) != null;
+                boolean isUpdate =
+                        mEntryManager.getNotificationData().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,
@@ -86,16 +90,17 @@
 
                     // Remove existing notification to avoid stale data.
                     if (isUpdate) {
-                        mPresenter.removeNotification(key, rankingMap);
+                        mEntryManager.removeNotification(key, rankingMap);
                     } else {
-                        mPresenter.getNotificationData().updateRanking(rankingMap);
+                        mEntryManager.getNotificationData()
+                                .updateRanking(rankingMap);
                     }
                     return;
                 }
                 if (isUpdate) {
-                    mPresenter.updateNotification(sbn, rankingMap);
+                    mEntryManager.updateNotification(sbn, rankingMap);
                 } else {
-                    mPresenter.addNotification(sbn, rankingMap);
+                    mEntryManager.addNotification(sbn, rankingMap);
                 }
             });
         }
@@ -107,7 +112,9 @@
         if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
         if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
             final String key = sbn.getKey();
-            mPresenter.getHandler().post(() -> mPresenter.removeNotification(key, rankingMap));
+            mPresenter.getHandler().post(() -> {
+                mEntryManager.removeNotification(key, rankingMap);
+            });
         }
     }
 
@@ -116,12 +123,16 @@
         if (DEBUG) Log.d(TAG, "onRankingUpdate");
         if (rankingMap != null) {
             RankingMap r = onPluginRankingUpdate(rankingMap);
-            mPresenter.getHandler().post(() -> mPresenter.updateNotificationRanking(r));
+            mPresenter.getHandler().post(() -> {
+                mEntryManager.updateNotificationRanking(r);
+            });
         }
     }
 
-    public void setUpWithPresenter(NotificationPresenter presenter) {
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager) {
         mPresenter = presenter;
+        mEntryManager = entryManager;
 
         try {
             registerAsSystemService(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 644d834..bcdc269 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -81,7 +81,7 @@
                     isCurrentProfile(getSendingUserId())) {
                 mUsersAllowingPrivateNotifications.clear();
                 updateLockscreenNotificationSetting();
-                mPresenter.updateNotifications();
+                mEntryManager.updateNotifications();
             } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
                 if (userId != mCurrentUserId && isCurrentProfile(userId)) {
                     mPresenter.onWorkChallengeChanged();
@@ -108,29 +108,15 @@
                 // Start the overview connection to the launcher service
                 Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
             } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                List<ActivityManager.RecentTaskInfo> recentTask = null;
                 try {
-                    recentTask = ActivityManager.getService().getRecentTasks(1,
-                            ActivityManager.RECENT_WITH_EXCLUDED,
-                            mCurrentUserId).getList();
+                    final int lastResumedActivityUserId =
+                            ActivityManager.getService().getLastResumedActivityUserId();
+                    if (mUserManager.isManagedProfile(lastResumedActivityUserId)) {
+                        showForegroundManagedProfileActivityToast();
+                    }
                 } catch (RemoteException e) {
                     // Abandon hope activity manager not running.
                 }
-                if (recentTask != null && recentTask.size() > 0) {
-                    UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId);
-                    if (user != null && user.isManagedProfile()) {
-                        Toast toast = Toast.makeText(mContext,
-                                R.string.managed_profile_foreground_toast,
-                                Toast.LENGTH_SHORT);
-                        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(
-                                R.dimen.managed_profile_toast_padding);
-                        text.setCompoundDrawablePadding(paddingPx);
-                        toast.show();
-                    }
-                }
             } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
                 final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
                 final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
@@ -157,6 +143,7 @@
 
     protected int mCurrentUserId = 0;
     protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
     protected ContentObserver mLockscreenSettingsObserver;
     protected ContentObserver mSettingsObserver;
 
@@ -170,8 +157,10 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     }
 
-    public void setUpWithPresenter(NotificationPresenter presenter) {
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager) {
         mPresenter = presenter;
+        mEntryManager = entryManager;
 
         mLockscreenSettingsObserver = new ContentObserver(mPresenter.getHandler()) {
             @Override
@@ -182,7 +171,7 @@
                 mUsersAllowingNotifications.clear();
                 // ... and refresh all the notifications
                 updateLockscreenNotificationSetting();
-                mPresenter.updateNotifications();
+                mEntryManager.updateNotifications();
             }
         };
 
@@ -191,7 +180,7 @@
             public void onChange(boolean selfChange) {
                 updateLockscreenNotificationSetting();
                 if (mDeviceProvisionedController.isDeviceProvisioned()) {
-                    mPresenter.updateNotifications();
+                    mEntryManager.updateNotifications();
                 }
             }
         };
@@ -242,6 +231,19 @@
         mSettingsObserver.onChange(false);  // set up
     }
 
+    private void showForegroundManagedProfileActivityToast() {
+        Toast toast = Toast.makeText(mContext,
+                R.string.managed_profile_foreground_toast,
+                Toast.LENGTH_SHORT);
+        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(
+                R.dimen.managed_profile_toast_padding);
+        text.setCompoundDrawablePadding(paddingPx);
+        toast.show();
+    }
+
     public boolean shouldShowLockscreenNotifications() {
         return mShowLockscreenNotifications;
     }
@@ -271,13 +273,13 @@
      */
     public boolean shouldHideNotifications(String key) {
         return isLockscreenPublicMode(mCurrentUserId)
-                && mPresenter.getNotificationData().getVisibilityOverride(key) ==
+                && mEntryManager.getNotificationData().getVisibilityOverride(key) ==
                         Notification.VISIBILITY_SECRET;
     }
 
     public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
         return mShowLockscreenNotifications
-                && !mPresenter.getNotificationData().isAmbient(sbn.getKey());
+                && !mEntryManager.getNotificationData().isAmbient(sbn.getKey());
     }
 
     private void setShowLockscreenNotifications(boolean show) {
@@ -395,7 +397,7 @@
     }
 
     private boolean packageHasVisibilityOverride(String key) {
-        return mPresenter.getNotificationData().getVisibilityOverride(key) ==
+        return mEntryManager.getNotificationData().getVisibilityOverride(key) ==
                 Notification.VISIBILITY_PRIVATE;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
index e58d801..4225f83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
@@ -27,8 +27,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.Dependency;
 import com.android.systemui.UiOffloadThread;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -47,21 +47,22 @@
     /** Keys of notifications currently visible to the user. */
     private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
             new ArraySet<>();
-    private final NotificationListenerService mNotificationListener;
-    private final UiOffloadThread mUiOffloadThread;
 
-    protected NotificationPresenter mPresenter;
+    // Dependencies:
+    private final NotificationListenerService mNotificationListener =
+            Dependency.get(NotificationListener.class);
+    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+
+    protected NotificationEntryManager mEntryManager;
     protected Handler mHandler = new Handler();
     protected IStatusBarService mBarService;
     private long mLastVisibilityReportUptimeMs;
-    private NotificationStackScrollLayout mStackScroller;
+    private NotificationListContainer mListContainer;
 
-    protected final NotificationStackScrollLayout.OnChildLocationsChangedListener
-            mNotificationLocationsChangedListener =
-            new NotificationStackScrollLayout.OnChildLocationsChangedListener() {
+    protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
+            new OnChildLocationsChangedListener() {
                 @Override
-                public void onChildLocationsChanged(
-                        NotificationStackScrollLayout stackScrollLayout) {
+                public void onChildLocationsChanged() {
                     if (mHandler.hasCallbacks(mVisibilityReporter)) {
                         // Visibilities will be reported when the existing
                         // callback is executed.
@@ -99,13 +100,13 @@
             //    notifications.
             // 3. Report newly visible and no-longer visible notifications.
             // 4. Keep currently visible notifications for next report.
-            ArrayList<NotificationData.Entry> activeNotifications = mPresenter.
-                    getNotificationData().getActiveNotifications();
+            ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
+                    .getNotificationData().getActiveNotifications();
             int N = activeNotifications.size();
             for (int i = 0; i < N; i++) {
                 NotificationData.Entry entry = activeNotifications.get(i);
                 String key = entry.notification.getKey();
-                boolean isVisible = mStackScroller.isInVisibleLocation(entry.row);
+                boolean isVisible = mListContainer.isInVisibleLocation(entry.row);
                 NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible);
                 boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
                 if (isVisible) {
@@ -135,19 +136,15 @@
         }
     };
 
-    public NotificationLogger(NotificationListenerService notificationListener,
-            UiOffloadThread uiOffloadThread) {
-        mNotificationListener = notificationListener;
-        mUiOffloadThread = uiOffloadThread;
+    public NotificationLogger() {
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     }
 
-    // TODO: Remove dependency on NotificationStackScrollLayout.
-    public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationStackScrollLayout stackScroller) {
-        mPresenter = presenter;
-        mStackScroller = stackScroller;
+    public void setUpWithEntryManager(NotificationEntryManager entryManager,
+            NotificationListContainer listContainer) {
+        mEntryManager = entryManager;
+        mListContainer = listContainer;
     }
 
     public void stopNotificationLogging() {
@@ -159,18 +156,18 @@
             recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
         }
         mHandler.removeCallbacks(mVisibilityReporter);
-        mStackScroller.setChildLocationsChangedListener(null);
+        mListContainer.setChildLocationsChangedListener(null);
     }
 
     public void startNotificationLogging() {
-        mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
+        mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
         // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
         // cause the scroller to emit child location events. Hence generate
         // one ourselves to guarantee that we're reporting visible
         // notifications.
         // (Note that in cases where the scroller does emit events, this
         // additional event doesn't break anything.)
-        mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller);
+        mNotificationLocationsChangedListener.onChildLocationsChanged();
     }
 
     private void logNotificationVisibilityChanges(
@@ -220,4 +217,11 @@
     public Runnable getVisibilityReporter() {
         return mVisibilityReporter;
     }
+
+    /**
+     * A listener that is notified when some child locations might have changed.
+     */
+    public interface OnChildLocationsChangedListener {
+        void onChildLocationsChanged();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 283a6e3..852239a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -40,9 +40,11 @@
     private static final String TAG = "NotificationMediaManager";
     public static final boolean DEBUG_MEDIA = false;
 
-    private final NotificationPresenter mPresenter;
     private final Context mContext;
     private final MediaSessionManager mMediaSessionManager;
+
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
     private MediaController mMediaController;
     private String mMediaNotificationKey;
     private MediaMetadata mMediaMetadata;
@@ -73,8 +75,7 @@
         }
     };
 
-    public NotificationMediaManager(NotificationPresenter presenter, Context context) {
-        mPresenter = presenter;
+    public NotificationMediaManager(Context context) {
         mContext = context;
         mMediaSessionManager
                 = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -82,6 +83,12 @@
         // in session state
     }
 
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+    }
+
     public void onNotificationRemoved(String key) {
         if (key.equals(mMediaNotificationKey)) {
             clearCurrentMediaNotification();
@@ -100,8 +107,8 @@
     public void findAndUpdateMediaNotifications() {
         boolean metaDataChanged = false;
 
-        synchronized (mPresenter.getNotificationData()) {
-            ArrayList<NotificationData.Entry> activeNotifications = mPresenter
+        synchronized (mEntryManager.getNotificationData()) {
+            ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
                     .getNotificationData().getActiveNotifications();
             final int N = activeNotifications.size();
 
@@ -188,7 +195,7 @@
         }
 
         if (metaDataChanged) {
-            mPresenter.updateNotifications();
+            mEntryManager.updateNotifications();
         }
         mPresenter.updateMediaMetaData(metaDataChanged, true);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 33c7253..12641a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -16,12 +16,11 @@
 package com.android.systemui.statusbar;
 
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Handler;
-import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
 import android.view.View;
 
-import java.util.Set;
-
 /**
  * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
  * for both querying the state of the system (some modularised piece of functionality may
@@ -29,9 +28,11 @@
  * for affecting the state of the system (e.g. starting an intent, given that the presenter may
  * want to perform some action before doing so).
  */
-public interface NotificationPresenter extends NotificationUpdateHandler,
-        NotificationData.Environment, NotificationRemoteInputManager.Callback {
-
+public interface NotificationPresenter extends NotificationData.Environment,
+        NotificationRemoteInputManager.Callback,
+        ExpandableNotificationRow.OnExpandClickListener,
+        ActivatableNotificationView.OnActivatedListener,
+        NotificationEntryManager.Callback {
     /**
      * Returns true if the presenter is not visible. For example, it may not be necessary to do
      * animations if this returns true.
@@ -50,32 +51,15 @@
     void startNotificationGutsIntent(Intent intent, int appUid);
 
     /**
-     * Returns NotificationData.
-     */
-    NotificationData getNotificationData();
-
-    /**
      * Returns the Handler for NotificationPresenter.
      */
     Handler getHandler();
 
-    // TODO: Create NotificationEntryManager and move this method to there.
-    /**
-     * Signals that some notifications have changed, and NotificationPresenter should update itself.
-     */
-    void updateNotifications();
-
     /**
      * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
      */
     void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
 
-    // TODO: Create NotificationEntryManager and move this method to there.
-    /**
-     * Gets the latest ranking map.
-     */
-    NotificationListenerService.RankingMap getLatestRankingMap();
-
     /**
      * Called when the locked status of the device is changed for a work profile.
      */
@@ -107,4 +91,32 @@
      * @return true iff the device is locked
      */
     boolean isDeviceLocked(int userId);
+
+    /**
+     * @return true iff the device is in vr mode
+     */
+    boolean isDeviceInVrMode();
+
+    /**
+     * Updates the visual representation of the notifications.
+     */
+    void updateNotificationViews();
+
+    /**
+     * @return true iff the device is dozing
+     */
+    boolean isDozing();
+
+    /**
+     * Returns the maximum number of notifications to show while locked.
+     *
+     * @param recompute whether something has changed that means we should recompute this value
+     * @return the maximum number of notifications to show while locked
+     */
+    int getMaxNotificationsWhileLocked(boolean recompute);
+
+    /**
+     * Called when the row states are updated by NotificationViewHierarchyManager.
+     */
+    void onUpdateRowStates();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 7827f62..f25379a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -39,6 +39,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 
@@ -70,7 +71,10 @@
 
     protected final ArraySet<NotificationData.Entry> mRemoteInputEntriesToRemoveOnCollapse =
             new ArraySet<>();
-    protected final NotificationLockscreenUserManager mLockscreenUserManager;
+
+    // Dependencies:
+    protected final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
 
     /**
      * Notifications with keys in this set are not actually around anymore. We kept them around
@@ -83,6 +87,7 @@
 
     protected RemoteInputController mRemoteInputController;
     protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
     protected IStatusBarService mBarService;
     protected Callback mCallback;
 
@@ -263,9 +268,7 @@
         }
     };
 
-    public NotificationRemoteInputManager(NotificationLockscreenUserManager lockscreenUserManager,
-            Context context) {
-        mLockscreenUserManager = lockscreenUserManager;
+    public NotificationRemoteInputManager(Context context) {
         mContext = context;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -273,16 +276,18 @@
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager,
             Callback callback,
             RemoteInputController.Delegate delegate) {
         mPresenter = presenter;
+        mEntryManager = entryManager;
         mCallback = callback;
         mRemoteInputController = new RemoteInputController(delegate);
         mRemoteInputController.addCallback(new RemoteInputController.Callback() {
             @Override
             public void onRemoteInputSent(NotificationData.Entry entry) {
                 if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) {
-                    mPresenter.removeNotification(entry.key, null);
+                    mEntryManager.removeNotification(entry.key, null);
                 } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) {
                     // We're currently holding onto this notification, but from the apps point of
                     // view it is already canceled, so we'll need to cancel it on the apps behalf
@@ -290,7 +295,7 @@
                     // bit.
                     mPresenter.getHandler().postDelayed(() -> {
                         if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) {
-                            mPresenter.removeNotification(entry.key, null);
+                            mEntryManager.removeNotification(entry.key, null);
                         }
                     }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
                 }
@@ -336,7 +341,7 @@
         for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
             NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
             mRemoteInputController.removeRemoteInput(entry, null);
-            mPresenter.removeNotification(entry.key, mPresenter.getLatestRankingMap());
+            mEntryManager.removeNotification(entry.key, mEntryManager.getLatestRankingMap());
         }
         mRemoteInputEntriesToRemoveOnCollapse.clear();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 1cbb440..8325df7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -25,6 +25,7 @@
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
@@ -56,6 +57,7 @@
     private static final boolean ICON_ANMATIONS_WHILE_SCROLLING
             = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
     private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
+    private static final String TAG = "NotificationShelf";
     private ViewInvertHelper mViewInvertHelper;
     private boolean mDark;
     private NotificationIconContainer mShelfIcons;
@@ -294,10 +296,15 @@
             if (notGoneIndex == 0) {
                 StatusBarIconView icon = row.getEntry().expandedIcon;
                 NotificationIconContainer.IconState iconState = getIconState(icon);
-                if (iconState.clampedAppearAmount == 1.0f) {
+                if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
                     // only if the first icon is fully in the shelf we want to clip to it!
                     backgroundTop = (int) (row.getTranslationY() - getTranslationY());
                     firstElementRoundness = row.getCurrentTopRoundness();
+                } else if (iconState == null) {
+                    Log.wtf(TAG, "iconState is null. ExpandedIcon: " + row.getEntry().expandedIcon
+                            + (row.getEntry().expandedIcon != null
+                            ? "\n icon parent: " + row.getEntry().expandedIcon.getParent() : "")
+                            + " \n number of notifications: " + mHostLayout.getChildCount() );
                 }
             }
             notGoneIndex++;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
new file mode 100644
index 0000000..266c09b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.content.res.Resources;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
+ * on their group structure. For example, if a notification becomes bundled with another,
+ * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
+ * tell NotificationListContainer which notifications to display, and inform it of changes to those
+ * notifications that might affect their display.
+ */
+public class NotificationViewHierarchyManager {
+    private static final String TAG = "NotificationViewHierarchyManager";
+
+    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
+            mTmpChildOrderMap = new HashMap<>();
+
+    // Dependencies:
+    protected final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+    protected final NotificationGroupManager mGroupManager =
+            Dependency.get(NotificationGroupManager.class);
+    protected final VisualStabilityManager mVisualStabilityManager =
+            Dependency.get(VisualStabilityManager.class);
+
+    /**
+     * {@code true} if notifications not part of a group should by default be rendered in their
+     * expanded state. If {@code false}, then only the first notification will be expanded if
+     * possible.
+     */
+    private final boolean mAlwaysExpandNonGroupedNotification;
+
+    private NotificationPresenter mPresenter;
+    private NotificationEntryManager mEntryManager;
+    private NotificationListContainer mListContainer;
+
+    public NotificationViewHierarchyManager(Context context) {
+        Resources res = context.getResources();
+        mAlwaysExpandNonGroupedNotification =
+                res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager, NotificationListContainer listContainer) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+        mListContainer = listContainer;
+    }
+
+    /**
+     * Updates the visual representation of the notifications.
+     */
+    public void updateNotificationViews() {
+        ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
+                .getActiveNotifications();
+        ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
+        final int N = activeNotifications.size();
+        for (int i = 0; i < N; i++) {
+            NotificationData.Entry ent = activeNotifications.get(i);
+            if (ent.row.isDismissed() || ent.row.isRemoved()) {
+                // we don't want to update removed notifications because they could
+                // temporarily become children if they were isolated before.
+                continue;
+            }
+            int userId = ent.notification.getUserId();
+
+            // Display public version of the notification if we need to redact.
+            // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
+            // We can probably move some of this code there.
+            boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
+                    mLockscreenUserManager.getCurrentUserId());
+            boolean userPublic = devicePublic
+                    || mLockscreenUserManager.isLockscreenPublicMode(userId);
+            boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
+            boolean sensitive = userPublic && needsRedaction;
+            boolean deviceSensitive = devicePublic
+                    && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+                    mLockscreenUserManager.getCurrentUserId());
+            ent.row.setSensitive(sensitive, deviceSensitive);
+            ent.row.setNeedsRedaction(needsRedaction);
+            if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
+                ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
+                        ent.row.getStatusBarNotification());
+                List<ExpandableNotificationRow> orderedChildren =
+                        mTmpChildOrderMap.get(summary);
+                if (orderedChildren == null) {
+                    orderedChildren = new ArrayList<>();
+                    mTmpChildOrderMap.put(summary, orderedChildren);
+                }
+                orderedChildren.add(ent.row);
+            } else {
+                toShow.add(ent.row);
+            }
+
+        }
+
+        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+        for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
+                toRemove.add((ExpandableNotificationRow) child);
+            }
+        }
+
+        for (ExpandableNotificationRow remove : toRemove) {
+            if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
+                // we are only transferring this notification to its parent, don't generate an
+                // animation
+                mListContainer.setChildTransferInProgress(true);
+            }
+            if (remove.isSummaryWithChildren()) {
+                remove.removeAllChildren();
+            }
+            mListContainer.removeContainerView(remove);
+            mListContainer.setChildTransferInProgress(false);
+        }
+
+        removeNotificationChildren();
+
+        for (int i = 0; i < toShow.size(); i++) {
+            View v = toShow.get(i);
+            if (v.getParent() == null) {
+                mVisualStabilityManager.notifyViewAddition(v);
+                mListContainer.addContainerView(v);
+            }
+        }
+
+        addNotificationChildrenAndSort();
+
+        // So after all this work notifications still aren't sorted correctly.
+        // Let's do that now by advancing through toShow and mListContainer in
+        // lock-step, making sure mListContainer matches what we see in toShow.
+        int j = 0;
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow targetChild = toShow.get(j);
+            if (child != targetChild) {
+                // Oops, wrong notification at this position. Put the right one
+                // here and advance both lists.
+                if (mVisualStabilityManager.canReorderNotification(targetChild)) {
+                    mListContainer.changeViewPosition(targetChild, i);
+                } else {
+                    mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager);
+                }
+            }
+            j++;
+
+        }
+
+        mVisualStabilityManager.onReorderingFinished();
+        // clear the map again for the next usage
+        mTmpChildOrderMap.clear();
+
+        updateRowStates();
+
+        mListContainer.onNotificationViewUpdateFinished();
+    }
+
+    private void addNotificationChildrenAndSort() {
+        // Let's now add all notification children which are missing
+        boolean orderChanged = false;
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View view = mListContainer.getContainerChildAt(i);
+            if (!(view instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+            for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
+                    childIndex++) {
+                ExpandableNotificationRow childView = orderedChildren.get(childIndex);
+                if (children == null || !children.contains(childView)) {
+                    if (childView.getParent() != null) {
+                        Log.wtf(TAG, "trying to add a notification child that already has " +
+                                "a parent. class:" + childView.getParent().getClass() +
+                                "\n child: " + childView);
+                        // This shouldn't happen. We can recover by removing it though.
+                        ((ViewGroup) childView.getParent()).removeView(childView);
+                    }
+                    mVisualStabilityManager.notifyViewAddition(childView);
+                    parent.addChildNotification(childView, childIndex);
+                    mListContainer.notifyGroupChildAdded(childView);
+                }
+            }
+
+            // Finally after removing and adding has been performed we can apply the order.
+            orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager,
+                    mEntryManager);
+        }
+        if (orderChanged) {
+            mListContainer.generateChildOrderChangedEvent();
+        }
+    }
+
+    private void removeNotificationChildren() {
+        // First let's remove all children which don't belong in the parents
+        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View view = mListContainer.getContainerChildAt(i);
+            if (!(view instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+            if (children != null) {
+                toRemove.clear();
+                for (ExpandableNotificationRow childRow : children) {
+                    if ((orderedChildren == null
+                            || !orderedChildren.contains(childRow))
+                            && !childRow.keepInParent()) {
+                        toRemove.add(childRow);
+                    }
+                }
+                for (ExpandableNotificationRow remove : toRemove) {
+                    parent.removeChildNotification(remove);
+                    if (mEntryManager.getNotificationData().get(
+                            remove.getStatusBarNotification().getKey()) == null) {
+                        // We only want to add an animation if the view is completely removed
+                        // otherwise it's just a transfer
+                        mListContainer.notifyGroupChildRemoved(remove,
+                                parent.getChildrenContainer());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates expanded, dimmed and locked states of notification rows.
+     */
+    public void updateRowStates() {
+        final int N = mListContainer.getContainerChildCount();
+
+        int visibleNotifications = 0;
+        boolean isLocked = mPresenter.isPresenterLocked();
+        int maxNotifications = -1;
+        if (isLocked) {
+            maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
+        }
+        mListContainer.setMaxDisplayedNotifications(maxNotifications);
+        Stack<ExpandableNotificationRow> stack = new Stack<>();
+        for (int i = N - 1; i >= 0; i--) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            stack.push((ExpandableNotificationRow) child);
+        }
+        while(!stack.isEmpty()) {
+            ExpandableNotificationRow row = stack.pop();
+            NotificationData.Entry entry = row.getEntry();
+            boolean isChildNotification =
+                    mGroupManager.isChildInGroupWithSummary(entry.notification);
+
+            row.setOnKeyguard(isLocked);
+
+            if (!isLocked) {
+                // If mAlwaysExpandNonGroupedNotification is false, then only expand the
+                // very first notification and if it's not a child of grouped notifications.
+                row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
+                        || (visibleNotifications == 0 && !isChildNotification
+                        && !row.isLowPriority()));
+            }
+
+            entry.row.setShowAmbient(mPresenter.isDozing());
+            int userId = entry.notification.getUserId();
+            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
+                    entry.notification) && !entry.row.isRemoved();
+            boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
+                    .notification);
+            if (suppressedSummary
+                    || (mLockscreenUserManager.isLockscreenPublicMode(userId)
+                    && !mLockscreenUserManager.shouldShowLockscreenNotifications())
+                    || (isLocked && !showOnKeyguard)) {
+                entry.row.setVisibility(View.GONE);
+            } else {
+                boolean wasGone = entry.row.getVisibility() == View.GONE;
+                if (wasGone) {
+                    entry.row.setVisibility(View.VISIBLE);
+                }
+                if (!isChildNotification && !entry.row.isRemoved()) {
+                    if (wasGone) {
+                        // notify the scroller of a child addition
+                        mListContainer.generateAddAnimation(entry.row,
+                                !showOnKeyguard /* fromMoreCard */);
+                    }
+                    visibleNotifications++;
+                }
+            }
+            if (row.isSummaryWithChildren()) {
+                List<ExpandableNotificationRow> notificationChildren =
+                        row.getNotificationChildren();
+                int size = notificationChildren.size();
+                for (int i = size - 1; i >= 0; i--) {
+                    stack.push(notificationChildren.get(i));
+                }
+            }
+        }
+
+        mPresenter.onUpdateRowStates();
+    }
+}
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 5ba6f6a..3ebeb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -247,19 +247,6 @@
         return null;
     }
 
-    /**
-     * Returns the
-     * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
-     * be triggered when a notification card is long-pressed.
-     */
-    @Override
-    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.
-        return null;
-    }
-
     @Override
     public void showBatteryView() {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -388,18 +375,6 @@
     }
 
     @Override
-    protected boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
-        // Because space is usually constrained in the auto use-case, there should not be a
-        // pinned notification when the shade has been expanded. Ensure this by not pinning any
-        // notification if the shade is already opened.
-        if (mPanelExpanded) {
-            return false;
-        }
-
-        return super.shouldPeek(entry, sbn);
-    }
-
-    @Override
     public void animateExpandNotificationsPanel() {
         // Because space is usually constrained in the auto use-case, there should not be a
         // pinned notification when the shade has been expanded. Ensure this by removing all heads-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 6d85fb3..fb3adf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -25,12 +25,14 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
+import com.android.systemui.tuner.TunerService;
 
 import java.io.PrintWriter;
 
-public class DozeParameters {
+public class DozeParameters implements TunerService.Tunable {
     private static final int MAX_DURATION = 60 * 1000;
     public static final String DOZE_SENSORS_WAKE_UP_FULLY = "doze_sensors_wake_up_fully";
 
@@ -40,10 +42,15 @@
     private static IntInOutMatcher sPickupSubtypePerformsProxMatcher;
     private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
 
+    private boolean mDozeAlwaysOn;
+
     public DozeParameters(Context context) {
         mContext = context;
         mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
         mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(context);
+
+        Dependency.get(TunerService.class).addTunable(this, Settings.Secure.DOZE_ALWAYS_ON,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
     }
 
     public void dump(PrintWriter pw) {
@@ -144,7 +151,7 @@
      * @return {@code true} if enabled and available.
      */
     public boolean getAlwaysOn() {
-        return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+        return mDozeAlwaysOn;
     }
 
     /**
@@ -207,6 +214,10 @@
         return mContext.getResources().getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
     }
 
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+    }
 
     /**
      * Parses a spec of the form `1,2,3,!5,*`. The resulting object will match numbers that are
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index df1ffda..46d9827 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -529,6 +529,13 @@
         KeyguardAffordanceView targetView = left ? mLeftIcon : mRightIcon;
         KeyguardAffordanceView otherView = left ? mRightIcon : mLeftIcon;
         startSwiping(targetView);
+
+        // Do not animate the circle expanding if the affordance isn't visible,
+        // otherwise the circle will be meaningless.
+        if (targetView.getVisibility() != View.VISIBLE) {
+            animate = false;
+        }
+
         if (animate) {
             fling(0, false, !left);
             updateIcon(otherView, 0.0f, 0, true, false, true, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index f058862..01b3b44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -822,8 +822,10 @@
         @Override
         public IconState getIcon() {
             mLeftIsVoiceAssist = canLaunchVoiceAssist();
+            final boolean showAffordance =
+                    getResources().getBoolean(R.bool.config_keyguardShowLeftAffordance);
             if (mLeftIsVoiceAssist) {
-                mIconState.isVisible = mUserSetupComplete;
+                mIconState.isVisible = mUserSetupComplete && showAffordance;
                 if (mLeftAssistIcon == null) {
                     mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
                 } else {
@@ -832,7 +834,7 @@
                 mIconState.contentDescription = mContext.getString(
                         R.string.accessibility_voice_assist_button);
             } else {
-                mIconState.isVisible = mUserSetupComplete && isPhoneVisible();
+                mIconState.isVisible = mUserSetupComplete && showAffordance && isPhoneVisible();
                 mIconState.drawable = mContext.getDrawable(R.drawable.ic_phone_24dp);
                 mIconState.contentDescription = mContext.getString(
                         R.string.accessibility_phone_button);
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 bed6d82..6f636aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -47,8 +47,7 @@
 /**
  * Class to detect gestures on the navigation bar.
  */
-public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureListener
-        implements TunerService.Tunable, GestureHelper {
+public class NavigationBarGestureHelper implements TunerService.Tunable, GestureHelper {
 
     private static final String TAG = "NavBarGestureHelper";
     private static final String KEY_DOCK_WINDOW_GESTURE = "overview_nav_bar_gesture";
@@ -72,11 +71,8 @@
     private Context mContext;
     private NavigationBarView mNavigationBarView;
     private boolean mIsVertical;
-    private boolean mIsRTL;
 
-    private final GestureDetector mTaskSwitcherDetector;
     private final int mScrollTouchSlop;
-    private final int mMinFlingVelocity;
     private final Matrix mTransformGlobalMatrix = new Matrix();
     private final Matrix mTransformLocalMatrix = new Matrix();
     private int mTouchDownX;
@@ -91,11 +87,8 @@
 
     public NavigationBarGestureHelper(Context context) {
         mContext = context;
-        ViewConfiguration configuration = ViewConfiguration.get(context);
         Resources r = context.getResources();
         mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
-        mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
-        mTaskSwitcherDetector = new GestureDetector(context, this);
         Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
     }
 
@@ -112,7 +105,6 @@
 
     public void setBarState(boolean isVertical, boolean isRTL) {
         mIsVertical = isVertical;
-        mIsRTL = isRTL;
     }
 
     private boolean proxyMotionEvents(MotionEvent event) {
@@ -161,11 +153,7 @@
             case MotionEvent.ACTION_UP:
                 break;
         }
-        if (!proxyMotionEvents(event)) {
-            // If we move more than a fixed amount, then start capturing for the
-            // task switcher detector, disabled when proxying motion events to launcher service
-            mTaskSwitcherDetector.onTouchEvent(event);
-        }
+        proxyMotionEvents(event);
         return result || (mDockWindowEnabled && interceptDockWindowEvent(event));
     }
 
@@ -306,7 +294,7 @@
     }
 
     public boolean onTouchEvent(MotionEvent event) {
-        boolean result = proxyMotionEvents(event) || mTaskSwitcherDetector.onTouchEvent(event);
+        boolean result = proxyMotionEvents(event);
         if (mDockWindowEnabled) {
             result |= handleDockWindowEvent(event);
         }
@@ -314,29 +302,6 @@
     }
 
     @Override
-    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-        float absVelX = Math.abs(velocityX);
-        float absVelY = Math.abs(velocityY);
-        boolean isValidFling = absVelX > mMinFlingVelocity &&
-                mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY);
-        if (isValidFling && mRecentsComponent != null) {
-            boolean showNext;
-            if (!mIsRTL) {
-                showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0);
-            } else {
-                // In RTL, vertical is still the same, but horizontal is flipped
-                showNext = mIsVertical ? (velocityY < 0) : (velocityX > 0);
-            }
-            if (showNext) {
-                mRecentsComponent.showNextAffiliatedTask();
-            } else {
-                mRecentsComponent.showPrevAffiliatedTask();
-            }
-        }
-        return true;
-    }
-
-    @Override
     public void onTuningChanged(String key, String newValue) {
         switch (key) {
             case KEY_DOCK_WINDOW_GESTURE:
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 b81a3b0..bd6421c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -26,7 +26,6 @@
 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;
@@ -37,6 +36,7 @@
     private final NavigationBarView mView;
     private final IStatusBarService mBarService;
     private final LightBarTransitionsController mLightTransitionsController;
+    private final boolean mAllowAutoDimWallpaperNotVisible;
     private boolean mWallpaperVisible;
 
     private boolean mLightsOut;
@@ -49,6 +49,8 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mLightTransitionsController = new LightBarTransitionsController(view.getContext(),
                 this::applyDarkIntensity);
+        mAllowAutoDimWallpaperNotVisible = view.getContext().getResources()
+                .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper);
 
         IWindowManager windowManagerService = Dependency.get(IWindowManager.class);
         Handler handler = Handler.getMain();
@@ -80,8 +82,8 @@
 
     @Override
     protected boolean isLightsOut(int mode) {
-        return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible
-                && mode != MODE_WARNING);
+        return super.isLightsOut(mode) || (mAllowAutoDimWallpaperNotVisible && mAutoDim
+                && !mWallpaperVisible && mode != MODE_WARNING);
     }
 
     public LightBarTransitionsController getLightTransitionsController() {
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 2796f0f..392581d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -199,8 +199,10 @@
         }
     }
 
-    private final OverviewProxyListener mOverviewProxyListener =
-            isConnected -> setSlippery(!isConnected);
+    private final OverviewProxyListener mOverviewProxyListener = isConnected -> {
+        setSlippery(!isConnected);
+        setDisabledFlags(mDisabledFlags, true);
+    };
 
     public NavigationBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
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 61dd22f..f0bd1f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -455,7 +455,7 @@
             mTopPaddingAdjustment = 0;
         } else {
             mClockPositionAlgorithm.setup(
-                    mStatusBar.getMaxKeyguardNotifications(),
+                    mStatusBar.getMaxNotificationsWhileLocked(),
                     getMaxPanelHeight(),
                     getExpandedHeight(),
                     mNotificationStackScroller.getNotGoneChildCount(),
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 c5349d1..2da1e4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -28,10 +28,6 @@
         .NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager
-        .FORCE_REMOTE_INPUT_HISTORY;
-import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -66,14 +62,12 @@
 import android.content.IntentSender;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -88,7 +82,6 @@
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -103,12 +96,9 @@
 import android.os.UserManager;
 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.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
@@ -139,7 +129,6 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.MessagingGroup;
 import com.android.internal.widget.MessagingMessage;
@@ -149,11 +138,9 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.ActivityStarterDelegate;
 import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.DejankUtils;
 import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.EventLogTags;
-import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
@@ -200,6 +187,7 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationEntryManager;
 import com.android.systemui.statusbar.NotificationGutsManager;
 import com.android.systemui.statusbar.NotificationInfo;
 import com.android.systemui.statusbar.NotificationListener;
@@ -209,13 +197,12 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
-import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.RowInflaterTask;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -239,7 +226,6 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.util.NotificationChannels;
-import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.volume.VolumeComponent;
 
 import java.io.FileDescriptor;
@@ -247,17 +233,12 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Stack;
 
 public class StatusBar extends SystemUI implements DemoMode,
         DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
-        OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
-        ActivatableNotificationView.OnActivatedListener,
-        ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
-        ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
+        OnHeadsUpChangedListener, CommandQueue.Callbacks,
         ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
     public static final boolean MULTIUSER_DEBUG = false;
 
@@ -270,10 +251,6 @@
     protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
     protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
 
-    protected static final boolean ENABLE_HEADS_UP = true;
-    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
-
-
     // Should match the values in PhoneWindowManager
     public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
     public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
@@ -309,7 +286,7 @@
     // Time after we abort the launch transition.
     private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000;
 
-    private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
+    protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
 
     private static final int STATUS_OR_NAV_TRANSIENT =
             View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
@@ -392,13 +369,6 @@
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
     private TextView mNotificationPanelDebugText;
 
-    /**
-     * {@code true} if notifications not part of a group should by default be rendered in their
-     * expanded state. If {@code false}, then only the first notification will be expanded if
-     * possible.
-     */
-    private boolean mAlwaysExpandNonGroupedNotification;
-
     // settings
     private QSPanel mQSPanel;
 
@@ -427,6 +397,8 @@
 
     private NotificationGutsManager mGutsManager;
     protected NotificationLogger mNotificationLogger;
+    protected NotificationEntryManager mEntryManager;
+    protected NotificationViewHierarchyManager mViewHierarchyManager;
 
     // for disabling the status bar
     private int mDisabled1 = 0;
@@ -478,23 +450,6 @@
     };
 
     protected final H mHandler = createHandler();
-    final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
-        @Override
-        public void onChange(boolean selfChange) {
-            boolean wasUsing = mUseHeadsUp;
-            mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
-                    && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
-                    mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
-                    Settings.Global.HEADS_UP_OFF);
-            Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
-            if (wasUsing != mUseHeadsUp) {
-                if (!mUseHeadsUp) {
-                    Log.d(TAG, "dismissing any existing heads up notification on disable event");
-                    mHeadsUpManager.releaseAllImmediately();
-                }
-            }
-        }
-    };
 
     private int mInteractingWindows;
     private boolean mAutohideSuspended;
@@ -588,7 +543,6 @@
         }
     };
 
-    private NotificationMessagingUtil mMessagingUtil;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private UserSwitcherController mUserSwitcherController;
     private NetworkController mNetworkController;
@@ -603,11 +557,9 @@
     private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     protected NotificationIconAreaController mNotificationIconAreaController;
     private boolean mReinflateNotificationsOnUserSwitched;
-    private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
     private boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
     private SysuiColorExtractor mColorExtractor;
-    private ForegroundServiceController mForegroundServiceController;
     private ScreenLifecycle mScreenLifecycle;
     @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
 
@@ -617,9 +569,6 @@
             goToLockedShade(null);
         }
     };
-    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
-            mTmpChildOrderMap = new HashMap<>();
-    private RankingMap mLatestRankingMap;
     private boolean mNoAnimationOnNextBarModeChange;
     private FalsingManager mFalsingManager;
 
@@ -639,8 +588,11 @@
     @Override
     public void start() {
         mGroupManager = Dependency.get(NotificationGroupManager.class);
+        mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
         mNotificationLogger = Dependency.get(NotificationLogger.class);
         mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
+        mNotificationListener =  Dependency.get(NotificationListener.class);
+        mGroupManager = Dependency.get(NotificationGroupManager.class);
         mNetworkController = Dependency.get(NetworkController.class);
         mUserSwitcherController = Dependency.get(UserSwitcherController.class);
         mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
@@ -649,27 +601,25 @@
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         mBatteryController = Dependency.get(BatteryController.class);
         mAssistManager = Dependency.get(AssistManager.class);
-        mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
         mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class);
         mGutsManager = Dependency.get(NotificationGutsManager.class);
+        mMediaManager = Dependency.get(NotificationMediaManager.class);
+        mEntryManager = Dependency.get(NotificationEntryManager.class);
+        mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
 
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
 
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
 
-        mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
-
         mDisplay = mWindowManager.getDefaultDisplay();
         updateDisplaySize();
 
         Resources res = mContext.getResources();
         mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src);
         mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
-        mAlwaysExpandNonGroupedNotification =
-                res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
 
         DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
         putComponent(StatusBar.class, this);
@@ -679,16 +629,12 @@
         mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
 
-        mNotificationData = new NotificationData(this);
-        mMessagingUtil = new NotificationMessagingUtil(mContext);
-
         mAccessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
-        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -698,8 +644,7 @@
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mLockPatternUtils = new LockPatternUtils(mContext);
 
-        mMediaManager = new NotificationMediaManager(this, mContext);
-        mLockscreenUserManager.setUpWithPresenter(this);
+        mMediaManager.setUpWithPresenter(this, mEntryManager);
 
         // Connect in to the status bar manager service
         mCommandQueue = getComponent(CommandQueue.class);
@@ -725,6 +670,7 @@
         mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter);
         mWallpaperChangedReceiver.onReceive(mContext, null);
 
+        mLockscreenUserManager.setUpWithPresenter(this, mEntryManager);
         mCommandQueue.disable(switches[0], switches[6], false /* animate */);
         setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
                 fullscreenStackBounds, dockedStackBounds);
@@ -739,8 +685,7 @@
         }
 
         // Set up the initial notification state.
-        mNotificationListener = Dependency.get(NotificationListener.class);
-        mNotificationListener.setUpWithPresenter(this);
+        mNotificationListener.setUpWithPresenter(this, mEntryManager);
 
         if (DEBUG) {
             Log.d(TAG, String.format(
@@ -774,15 +719,6 @@
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
 
-        mHeadsUpObserver.onChange(true); // set up
-        if (ENABLE_HEADS_UP) {
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true,
-                    mHeadsUpObserver);
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
-                    mHeadsUpObserver);
-        }
         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
         mUnlockMethodCache.addListener(this);
         startKeyguard();
@@ -817,7 +753,7 @@
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
-        mGutsManager.setUp(this, mStackScroller, mCheckSaveListener,
+        mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener,
                 key -> {
                     try {
                         mBarService.onNotificationSettingsViewed(key);
@@ -825,7 +761,7 @@
                         // if we're here we're dead
                     }
                 });
-        mNotificationLogger.setUpWithPresenter(this, mStackScroller);
+        mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller);
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -864,11 +800,13 @@
         mHeadsUpManager.addListener(mGroupManager);
         mHeadsUpManager.addListener(mVisualStabilityManager);
         mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
-        mNotificationData.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
         mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
         putComponent(HeadsUpManager.class, mHeadsUpManager);
 
+        mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
+        mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, mStackScroller);
+
         if (MULTIUSER_DEBUG) {
             mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
             mNotificationPanelDebugText.setVisibility(View.VISIBLE);
@@ -884,7 +822,7 @@
             // no window manager? good luck with that
         }
 
-        mStackScroller.setLongPressListener(getNotificationLongClicker());
+        mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker());
         mStackScroller.setStatusBar(this);
         mStackScroller.setGroupManager(mGroupManager);
         mStackScroller.setHeadsUpManager(mHeadsUpManager);
@@ -1107,7 +1045,7 @@
         MessagingGroup.dropCache();
         // start old BaseStatusBar.onDensityOrFontScaleChanged().
         if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
-            updateNotificationsOnDensityOrFontScaleChanged();
+            mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
         } else {
             mReinflateNotificationsOnUserSwitched = true;
         }
@@ -1171,20 +1109,6 @@
         }
     }
 
-    private void updateNotificationsOnDensityOrFontScaleChanged() {
-        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-        for (int i = 0; i < activeNotifications.size(); i++) {
-            Entry entry = activeNotifications.get(i);
-            boolean exposedGuts = mGutsManager.getExposedGuts() != null
-                    && entry.row.getGuts() == mGutsManager.getExposedGuts();
-            entry.row.onDensityOrFontScaleChanged();
-            if (exposedGuts) {
-                mGutsManager.setExposedGuts(entry.row.getGuts());
-                mGutsManager.bindGuts(entry.row);
-            }
-        }
-    }
-
     private void inflateSignalClusters() {
         if (mKeyguardStatusBar != null) reinflateSignalCluster(mKeyguardStatusBar);
     }
@@ -1298,7 +1222,7 @@
             mStackScroller.setDismissAllInProgress(false);
             for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
                 if (mStackScroller.canChildBeDismissed(rowToRemove)) {
-                    removeNotification(rowToRemove.getEntry().key, null);
+                    mEntryManager.removeNotification(rowToRemove.getEntry().key, null);
                 } else {
                     rowToRemove.resetTranslation();
                 }
@@ -1406,213 +1330,53 @@
         return true;
     }
 
-    void awakenDreams() {
-        SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+    @Override
+    public void onPerformRemoveNotification(StatusBarNotification n) {
+        if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
+            // We were showing a pulse for a notification, but no notifications are pulsing anymore.
+            // Finish the pulse.
+            mDozeScrimController.pulseOutNow();
+        }
     }
 
     @Override
-    public void addNotification(StatusBarNotification notification, RankingMap ranking) {
-        String key = notification.getKey();
-        if (DEBUG) Log.d(TAG, "addNotification key=" + key);
+    public void updateNotificationViews() {
+        // The function updateRowStates depends on both of these being non-null, so check them here.
+        // We may be called before they are set from DeviceProvisionedController's callback.
+        if (mStackScroller == null || mScrimController == null) return;
 
-        mNotificationData.updateRanking(ranking);
-        Entry shadeEntry = null;
-        try {
-            shadeEntry = createNotificationViews(notification);
-        } catch (InflationException e) {
-            handleInflationException(notification, e);
+        // Do not modify the notifications during collapse.
+        if (isCollapsing()) {
+            addPostCollapseAction(this::updateNotificationViews);
             return;
         }
-        boolean isHeadsUped = shouldPeek(shadeEntry);
-        if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
-            if (shouldSuppressFullScreenIntent(key)) {
-                if (DEBUG) {
-                    Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
-                }
-            } else if (mNotificationData.getImportance(key)
-                    < NotificationManager.IMPORTANCE_HIGH) {
-                if (DEBUG) {
-                    Log.d(TAG, "No Fullscreen intent: not important enough: "
-                            + key);
-                }
-            } else {
-                // Stop screensaver if the notification has a fullscreen intent.
-                // (like an incoming phone call)
-                awakenDreams();
 
-                // not immersive & a fullscreen alert should be shown
-                if (DEBUG)
-                    Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
-                try {
-                    EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
-                            key);
-                    notification.getNotification().fullScreenIntent.send();
-                    shadeEntry.notifyFullScreenIntentLaunched();
-                    mMetricsLogger.count("note_fullscreen", 1);
-                } catch (PendingIntent.CanceledException e) {
-                }
-            }
-        }
-        abortExistingInflation(key);
+        mViewHierarchyManager.updateNotificationViews();
 
-        mForegroundServiceController.addNotification(notification,
-                mNotificationData.getImportance(key));
+        updateSpeedBumpIndex();
+        updateClearAll();
+        updateEmptyShadeView();
 
-        mPendingNotifications.put(key, shadeEntry);
+        updateQsExpansionEnabled();
+
+        // Let's also update the icons
+        mNotificationIconAreaController.updateNotificationIcons(
+                mEntryManager.getNotificationData());
     }
 
-    private void abortExistingInflation(String key) {
-        if (mPendingNotifications.containsKey(key)) {
-            Entry entry = mPendingNotifications.get(key);
-            entry.abortTask();
-            mPendingNotifications.remove(key);
-        }
-        Entry addedEntry = mNotificationData.get(key);
-        if (addedEntry != null) {
-            addedEntry.abortTask();
-        }
-    }
-
-    private void addEntry(Entry shadeEntry) {
-        boolean isHeadsUped = shouldPeek(shadeEntry);
-        if (isHeadsUped) {
-            mHeadsUpManager.showNotification(shadeEntry);
-            // Mark as seen immediately
-            setNotificationShown(shadeEntry.notification);
-        }
-        addNotificationViews(shadeEntry);
+    @Override
+    public void onNotificationAdded(Entry shadeEntry) {
         // Recalculate the position of the sliding windows and the titles.
         setAreThereNotifications();
     }
 
     @Override
-    public void handleInflationException(StatusBarNotification notification, Exception e) {
-        handleNotificationError(notification, e.getMessage());
+    public void onNotificationUpdated(StatusBarNotification notification) {
+        setAreThereNotifications();
     }
 
     @Override
-    public void onAsyncInflationFinished(Entry entry) {
-        mPendingNotifications.remove(entry.key);
-        // If there was an async task started after the removal, we don't want to add it back to
-        // the list, otherwise we might get leaks.
-        boolean isNew = mNotificationData.get(entry.key) == null;
-        if (isNew && !entry.row.isRemoved()) {
-            addEntry(entry);
-        } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
-            mVisualStabilityManager.onLowPriorityUpdated(entry);
-            updateNotificationShade();
-        }
-        entry.row.setLowPriorityStateUpdated(false);
-    }
-
-    private boolean shouldSuppressFullScreenIntent(String key) {
-        if (isDeviceInVrMode()) {
-            return true;
-        }
-
-        if (mPowerManager.isInteractive()) {
-            return mNotificationData.shouldSuppressScreenOn(key);
-        } else {
-            return mNotificationData.shouldSuppressScreenOff(key);
-        }
-    }
-
-    @Override
-    public void updateNotificationRanking(RankingMap ranking) {
-        mNotificationData.updateRanking(ranking);
-        updateNotifications();
-    }
-
-    @Override
-    public void removeNotification(String key, RankingMap ranking) {
-        boolean deferRemoval = false;
-        abortExistingInflation(key);
-        if (mHeadsUpManager.isHeadsUp(key)) {
-            // 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.
-            boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
-                    && !FORCE_REMOTE_INPUT_HISTORY
-                    || !mVisualStabilityManager.isReorderingAllowed();
-            deferRemoval = !mHeadsUpManager.removeNotification(key,  ignoreEarliestRemovalTime);
-        }
-        mMediaManager.onNotificationRemoved(key);
-
-        Entry entry = mNotificationData.get(key);
-        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key)
-                && entry.row != null && !entry.row.isDismissed()) {
-            StatusBarNotification sbn = entry.notification;
-
-            Notification.Builder b = Notification.Builder
-                    .recoverBuilder(mContext, sbn.getNotification().clone());
-            CharSequence[] oldHistory = sbn.getNotification().extras
-                    .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
-            CharSequence[] newHistory;
-            if (oldHistory == null) {
-                newHistory = new CharSequence[1];
-            } else {
-                newHistory = new CharSequence[oldHistory.length + 1];
-                System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
-            }
-            newHistory[0] = String.valueOf(entry.remoteInputText);
-            b.setRemoteInputHistory(newHistory);
-
-            Notification newNotification = b.build();
-
-            // Undo any compatibility view inflation
-            newNotification.contentView = sbn.getNotification().contentView;
-            newNotification.bigContentView = sbn.getNotification().bigContentView;
-            newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
-
-            StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
-                    sbn.getOpPkg(),
-                    sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
-                    newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
-            boolean updated = false;
-            try {
-                updateNotificationInternal(newSbn, null);
-                updated = true;
-            } catch (InflationException e) {
-                deferRemoval = false;
-            }
-            if (updated) {
-                Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
-                mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
-                return;
-            }
-        }
-        if (deferRemoval) {
-            mLatestRankingMap = ranking;
-            mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
-            return;
-        }
-
-        if (mRemoteInputManager.onRemoveNotification(entry)) {
-            mLatestRankingMap = ranking;
-            return;
-        }
-
-        if (entry != null && mGutsManager.getExposedGuts() != null
-                && mGutsManager.getExposedGuts() == entry.row.getGuts()
-                && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
-            Log.w(TAG, "Keeping notification because it's showing guts. " + key);
-            mLatestRankingMap = ranking;
-            mGutsManager.setKeyToRemoveOnGutsClosed(key);
-            return;
-        }
-
-        if (entry != null) {
-            mForegroundServiceController.removeNotification(entry.notification);
-        }
-
-        if (entry != null && entry.row != null) {
-            entry.row.setRemoved();
-            mStackScroller.cleanUpViewState(entry.row);
-        }
-        // Let's remove the children if this was a summary
-        handleGroupSummaryRemoved(key);
-        StatusBarNotification old = removeNotificationViews(key, ranking);
+    public void onNotificationRemoved(String key, StatusBarNotification old) {
         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
 
         if (old != null) {
@@ -1629,195 +1393,6 @@
     }
 
     /**
-     * Ensures that the group children are cancelled immediately when the group summary is cancelled
-     * instead of waiting for the notification manager to send all cancels. Otherwise this could
-     * lead to flickers.
-     *
-     * 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
-     *
-     */
-    private void handleGroupSummaryRemoved(String key) {
-        Entry entry = mNotificationData.get(key);
-        if (entry != null && entry.row != null
-                && entry.row.isSummaryWithChildren()) {
-            if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
-                // We don't want to remove children for autobundled notifications as they are not
-                // always cancelled. We only remove them if they were dismissed by the user.
-                return;
-            }
-            List<ExpandableNotificationRow> notificationChildren =
-                    entry.row.getNotificationChildren();
-            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 foreground service notification which we can't remove!
-                    continue;
-                }
-                row.setKeepInParent(true);
-                // we need to set this state earlier as otherwise we might generate some weird
-                // animations
-                row.setRemoved();
-            }
-        }
-    }
-
-    protected void performRemoveNotification(StatusBarNotification n) {
-        Entry entry = mNotificationData.get(n.getKey());
-        mRemoteInputManager.onPerformRemoveNotification(n, entry);
-        // start old BaseStatusBar.performRemoveNotification.
-        final String pkg = n.getPackageName();
-        final String tag = n.getTag();
-        final int id = n.getId();
-        final int userId = n.getUserId();
-        try {
-            int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
-            if (isHeadsUp(n.getKey())) {
-                dismissalSurface = NotificationStats.DISMISSAL_PEEK;
-            } else if (mStackScroller.hasPulsingNotifications()) {
-                dismissalSurface = NotificationStats.DISMISSAL_AOD;
-            }
-            mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
-            removeNotification(n.getKey(), null);
-
-        } catch (RemoteException ex) {
-            // system process is dead if we're here.
-        }
-        if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
-            // We were showing a pulse for a notification, but no notifications are pulsing anymore.
-            // Finish the pulse.
-            mDozeScrimController.pulseOutNow();
-        }
-        // end old BaseStatusBar.performRemoveNotification.
-    }
-
-    private void updateNotificationShade() {
-        if (mStackScroller == null) return;
-
-        // Do not modify the notifications during collapse.
-        if (isCollapsing()) {
-            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++) {
-            Entry ent = activeNotifications.get(i);
-            if (ent.row.isDismissed() || ent.row.isRemoved()) {
-                // we don't want to update removed notifications because they could
-                // temporarily become children if they were isolated before.
-                continue;
-            }
-            int userId = ent.notification.getUserId();
-
-            // Display public version of the notification if we need to redact.
-            // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
-            // We can probably move some of this code there.
-            boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
-                    mLockscreenUserManager.getCurrentUserId());
-            boolean userPublic = devicePublic
-                    || mLockscreenUserManager.isLockscreenPublicMode(userId);
-            boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
-            boolean sensitive = userPublic && needsRedaction;
-            boolean deviceSensitive = devicePublic
-                    && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
-                    mLockscreenUserManager.getCurrentUserId());
-            ent.row.setSensitive(sensitive, deviceSensitive);
-            ent.row.setNeedsRedaction(needsRedaction);
-            if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
-                ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
-                        ent.row.getStatusBarNotification());
-                List<ExpandableNotificationRow> orderedChildren =
-                        mTmpChildOrderMap.get(summary);
-                if (orderedChildren == null) {
-                    orderedChildren = new ArrayList<>();
-                    mTmpChildOrderMap.put(summary, orderedChildren);
-                }
-                orderedChildren.add(ent.row);
-            } else {
-                toShow.add(ent.row);
-            }
-
-        }
-
-        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
-        for (int i=0; i< mStackScroller.getChildCount(); i++) {
-            View child = mStackScroller.getChildAt(i);
-            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
-                toRemove.add((ExpandableNotificationRow) child);
-            }
-        }
-
-        for (ExpandableNotificationRow remove : toRemove) {
-            if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
-                // we are only transferring this notification to its parent, don't generate an
-                // animation
-                mStackScroller.setChildTransferInProgress(true);
-            }
-            if (remove.isSummaryWithChildren()) {
-                remove.removeAllChildren();
-            }
-            mStackScroller.removeView(remove);
-            mStackScroller.setChildTransferInProgress(false);
-        }
-
-        removeNotificationChildren();
-
-        for (int i = 0; i < toShow.size(); i++) {
-            View v = toShow.get(i);
-            if (v.getParent() == null) {
-                mVisualStabilityManager.notifyViewAddition(v);
-                mStackScroller.addView(v);
-            }
-        }
-
-        addNotificationChildrenAndSort();
-
-        // So after all this work notifications still aren't sorted correctly.
-        // Let's do that now by advancing through toShow and mStackScroller in
-        // lock-step, making sure mStackScroller matches what we see in toShow.
-        int j = 0;
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View child = mStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow targetChild = toShow.get(j);
-            if (child != targetChild) {
-                // Oops, wrong notification at this position. Put the right one
-                // here and advance both lists.
-                if (mVisualStabilityManager.canReorderNotification(targetChild)) {
-                    mStackScroller.changeViewPosition(targetChild, i);
-                } else {
-                    mVisualStabilityManager.addReorderingAllowedCallback(this);
-                }
-            }
-            j++;
-
-        }
-
-        mVisualStabilityManager.onReorderingFinished();
-        // clear the map again for the next usage
-        mTmpChildOrderMap.clear();
-
-        updateRowStates();
-        updateSpeedBumpIndex();
-        updateClearAll();
-        updateEmptyShadeView();
-
-        updateQsExpansionEnabled();
-
-        // Let's also update the icons
-        mNotificationIconAreaController.updateNotificationIcons(mNotificationData);
-    }
-
-    /**
      * Disable QS if device not provisioned.
      * If the user switcher is simple then disable QS during setup because
      * the user intends to use the lock screen user switcher, QS in not needed.
@@ -1832,81 +1407,6 @@
                 && !ONLY_CORE_APPS);
     }
 
-    private void addNotificationChildrenAndSort() {
-        // Let's now add all notification children which are missing
-        boolean orderChanged = false;
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View view = mStackScroller.getChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
-            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
-            for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
-                    childIndex++) {
-                ExpandableNotificationRow childView = orderedChildren.get(childIndex);
-                if (children == null || !children.contains(childView)) {
-                    if (childView.getParent() != null) {
-                        Log.wtf(TAG, "trying to add a notification child that already has " +
-                                "a parent. class:" + childView.getParent().getClass() +
-                                "\n child: " + childView);
-                        // This shouldn't happen. We can recover by removing it though.
-                        ((ViewGroup) childView.getParent()).removeView(childView);
-                    }
-                    mVisualStabilityManager.notifyViewAddition(childView);
-                    parent.addChildNotification(childView, childIndex);
-                    mStackScroller.notifyGroupChildAdded(childView);
-                }
-            }
-
-            // Finally after removing and adding has been performed we can apply the order.
-            orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this);
-        }
-        if (orderChanged) {
-            mStackScroller.generateChildOrderChangedEvent();
-        }
-    }
-
-    private void removeNotificationChildren() {
-        // First let's remove all children which don't belong in the parents
-        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View view = mStackScroller.getChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
-            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
-            if (children != null) {
-                toRemove.clear();
-                for (ExpandableNotificationRow childRow : children) {
-                    if ((orderedChildren == null
-                            || !orderedChildren.contains(childRow))
-                            && !childRow.keepInParent()) {
-                        toRemove.add(childRow);
-                    }
-                }
-                for (ExpandableNotificationRow remove : toRemove) {
-                    parent.removeChildNotification(remove);
-                    if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) {
-                        // We only want to add an animation if the view is completely removed
-                        // otherwise it's just a transfer
-                        mStackScroller.notifyGroupChildRemoved(remove,
-                                parent.getChildrenContainer());
-                    }
-                }
-            }
-        }
-    }
-
     public void addQsTile(ComponentName tile) {
         mQSPanel.getHost().addTile(tile);
     }
@@ -1949,7 +1449,7 @@
     private void updateEmptyShadeView() {
         boolean showEmptyShadeView =
                 mState != StatusBarState.KEYGUARD &&
-                        mNotificationData.getActiveNotifications().size() == 0;
+                        mEntryManager.getNotificationData().getActiveNotifications().size() == 0;
         mNotificationPanel.showEmptyShadeView(showEmptyShadeView);
     }
 
@@ -1964,7 +1464,8 @@
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
             currentIndex++;
-            if (!mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) {
+            if (!mEntryManager.getNotificationData().isAmbient(
+                    row.getStatusBarNotification().getKey())) {
                 speedBumpIndex = currentIndex;
             }
         }
@@ -1976,15 +1477,9 @@
         return entry.row.getParent() instanceof NotificationStackScrollLayout;
     }
 
-    @Override
-    public void updateNotifications() {
-        mNotificationData.filterAndSort();
-
-        updateNotificationShade();
-    }
 
     public void requestNotificationUpdate() {
-        updateNotifications();
+        mEntryManager.updateNotifications();
     }
 
     protected void setAreThereNotifications() {
@@ -1993,7 +1488,7 @@
             final boolean clearable = hasActiveNotifications() &&
                     hasActiveClearableNotifications();
             Log.d(TAG, "setAreThereNotifications: N=" +
-                    mNotificationData.getActiveNotifications().size() + " any=" +
+                    mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" +
                     hasActiveNotifications() + " clearable=" + clearable);
         }
 
@@ -2278,9 +1773,8 @@
         }
 
         if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
-            mDisableNotificationAlerts =
-                    (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
-            mHeadsUpObserver.onChange(true);
+            mEntryManager.setDisableNotificationAlerts(
+                    (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0);
         }
 
         if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
@@ -2343,10 +1837,40 @@
         return getBarState() == StatusBarState.KEYGUARD;
     }
 
+    @Override
     public boolean isDozing() {
         return mDozing;
     }
 
+    @Override
+    public boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
+        if (mIsOccluded && !isDozing()) {
+            boolean devicePublic = mLockscreenUserManager.
+                    isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
+            boolean userPublic = devicePublic
+                    || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
+            boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
+            if (userPublic && needsRedaction) {
+                return false;
+            }
+        }
+
+        if (sbn.getNotification().fullScreenIntent != null) {
+            if (mAccessibilityManager.isTouchExplorationEnabled()) {
+                if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
+                return false;
+            } else if (isDozing()) {
+                // We never want heads up when we are dozing.
+                return false;
+            } else {
+                // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
+                return !mStatusBarKeyguardViewManager.isShowing()
+                        || mStatusBarKeyguardViewManager.isOccluded();
+            }
+        }
+        return true;
+    }
+
     @Override  // NotificationData.Environment
     public String getCurrentMediaNotificationKey() {
         return mMediaManager.getMediaNotificationKey();
@@ -2415,34 +1939,10 @@
 
     @Override
     public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
-        if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
-            removeNotification(entry.key, mLatestRankingMap);
-            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
-            if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
-                mLatestRankingMap = null;
-            }
-        } else {
-            updateNotificationRanking(null);
-            if (isHeadsUp) {
-                mDozeServiceHost.fireNotificationHeadsUp();
-            }
-        }
+        mEntryManager.onHeadsUpStateChanged(entry, isHeadsUp);
 
-    }
-
-    protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek,
-            boolean alertAgain) {
-        final boolean wasHeadsUp = isHeadsUp(key);
-        if (wasHeadsUp) {
-            if (!shouldPeek) {
-                // We don't want this to be interrupting anymore, lets remove it
-                mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
-            } else {
-                mHeadsUpManager.updateNotification(entry, alertAgain);
-            }
-        } else if (shouldPeek && alertAgain) {
-            // This notification was updated to be a heads-up, show it!
-            mHeadsUpManager.showNotification(entry);
+        if (isHeadsUp) {
+            mDozeServiceHost.fireNotificationHeadsUp();
         }
     }
 
@@ -2452,14 +1952,6 @@
         }
     }
 
-    public boolean isHeadsUp(String key) {
-        return mHeadsUpManager.isHeadsUp(key);
-    }
-
-    protected boolean isSnoozedPackage(StatusBarNotification sbn) {
-        return mHeadsUpManager.isSnoozed(sbn.getPackageName());
-    }
-
     public boolean isKeyguardCurrentlySecure() {
         return !mUnlockMethodCache.canSkipBouncer();
     }
@@ -2489,11 +1981,6 @@
         return mDozeScrimController != null && mDozeScrimController.isPulsing();
     }
 
-    @Override
-    public void onReorderingAllowed() {
-        updateNotifications();
-    }
-
     public boolean isLaunchTransitionFadingAway() {
         return mLaunchTransitionFadingAway;
     }
@@ -3127,14 +2614,6 @@
                     + " scroll " + mStackScroller.getScrollX()
                     + "," + mStackScroller.getScrollY());
         }
-        pw.print("  mPendingNotifications=");
-        if (mPendingNotifications.size() == 0) {
-            pw.println("null");
-        } else {
-            for (Entry entry : mPendingNotifications.values()) {
-                pw.println(entry.notification);
-            }
-        }
 
         pw.print("  mInteractingWindows="); pw.println(mInteractingWindows);
         pw.print("  mStatusBarWindowState=");
@@ -3146,8 +2625,7 @@
         pw.println(Settings.Global.zenModeToString(Settings.Global.getInt(
                 mContext.getContentResolver(), Settings.Global.ZEN_MODE,
                 Settings.Global.ZEN_MODE_OFF)));
-        pw.print("  mUseHeadsUp=");
-        pw.println(mUseHeadsUp);
+
         if (mStatusBarView != null) {
             dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
         }
@@ -3189,8 +2667,8 @@
         }
 
         if (DUMPTRUCK) {
-            synchronized (mNotificationData) {
-                mNotificationData.dump(pw, "  ");
+            synchronized (mEntryManager.getNotificationData()) {
+                mEntryManager.getNotificationData().dump(pw, "  ");
             }
 
             if (false) {
@@ -3250,19 +2728,20 @@
     private void addStatusBarWindow() {
         makeStatusBarView();
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
-        mRemoteInputManager.setUpWithPresenter(this, this, new RemoteInputController.Delegate() {
-            public void setRemoteInputActive(NotificationData.Entry entry,
-                    boolean remoteInputActive) {
-                mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
-            }
-            public void lockScrollTo(NotificationData.Entry entry) {
-                mStackScroller.lockScrollTo(entry.row);
-            }
-            public void requestDisallowLongPressAndDismiss() {
-                mStackScroller.requestDisallowLongPress();
-                mStackScroller.requestDisallowDismiss();
-            }
-        });
+        mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this,
+                new RemoteInputController.Delegate() {
+                    public void setRemoteInputActive(NotificationData.Entry entry,
+                            boolean remoteInputActive) {
+                        mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+                    }
+                    public void lockScrollTo(NotificationData.Entry entry) {
+                        mStackScroller.lockScrollTo(entry.row);
+                    }
+                    public void requestDisallowLongPressAndDismiss() {
+                        mStackScroller.requestDisallowLongPress();
+                        mStackScroller.requestDisallowDismiss();
+                    }
+                });
         mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
     }
 
@@ -3429,7 +2908,8 @@
     };
 
     public void resetUserExpandedStates() {
-        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
+        ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData()
+                .getActiveNotifications();
         final int notificationCount = activeNotifications.size();
         for (int i = 0; i < notificationCount; i++) {
             NotificationData.Entry entry = activeNotifications.get(i);
@@ -3472,7 +2952,7 @@
             Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
         }
 
-        updateRowStates();
+        mViewHierarchyManager.updateRowStates();
         mScreenPinningRequest.onConfigurationChanged();
     }
 
@@ -3484,12 +2964,12 @@
         if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
         animateCollapsePanels();
         updatePublicMode();
-        mNotificationData.filterAndSort();
+        mEntryManager.getNotificationData().filterAndSort();
         if (mReinflateNotificationsOnUserSwitched) {
-            updateNotificationsOnDensityOrFontScaleChanged();
+            mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
             mReinflateNotificationsOnUserSwitched = false;
         }
-        updateNotificationShade();
+        updateNotificationViews();
         mMediaManager.clearCurrentMediaNotification();
         setLockscreenUser(newUserId);
     }
@@ -3499,6 +2979,13 @@
         return mLockscreenUserManager;
     }
 
+    @Override
+    public void onBindRow(Entry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
+        row.setAboveShelfChangedListener(mAboveShelfObserver);
+        row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
+    }
+
     protected void setLockscreenUser(int newUserId) {
         mLockscreenWallpaper.setCurrentUser(newUserId);
         mScrimController.setCurrentUser(newUserId);
@@ -3559,7 +3046,8 @@
         try {
             // consider the transition from peek to expanded to be a panel open,
             // but not one that clears notification effects.
-            int notificationLoad = mNotificationData.getActiveNotifications().size();
+            int notificationLoad = mEntryManager.getNotificationData()
+                    .getActiveNotifications().size();
             mBarService.onPanelRevealed(false, notificationLoad);
         } catch (RemoteException ex) {
             // Won't fail unless the world has ended.
@@ -3577,7 +3065,8 @@
                     !isPresenterFullyCollapsed() &&
                             (mState == StatusBarState.SHADE
                                     || mState == StatusBarState.SHADE_LOCKED);
-            int notificationLoad = mNotificationData.getActiveNotifications().size();
+            int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications()
+                    .size();
             if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
                 notificationLoad = 1;
             }
@@ -3712,7 +3201,7 @@
         } catch (RemoteException e) {
             // Ignore.
         }
-        mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+        mEntryManager.destroy();
         // End old BaseStatusBar.destroy().
         if (mStatusBarWindow != null) {
             mWindowManager.removeViewImmediate(mStatusBarWindow);
@@ -4165,7 +3654,7 @@
         updateDozingState();
         updatePublicMode();
         updateStackScrollerState(goingToFullShade, fromShadeLocked);
-        updateNotifications();
+        mEntryManager.updateNotifications();
         checkBarModes();
         updateScrimController();
         updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
@@ -4236,7 +3725,7 @@
         mKeyguardIndicationController.setDozing(mDozing);
         mNotificationPanel.setDark(mDozing, animate);
         updateQsExpansionEnabled();
-        updateRowStates();
+        mViewHierarchyManager.updateRowStates();
         Trace.endSection();
     }
 
@@ -4440,7 +3929,8 @@
         }
     }
 
-    protected int getMaxKeyguardNotifications(boolean recompute) {
+    @Override
+    public int getMaxNotificationsWhileLocked(boolean recompute) {
         if (recompute) {
             mMaxKeyguardNotifications = Math.max(1,
                     mNotificationPanel.computeMaxKeyguardNotifications(
@@ -4450,8 +3940,8 @@
         return mMaxKeyguardNotifications;
     }
 
-    public int getMaxKeyguardNotifications() {
-        return getMaxKeyguardNotifications(false /* recompute */);
+    public int getMaxNotificationsWhileLocked() {
+        return getMaxNotificationsWhileLocked(false /* recompute */);
     }
 
     // TODO: Figure out way to remove these.
@@ -4679,7 +4169,7 @@
     @Override
     public void onWorkChallengeChanged() {
         updatePublicMode();
-        updateNotifications();
+        mEntryManager.updateNotifications();
         if (mPendingWorkRemoteInputView != null
                 && !mLockscreenUserManager.isAnyProfilePublicMode()) {
             // Expand notification panel and the notification row, then click on remote input view
@@ -4889,7 +4379,7 @@
     }
 
     public boolean hasActiveNotifications() {
-        return !mNotificationData.getActiveNotifications().isEmpty();
+        return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
     }
 
     @Override
@@ -5268,7 +4758,6 @@
     protected IStatusBarService mBarService;
 
     // all notifications
-    protected NotificationData mNotificationData;
     protected NotificationStackScrollLayout mStackScroller;
 
     protected NotificationGroupManager mGroupManager;
@@ -5280,22 +4769,17 @@
     private AboveShelfObserver mAboveShelfObserver;
 
     // handling reordering
-    protected final VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
-
+    protected VisualStabilityManager mVisualStabilityManager;
 
     protected AccessibilityManager mAccessibilityManager;
 
     protected boolean mDeviceInteractive;
 
     protected boolean mVisible;
-    protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
 
     // mScreenOnFromKeyguard && mVisible.
     private boolean mVisibleToUser;
 
-    protected boolean mUseHeadsUp = false;
-    protected boolean mDisableNotificationAlerts = false;
-
     protected DevicePolicyManager mDevicePolicyManager;
     protected PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -5304,7 +4788,6 @@
     private LockPatternUtils mLockPatternUtils;
     private DeviceProvisionedController mDeviceProvisionedController
             = Dependency.get(DeviceProvisionedController.class);
-    protected SystemServicesProxy mSystemServicesProxy;
 
     // UI-specific methods
 
@@ -5319,8 +4802,6 @@
     protected DismissView mDismissView;
     protected EmptyShadeView mEmptyShadeView;
 
-    private final NotificationClicker mNotificationClicker = new NotificationClicker();
-
     protected AssistManager mAssistManager;
 
     protected boolean mVrMode;
@@ -5345,14 +4826,6 @@
         return mVrMode;
     }
 
-    private final DeviceProvisionedListener mDeviceProvisionedListener =
-            new DeviceProvisionedListener() {
-        @Override
-        public void onDeviceProvisionedChanged() {
-            updateNotifications();
-        }
-    };
-
     private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -5377,6 +4850,121 @@
         }
     };
 
+    @Override
+    public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
+        Notification notification = sbn.getNotification();
+        final PendingIntent intent = notification.contentIntent != null
+                ? notification.contentIntent
+                : notification.fullScreenIntent;
+        final String notificationKey = sbn.getKey();
+
+        final boolean afterKeyguardGone = intent.isActivity()
+                && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+                mLockscreenUserManager.getCurrentUserId());
+        dismissKeyguardThenExecute(() -> {
+            // TODO: Some of this code may be able to move to NotificationEntryManager.
+            if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+                // Release the HUN notification to the shade.
+
+                if (isPresenterFullyCollapsed()) {
+                    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);
+            }
+            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 = () -> {
+                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();
+                    }
+                }
+
+                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 =
+                                () -> mEntryManager.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);
+    }
+
     protected NotificationListener mNotificationListener;
 
     protected void notifyUserAboutHiddenNotifications() {
@@ -5438,14 +5026,6 @@
         return mLockscreenUserManager.isCurrentProfile(notificationUserId);
     }
 
-    protected void setNotificationShown(StatusBarNotification n) {
-        try {
-            mNotificationListener.setNotificationsShown(new String[]{n.getKey()});
-        } catch (RuntimeException e) {
-            Log.d(TAG, "failed setNotificationsShown: ", e);
-        }
-    }
-
     @Override
     public NotificationGroupManager getGroupManager() {
         return mGroupManager;
@@ -5475,15 +5055,15 @@
         }
     }
 
-    protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
-        return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item);
-    }
-
     @Override
     public void toggleSplitScreen() {
         toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
     }
 
+    void awakenDreams() {
+        SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+    }
+
     @Override
     public void preloadRecentApps() {
         int msg = MSG_PRELOAD_RECENT_APPS;
@@ -5561,104 +5141,14 @@
         if (mState == StatusBarState.KEYGUARD) {
             // Since the number of notifications is determined based on the height of the view, we
             // need to update them.
-            int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
-            int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+            int maxBefore = getMaxNotificationsWhileLocked(false /* recompute */);
+            int maxNotifications = getMaxNotificationsWhileLocked(true /* recompute */);
             if (maxBefore != maxNotifications) {
-                updateRowStates();
+                mViewHierarchyManager.updateRowStates();
             }
         }
     }
 
-    protected void inflateViews(Entry entry, ViewGroup parent) {
-        PackageManager pmUser = getPackageManagerForUser(mContext,
-                entry.notification.getUser().getIdentifier());
-
-        final StatusBarNotification sbn = entry.notification;
-        if (entry.row != null) {
-            entry.reset();
-            updateNotification(entry, pmUser, sbn, entry.row);
-        } else {
-            new RowInflaterTask().inflate(mContext, parent, entry,
-                    row -> {
-                        bindRow(entry, pmUser, sbn, row);
-                        updateNotification(entry, pmUser, sbn, row);
-                    });
-        }
-
-    }
-
-    private void bindRow(Entry entry, PackageManager pmUser,
-            StatusBarNotification sbn, ExpandableNotificationRow row) {
-        row.setExpansionLogger(this, entry.notification.getKey());
-        row.setGroupManager(mGroupManager);
-        row.setHeadsUpManager(mHeadsUpManager);
-        row.setAboveShelfChangedListener(mAboveShelfObserver);
-        row.setOnExpandClickListener(this);
-        row.setInflationCallback(this);
-        row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
-        row.setLongPressListener(getNotificationLongClicker());
-        mRemoteInputManager.bindRow(row);
-
-        // Get the app name.
-        // Note that Notification.Builder#bindHeaderAppName has similar logic
-        // but since this field is used in the guts, it must be accurate.
-        // Therefore we will only show the application label, or, failing that, the
-        // package name. No substitutions.
-        final String pkg = sbn.getPackageName();
-        String appname = pkg;
-        try {
-            final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES
-                            | PackageManager.MATCH_DISABLED_COMPONENTS);
-            if (info != null) {
-                appname = String.valueOf(pmUser.getApplicationLabel(info));
-            }
-        } catch (NameNotFoundException e) {
-            // Do nothing
-        }
-        row.setAppName(appname);
-        row.setOnDismissRunnable(() ->
-                performRemoveNotification(row.getStatusBarNotification()));
-        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-        if (ENABLE_REMOTE_INPUT) {
-            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
-        }
-    }
-
-    private void updateNotification(Entry entry, PackageManager pmUser,
-            StatusBarNotification sbn, ExpandableNotificationRow row) {
-        row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
-        boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
-        boolean isUpdate = mNotificationData.get(entry.key) != null;
-        boolean wasLowPriority = row.isLowPriority();
-        row.setIsLowPriority(isLowPriority);
-        row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
-        // bind the click event to the content area
-        mNotificationClicker.register(row, sbn);
-
-        // Extract target SDK version.
-        try {
-            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
-            entry.targetSdk = info.targetSdkVersion;
-        } catch (NameNotFoundException ex) {
-            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
-        }
-        row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
-                && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-        entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
-
-        entry.row = row;
-        entry.row.setOnActivatedListener(this);
-
-        boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
-                mNotificationData.getImportance(sbn.getKey()));
-        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
-        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
-        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
-        row.updateNotification(entry);
-    }
-
     public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
         if (!isDeviceProvisioned()) return;
 
@@ -5702,166 +5192,15 @@
         }, afterKeyguardGone);
     }
 
-
-    private final class NotificationClicker implements View.OnClickListener {
-
-        @Override
-        public void onClick(final View v) {
-            if (!(v instanceof ExpandableNotificationRow)) {
-                Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
-                return;
-            }
-
-            wakeUpIfDozing(SystemClock.uptimeMillis(), v);
-
-            final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-            final StatusBarNotification sbn = row.getStatusBarNotification();
-            if (sbn == null) {
-                Log.e(TAG, "NotificationClicker called on an unclickable notification,");
-                return;
-            }
-
-            // Check if the notification is displaying the menu, if so slide notification back
-            if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
-                row.animateTranslateNotification(0);
-                return;
-            }
-
-            Notification notification = sbn.getNotification();
-            final PendingIntent intent = notification.contentIntent != null
-                    ? notification.contentIntent
-                    : notification.fullScreenIntent;
-            final String notificationKey = sbn.getKey();
-
-            // Mark notification for one frame.
-            row.setJustClicked(true);
-            DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
-
-            final boolean afterKeyguardGone = intent.isActivity()
-                    && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
-                            mLockscreenUserManager.getCurrentUserId());
-            dismissKeyguardThenExecute(() -> {
-                if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
-                    // Release the HUN notification to the shade.
-
-                    if (isPresenterFullyCollapsed()) {
-                        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);
-                }
-                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 = () -> {
-                    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();
-                        }
-                    }
-
-                    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);
+    private boolean shouldAutoCancel(StatusBarNotification sbn) {
+        int flags = sbn.getNotification().flags;
+        if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
+            return false;
         }
-
-        private boolean shouldAutoCancel(StatusBarNotification sbn) {
-            int flags = sbn.getNotification().flags;
-            if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
-                return false;
-            }
-            if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                return false;
-            }
-            return true;
+        if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+            return false;
         }
-
-        public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
-            Notification notification = sbn.getNotification();
-            if (notification.contentIntent != null || notification.fullScreenIntent != null) {
-                row.setOnClickListener(this);
-            } else {
-                row.setOnClickListener(null);
-            }
-        }
+        return true;
     }
 
     protected Bundle getActivityOptions() {
@@ -5904,127 +5243,10 @@
     }
 
     /**
-     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
-     * about the failure.
-     *
-     * WARNING: this will call back into us.  Don't hold any locks.
-     */
-    void handleNotificationError(StatusBarNotification n, String message) {
-        removeNotification(n.getKey(), null);
-        try {
-            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
-                    n.getInitialPid(), message, n.getUserId());
-        } catch (RemoteException ex) {
-            // The end is nigh.
-        }
-    }
-
-    protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
-        NotificationData.Entry entry = mNotificationData.remove(key, ranking);
-        if (entry == null) {
-            Log.w(TAG, "removeNotification for unknown key: " + key);
-            return null;
-        }
-        updateNotifications();
-        Dependency.get(LeakDetector.class).trackGarbage(entry);
-        return entry.notification;
-    }
-
-    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
-            throws InflationException {
-        if (DEBUG) {
-            Log.d(TAG, "createNotificationViews(notification=" + sbn);
-        }
-        NotificationData.Entry entry = new NotificationData.Entry(sbn);
-        Dependency.get(LeakDetector.class).trackInstance(entry);
-        entry.createIcons(mContext, sbn);
-        // Construct the expanded view.
-        inflateViews(entry, mStackScroller);
-        return entry;
-    }
-
-    protected void addNotificationViews(Entry entry) {
-        if (entry == null) {
-            return;
-        }
-        // Add the expanded view and icon.
-        mNotificationData.add(entry);
-        updateNotifications();
-    }
-
-    /**
      * Updates expanded, dimmed and locked states of notification rows.
      */
-    protected void updateRowStates() {
-        final int N = mStackScroller.getChildCount();
-
-        int visibleNotifications = 0;
-        boolean onKeyguard = mState == StatusBarState.KEYGUARD;
-        int maxNotifications = -1;
-        if (onKeyguard) {
-            maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
-        }
-        mStackScroller.setMaxDisplayedNotifications(maxNotifications);
-        Stack<ExpandableNotificationRow> stack = new Stack<>();
-        for (int i = N - 1; i >= 0; i--) {
-            View child = mStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            stack.push((ExpandableNotificationRow) child);
-        }
-        while(!stack.isEmpty()) {
-            ExpandableNotificationRow row = stack.pop();
-            NotificationData.Entry entry = row.getEntry();
-            boolean isChildNotification =
-                    mGroupManager.isChildInGroupWithSummary(entry.notification);
-
-            row.setOnKeyguard(onKeyguard);
-
-            if (!onKeyguard) {
-                // If mAlwaysExpandNonGroupedNotification is false, then only expand the
-                // very first notification and if it's not a child of grouped notifications.
-                row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
-                        || (visibleNotifications == 0 && !isChildNotification
-                        && !row.isLowPriority()));
-            }
-
-            entry.row.setShowAmbient(isDozing());
-            int userId = entry.notification.getUserId();
-            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
-                    entry.notification) && !entry.row.isRemoved();
-            boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
-                    .notification);
-            if (suppressedSummary
-                    || (mLockscreenUserManager.isLockscreenPublicMode(userId)
-                            && !mLockscreenUserManager.shouldShowLockscreenNotifications())
-                    || (onKeyguard && !showOnKeyguard)) {
-                entry.row.setVisibility(View.GONE);
-            } else {
-                boolean wasGone = entry.row.getVisibility() == View.GONE;
-                if (wasGone) {
-                    entry.row.setVisibility(View.VISIBLE);
-                }
-                if (!isChildNotification && !entry.row.isRemoved()) {
-                    if (wasGone) {
-                        // notify the scroller of a child addition
-                        mStackScroller.generateAddAnimation(entry.row,
-                                !showOnKeyguard /* fromMoreCard */);
-                    }
-                    visibleNotifications++;
-                }
-            }
-            if (row.isSummaryWithChildren()) {
-                List<ExpandableNotificationRow> notificationChildren =
-                        row.getNotificationChildren();
-                int size = notificationChildren.size();
-                for (int i = size - 1; i >= 0; i--) {
-                    stack.push(notificationChildren.get(i));
-                }
-            }
-        }
-        mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0);
-
+    @Override
+    public void onUpdateRowStates() {
         // The following views will be moved to the end of mStackScroller. This counter represents
         // the offset from the last child. Initialized to 1 for the very last position. It is post-
         // incremented in the following "changeViewPosition" calls so that its value is correct for
@@ -6047,163 +5269,10 @@
         mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount());
     }
 
-    // TODO: Move this to NotificationEntryManager once it is created.
-    private void updateNotificationInternal(StatusBarNotification notification,
-            RankingMap ranking) throws InflationException {
-        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
-
-        final String key = notification.getKey();
-        abortExistingInflation(key);
-        Entry entry = mNotificationData.get(key);
-        if (entry == null) {
-            return;
-        }
-        mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
-        mRemoteInputManager.onUpdateNotification(entry);
-
-        if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
-            mGutsManager.setKeyToRemoveOnGutsClosed(null);
-            Log.w(TAG, "Notification that was kept for guts was updated. " + key);
-        }
-
-        Notification n = notification.getNotification();
-        mNotificationData.updateRanking(ranking);
-
-        final StatusBarNotification oldNotification = entry.notification;
-        entry.notification = notification;
-        mGroupManager.onEntryUpdated(entry, oldNotification);
-
-        entry.updateIcons(mContext, notification);
-        inflateViews(entry, mStackScroller);
-
-        mForegroundServiceController.updateNotification(notification,
-                mNotificationData.getImportance(key));
-
-        boolean shouldPeek = shouldPeek(entry, notification);
-        boolean alertAgain = alertAgain(entry, n);
-
-        updateHeadsUp(key, entry, shouldPeek, alertAgain);
-        updateNotifications();
-
-        if (!notification.isClearable()) {
-            // The user may have performed a dismiss action on the notification, since it's
-            // not clearable we should snap it back.
-            mStackScroller.snapViewIfNeeded(entry.row);
-        }
-
-        if (DEBUG) {
-            // Is this for you?
-            boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
-            Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
-        }
-
-        setAreThereNotifications();
-    }
-
-    @Override
-    public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
-        try {
-            updateNotificationInternal(notification, ranking);
-        } catch (InflationException e) {
-            handleInflationException(notification, e);
-        }
-    }
-
     protected void notifyHeadsUpGoingToSleep() {
         maybeEscalateHeadsUp();
     }
 
-    private boolean alertAgain(Entry oldEntry, Notification newNotification) {
-        return oldEntry == null || !oldEntry.hasInterrupted()
-                || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
-    }
-
-    protected boolean shouldPeek(Entry entry) {
-        return shouldPeek(entry, entry.notification);
-    }
-
-    protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
-        if (!mUseHeadsUp || isDeviceInVrMode()) {
-            if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
-            return false;
-        }
-
-        if (mNotificationData.shouldFilterOut(sbn)) {
-            if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
-            return false;
-        }
-
-        boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
-
-        if (!inUse && !isDozing()) {
-            if (DEBUG) {
-                Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (!isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
-            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
-            return false;
-        }
-
-        if (isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) {
-            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
-            return false;
-        }
-
-        if (entry.hasJustLaunchedFullScreenIntent()) {
-            if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
-            return false;
-        }
-
-        if (isSnoozedPackage(sbn)) {
-            if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
-            return false;
-        }
-
-        // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
-        int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
-                : NotificationManager.IMPORTANCE_HIGH;
-        if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
-            if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
-            return false;
-        }
-
-        if (mIsOccluded && !isDozing()) {
-            boolean devicePublic = mLockscreenUserManager.
-                    isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
-            boolean userPublic = devicePublic
-                    || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
-            boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
-            if (userPublic && needsRedaction) {
-                return false;
-            }
-        }
-
-        if (sbn.getNotification().fullScreenIntent != null) {
-            if (mAccessibilityManager.isTouchExplorationEnabled()) {
-                if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
-                return false;
-            } else if (mDozing) {
-                // We never want heads up when we are dozing.
-                return false;
-            } else {
-                // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
-                return !mStatusBarKeyguardViewManager.isShowing()
-                        || mStatusBarKeyguardViewManager.isOccluded();
-            }
-        }
-
-        // Don't peek notifications that are suppressed due to group alert behavior
-        if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
-            if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
-            return false;
-        }
-
-        return true;
-    }
-
     /**
      * @return Whether the security bouncer from Keyguard is showing.
      */
@@ -6233,17 +5302,6 @@
         return contextForUser.getPackageManager();
     }
 
-    @Override
-    public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
-        mUiOffloadThread.submit(() -> {
-            try {
-                mBarService.onNotificationExpansionChanged(key, userAction, expanded);
-            } catch (RemoteException e) {
-                // Ignore.
-            }
-        });
-    }
-
     public boolean isKeyguardSecure() {
         if (mStatusBarKeyguardViewManager == null) {
             // startKeyguard() hasn't been called yet, so we don't know.
@@ -6291,20 +5349,10 @@
     }
 
     @Override
-    public NotificationData getNotificationData() {
-        return mNotificationData;
-    }
-
-    @Override
     public Handler getHandler() {
         return mHandler;
     }
 
-    @Override
-    public RankingMap getLatestRankingMap() {
-        return mLatestRankingMap;
-    }
-
     private final NotificationInfo.CheckSaveListener mCheckSaveListener =
             (Runnable saveImportance, StatusBarNotification sbn) -> {
                 // If the user has security enabled, show challenge if the setting is changed.
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 fe39a89..369e7ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -80,6 +80,8 @@
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationListContainer;
+import com.android.systemui.statusbar.NotificationLogger;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.StackScrollerDecorView;
@@ -112,7 +114,8 @@
 public class NotificationStackScrollLayout extends ViewGroup
         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
+        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider,
+        NotificationListContainer {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -207,7 +210,7 @@
      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
      */
     private float mOverScrolledBottomPixels;
-    private OnChildLocationsChangedListener mListener;
+    private NotificationLogger.OnChildLocationsChangedListener mListener;
     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
@@ -447,6 +450,7 @@
         }
     }
 
+    @Override
     public NotificationSwipeActionHelper getSwipeActionHelper() {
         return mSwipeHelper;
     }
@@ -614,7 +618,9 @@
         mNoAmbient = noAmbient;
     }
 
-    public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
+    @Override
+    public void setChildLocationsChangedListener(
+            NotificationLogger.OnChildLocationsChangedListener listener) {
         mListener = listener;
     }
 
@@ -1325,6 +1331,7 @@
                 true /* isDismissAll */);
     }
 
+    @Override
     public void snapViewIfNeeded(ExpandableNotificationRow child) {
         boolean animate = mIsExpanded || isPinnedHeadsUp(child);
         // If the child is showing the notification menu snap to that
@@ -1333,6 +1340,11 @@
     }
 
     @Override
+    public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+        return this;
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
@@ -2053,6 +2065,7 @@
         return mAmbientState.isPulsing(entry);
     }
 
+    @Override
     public boolean hasPulsingNotifications() {
         return mPulsing != null;
     }
@@ -2610,10 +2623,7 @@
         }
     }
 
-    /**
-     * Called when a notification is removed from the shade. This cleans up the state for a given
-     * view.
-     */
+    @Override
     public void cleanUpViewState(View child) {
         if (child == mTranslatingParentView) {
             mTranslatingParentView = null;
@@ -2922,10 +2932,12 @@
         }
     }
 
+    @Override
     public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
         onViewRemovedInternal(row, childrenContainer);
     }
 
+    @Override
     public void notifyGroupChildAdded(View row) {
         onViewAddedInternal(row);
     }
@@ -2963,12 +2975,8 @@
         return mNeedsAnimation
                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
     }
-    /**
-     * Generate an animation for an added child view.
-     *
-     * @param child The view to be added.
-     * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
-     */
+
+    @Override
     public void generateAddAnimation(View child, boolean fromMoreCard) {
         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
             // Generate Animations
@@ -2984,12 +2992,7 @@
         }
     }
 
-    /**
-     * Change the position of child to a new location
-     *
-     * @param child the view to change the position for
-     * @param newIndex the new index
-     */
+    @Override
     public void changeViewPosition(View child, int newIndex) {
         int currentIndex = indexOfChild(child);
         if (child != null && child.getParent() == this && currentIndex != newIndex) {
@@ -3705,7 +3708,7 @@
     private void applyCurrentState() {
         mCurrentStackScrollState.apply();
         if (mListener != null) {
-            mListener.onChildLocationsChanged(this);
+            mListener.onChildLocationsChanged();
         }
         runAnimationFinishedRunnables();
         setAnimationRunning(false);
@@ -4189,6 +4192,26 @@
         }
     }
 
+    @Override
+    public int getContainerChildCount() {
+        return getChildCount();
+    }
+
+    @Override
+    public View getContainerChildAt(int i) {
+        return getChildAt(i);
+    }
+
+    @Override
+    public void removeContainerView(View v) {
+        removeView(v);
+    }
+
+    @Override
+    public void addContainerView(View v) {
+        addView(v);
+    }
+
     public void runAfterAnimationFinished(Runnable runnable) {
         mAnimationFinishedRunnables.add(runnable);
     }
@@ -4445,13 +4468,6 @@
     }
 
     /**
-     * A listener that is notified when some child locations might have changed.
-     */
-    public interface OnChildLocationsChangedListener {
-        void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
-    }
-
-    /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
     public interface OnEmptySpaceClickListener {
@@ -4706,6 +4722,7 @@
         }
     }
 
+    @Override
     public void resetExposedMenuView(boolean animate, boolean force) {
         mSwipeHelper.resetExposedMenuView(animate, force);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
index e117969..0a3e34e 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
@@ -68,7 +68,6 @@
         String appName = mResolveInfo.loadLabel(packageManager).toString();
 
         final AlertController.AlertParams ap = mAlertParams;
-        ap.mIcon = mResolveInfo.loadIcon(packageManager);
         ap.mTitle = appName;
         if (mDevice == null) {
             ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName,
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
index 4606aee..238407a 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
@@ -90,7 +90,6 @@
         String appName = aInfo.loadLabel(packageManager).toString();
 
         final AlertController.AlertParams ap = mAlertParams;
-        ap.mIcon = aInfo.loadIcon(packageManager);
         ap.mTitle = appName;
         if (mDevice == null) {
             ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 2129790..4464f75 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -168,7 +168,7 @@
     }
 
     protected int getAudioManagerStreamMinVolume(int stream) {
-        return mAudio.getStreamMinVolume(stream);
+        return mAudio.getStreamMinVolumeInt(stream);
     }
 
     public void register() {
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 9d44895..066cfe5 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -46,7 +46,12 @@
     android-support-v7-mediarouter \
     android-support-v7-palette \
     android-support-v14-preference \
-    android-support-v17-leanback
+    android-support-v17-leanback \
+    android-slices-core \
+    android-slices-view \
+    android-slices-builders \
+    apptoolkit-arch-core-runtime \
+    apptoolkit-lifecycle-extensions \
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     metrics-helper-lib \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
index 3e6bd7e..2398fd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
@@ -61,7 +61,7 @@
 
     @Override
     public void dozeTimeTick() {
-        throw new RuntimeException("not implemented");
+        // Nothing to do in here. Real host would just update the UI.
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
index 8e7f83d..2705bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
@@ -16,33 +16,90 @@
 
 package com.android.systemui.doze;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.IWallpaperManager;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.support.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.DozeParameters;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 
 @RunWith(JUnit4.class)
 @SmallTest
 public class DozeWallpaperStateTest extends SysuiTestCase {
 
+    private DozeWallpaperState mDozeWallpaperState;
+    @Mock IWallpaperManager mIWallpaperManager;
+    @Mock DozeParameters mDozeParameters;
+    @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDozeWallpaperState = new DozeWallpaperState(mIWallpaperManager, mDozeParameters,
+                mKeyguardUpdateMonitor);
+    }
+
     @Test
     public void testDreamNotification() throws RemoteException {
-        IWallpaperManager wallpaperManagerService = mock(IWallpaperManager.class);
-        DozeWallpaperState dozeWallpaperState = new DozeWallpaperState(wallpaperManagerService);
-        dozeWallpaperState.transitionTo(DozeMachine.State.UNINITIALIZED,
+        // Pre-condition
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+
+        mDozeWallpaperState.transitionTo(DozeMachine.State.UNINITIALIZED,
                 DozeMachine.State.DOZE_AOD);
-        verify(wallpaperManagerService).setInAmbientMode(eq(true));
-        dozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH);
-        verify(wallpaperManagerService).setInAmbientMode(eq(false));
+        verify(mIWallpaperManager).setInAmbientMode(eq(true), anyBoolean());
+        mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH);
+        verify(mIWallpaperManager).setInAmbientMode(eq(false), anyBoolean());
+
+        // Make sure we're sending false when AoD is off
+        reset(mDozeParameters);
+        mDozeWallpaperState.transitionTo(DozeMachine.State.FINISH, DozeMachine.State.DOZE_AOD);
+        verify(mIWallpaperManager).setInAmbientMode(eq(false), anyBoolean());
+    }
+
+    @Test
+    public void testAnimates_whenSupported() throws RemoteException {
+        // Pre-conditions
+        when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+        when(mDozeParameters.getCanControlScreenOffAnimation()).thenReturn(true);
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+
+        mDozeWallpaperState.transitionTo(DozeMachine.State.UNINITIALIZED,
+                DozeMachine.State.DOZE_AOD);
+        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(true));
+
+        mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH);
+        verify(mIWallpaperManager).setInAmbientMode(eq(false), eq(true));
+    }
+
+    @Test
+    public void testDoesNotAnimate_whenNotSupported() throws RemoteException {
+        // Pre-conditions
+        when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
+        when(mDozeParameters.getCanControlScreenOffAnimation()).thenReturn(false);
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+
+        mDozeWallpaperState.transitionTo(DozeMachine.State.UNINITIALIZED,
+                DozeMachine.State.DOZE_AOD);
+        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(false));
+
+        mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH);
+        verify(mIWallpaperManager).setInAmbientMode(eq(false), eq(false));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 4eae342..be28569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -16,11 +16,10 @@
 
 package com.android.systemui.keyguard;
 
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
+import androidx.app.slice.Slice;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Debug;
 import android.os.Handler;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -34,6 +33,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -63,7 +65,8 @@
     @Test
     public void returnsValidSlice() {
         Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
-        SliceItem text = SliceQuery.find(slice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE,
+        SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT,
+                android.app.slice.Slice.HINT_TITLE,
                 null /* nonHints */);
         Assert.assertNotNull("Slice must provide a title.", text);
     }
@@ -78,9 +81,10 @@
 
     @Test
     public void updatesClock() {
+        mProvider.mUpdateClockInvokations = 0;
         mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK));
         TestableLooper.get(this).processAllMessages();
-        Assert.assertEquals("Clock should have been updated.", 2 /* expected */,
+        Assert.assertEquals("Clock should have been updated.", 1 /* expected */,
                 mProvider.mUpdateClockInvokations);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
new file mode 100644
index 0000000..0a68389
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+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.app.ActivityManager;
+import android.app.Notification;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.widget.FrameLayout;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+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 java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationEntryManagerTest extends SysuiTestCase {
+    private static final String TEST_PACKAGE_NAME = "test";
+    private static final int TEST_UID = 0;
+
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private ExpandableNotificationRow mRow;
+    @Mock private NotificationListContainer mListContainer;
+    @Mock private NotificationEntryManager.Callback mCallback;
+    @Mock private HeadsUpManager mHeadsUpManager;
+    @Mock private NotificationListenerService.RankingMap mRankingMap;
+    @Mock private RemoteInputController mRemoteInputController;
+    @Mock private IStatusBarService mBarService;
+
+    // Dependency mocks:
+    @Mock private ForegroundServiceController mForegroundServiceController;
+    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private NotificationGutsManager mGutsManager;
+    @Mock private NotificationRemoteInputManager mRemoteInputManager;
+    @Mock private NotificationMediaManager mMediaManager;
+    @Mock private NotificationListener mNotificationListener;
+    @Mock private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private MetricsLogger mMetricsLogger;
+
+    private NotificationData.Entry mEntry;
+    private StatusBarNotification mSbn;
+    private Handler mHandler;
+    private TestableNotificationEntryManager mEntryManager;
+    private CountDownLatch mCountDownLatch;
+
+    private class TestableNotificationEntryManager extends NotificationEntryManager {
+        private final CountDownLatch mCountDownLatch;
+
+        public TestableNotificationEntryManager(Context context, IStatusBarService barService) {
+            super(context);
+            mBarService = barService;
+            mCountDownLatch = new CountDownLatch(1);
+            mUseHeadsUp = true;
+        }
+
+        @Override
+        public void onAsyncInflationFinished(NotificationData.Entry entry) {
+            super.onAsyncInflationFinished(entry);
+
+            mCountDownLatch.countDown();
+        }
+
+        public CountDownLatch getCountDownLatch() {
+            return mCountDownLatch;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(ForegroundServiceController.class,
+                mForegroundServiceController);
+        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
+                mLockscreenUserManager);
+        mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
+        mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
+        mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager);
+        mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
+        mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
+        mDependency.injectTestDependency(DeviceProvisionedController.class,
+                mDeviceProvisionedController);
+        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+
+        mHandler = new Handler(Looper.getMainLooper());
+        mCountDownLatch = new CountDownLatch(1);
+
+        when(mPresenter.getHandler()).thenReturn(mHandler);
+        when(mPresenter.getNotificationLockscreenUserManager()).thenReturn(mLockscreenUserManager);
+        when(mPresenter.getGroupManager()).thenReturn(mGroupManager);
+        when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
+        when(mListContainer.getViewParentForNotification(any())).thenReturn(
+                new FrameLayout(mContext));
+
+        Notification.Builder n = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
+                0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+        mEntry = new NotificationData.Entry(mSbn);
+        mEntry.expandedIcon = mock(StatusBarIconView.class);
+
+        mEntryManager = new TestableNotificationEntryManager(mContext, mBarService);
+        mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mCallback, mHeadsUpManager);
+    }
+
+    @Test
+    public void testAddNotification() throws Exception {
+        com.android.systemui.util.Assert.isNotMainThread();
+
+        doAnswer(invocation -> {
+            mCountDownLatch.countDown();
+            return null;
+        }).when(mCallback).onBindRow(any(), any(), any(), any());
+
+        // Post on main thread, otherwise we will be stuck waiting here for the inflation finished
+        // callback forever, since it won't execute until the tests ends.
+        mHandler.post(() -> {
+            mEntryManager.addNotification(mSbn, mRankingMap);
+        });
+        assertTrue(mCountDownLatch.await(1, TimeUnit.MINUTES));
+        assertTrue(mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES));
+        waitForIdleSync(mHandler);
+
+        // Check that no inflation error occurred.
+        verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+                any(), anyInt());
+        verify(mForegroundServiceController).addNotification(eq(mSbn), anyInt());
+
+        // Row inflation:
+        ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass(
+                NotificationData.Entry.class);
+        verify(mCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any());
+        NotificationData.Entry entry = entryCaptor.getValue();
+        verify(mRemoteInputManager).bindRow(entry.row);
+
+        // Row content inflation:
+        verify(mCallback).onNotificationAdded(entry);
+        verify(mPresenter).updateNotificationViews();
+
+        assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry);
+        assertNotNull(entry.row);
+    }
+
+    @Test
+    public void testUpdateNotification() throws Exception {
+        com.android.systemui.util.Assert.isNotMainThread();
+
+        mEntryManager.getNotificationData().add(mEntry);
+
+        mHandler.post(() -> {
+            mEntryManager.updateNotification(mSbn, mRankingMap);
+        });
+        // Wait for content update.
+        mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES);
+        waitForIdleSync(mHandler);
+
+        verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+                any(), anyInt());
+
+        verify(mRemoteInputManager).onUpdateNotification(mEntry);
+        verify(mPresenter).updateNotificationViews();
+        verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt());
+        verify(mCallback).onNotificationUpdated(mSbn);
+        assertNotNull(mEntry.row);
+    }
+
+    @Test
+    public void testRemoveNotification() throws Exception {
+        com.android.systemui.util.Assert.isNotMainThread();
+
+        mEntry.row = mRow;
+        mEntryManager.getNotificationData().add(mEntry);
+
+        mHandler.post(() -> {
+            mEntryManager.removeNotification(mSbn.getKey(), mRankingMap);
+        });
+        waitForIdleSync(mHandler);
+
+        verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+                any(), anyInt());
+
+        verify(mMediaManager).onNotificationRemoved(mSbn.getKey());
+        verify(mRemoteInputManager).onRemoveNotification(mEntry);
+        verify(mForegroundServiceController).removeNotification(mSbn);
+        verify(mListContainer).cleanUpViewState(mRow);
+        verify(mPresenter).updateNotificationViews();
+        verify(mCallback).onNotificationRemoved(mSbn.getKey(), mSbn);
+        verify(mRow).setRemoved();
+
+        assertNull(mEntryManager.getNotificationData().get(mSbn.getKey()));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index ccc3006..ef5f071 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -19,7 +19,6 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -38,6 +37,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -49,40 +50,45 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
 
-    private NotificationPresenter mPresenter;
-    private Handler mHandler;
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private NotificationListenerService.RankingMap mRanking;
+    @Mock private NotificationData mNotificationData;
+
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotificationRemoteInputManager mRemoteInputManager;
+
     private NotificationListener mListener;
+    private Handler mHandler;
     private StatusBarNotification mSbn;
-    private NotificationListenerService.RankingMap mRanking;
     private Set<String> mKeysKeptForRemoteInput;
-    private NotificationData mNotificationData;
-    private NotificationRemoteInputManager mRemoteInputManager;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(NotificationRemoteInputManager.class,
+                mRemoteInputManager);
+
         mHandler = new Handler(Looper.getMainLooper());
-        mPresenter = mock(NotificationPresenter.class);
-        mNotificationData = mock(NotificationData.class);
-        mRanking = mock(NotificationListenerService.RankingMap.class);
-        mRemoteInputManager = mock(NotificationRemoteInputManager.class);
         mKeysKeptForRemoteInput = new HashSet<>();
 
         when(mPresenter.getHandler()).thenReturn(mHandler);
-        when(mPresenter.getNotificationData()).thenReturn(mNotificationData);
+        when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
         when(mRemoteInputManager.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput);
 
-        mListener = new NotificationListener(mRemoteInputManager, mContext);
+        mListener = new NotificationListener(mContext);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
                 new Notification(), UserHandle.CURRENT, null, 0);
 
-        mListener.setUpWithPresenter(mPresenter);
+        mListener.setUpWithPresenter(mPresenter, mEntryManager);
     }
 
     @Test
     public void testNotificationAddCallsAddNotification() {
         mListener.onNotificationPosted(mSbn, mRanking);
         waitForIdleSync(mHandler);
-        verify(mPresenter).addNotification(mSbn, mRanking);
+        verify(mEntryManager).addNotification(mSbn, mRanking);
     }
 
     @Test
@@ -98,14 +104,14 @@
         when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationData.Entry(mSbn));
         mListener.onNotificationPosted(mSbn, mRanking);
         waitForIdleSync(mHandler);
-        verify(mPresenter).updateNotification(mSbn, mRanking);
+        verify(mEntryManager).updateNotification(mSbn, mRanking);
     }
 
     @Test
     public void testNotificationRemovalCallsRemoveNotification() {
         mListener.onNotificationRemoved(mSbn, mRanking);
         waitForIdleSync(mHandler);
-        verify(mPresenter).removeNotification(mSbn.getKey(), mRanking);
+        verify(mEntryManager).removeNotification(mSbn.getKey(), mRanking);
     }
 
     @Test
@@ -113,6 +119,6 @@
         mListener.onNotificationRankingUpdate(mRanking);
         waitForIdleSync(mHandler);
         // RankingMap may be modified by plugins.
-        verify(mPresenter).updateNotificationRanking(any());
+        verify(mEntryManager).updateNotificationRanking(any());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 4045995..cb8f7ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -22,7 +22,6 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -49,42 +48,47 @@
 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 NotificationLockscreenUserManagerTest extends SysuiTestCase {
-    private NotificationPresenter mPresenter;
-    private TestNotificationLockscreenUserManager mLockscreenUserManager;
-    private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private UserManager mUserManager;
+
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private DeviceProvisionedController mDeviceProvisionedController;
+
     private int mCurrentUserId;
     private Handler mHandler;
-    private UserManager mUserManager;
+    private TestNotificationLockscreenUserManager mLockscreenUserManager;
 
     @Before
     public void setUp() {
-        mUserManager = mock(UserManager.class);
-        mContext.addMockSystemService(UserManager.class, mUserManager);
+        MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(DeviceProvisionedController.class,
+                mDeviceProvisionedController);
+
         mHandler = new Handler(Looper.getMainLooper());
-        mDependency.injectMockDependency(DeviceProvisionedController.class);
-        mDeviceProvisionedController = mDependency.get(DeviceProvisionedController.class);
-        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
-        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
-                mLockscreenUserManager);
+        mContext.addMockSystemService(UserManager.class, mUserManager);
+        mCurrentUserId = ActivityManager.getCurrentUser();
 
         when(mUserManager.getProfiles(mCurrentUserId)).thenReturn(Lists.newArrayList(
                 new UserInfo(mCurrentUserId, "", 0), new UserInfo(mCurrentUserId + 1, "", 0)));
-
-        mPresenter = mock(NotificationPresenter.class);
         when(mPresenter.getHandler()).thenReturn(mHandler);
-        mLockscreenUserManager.setUpWithPresenter(mPresenter);
-        mCurrentUserId = ActivityManager.getCurrentUser();
+
+        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+        mLockscreenUserManager.setUpWithPresenter(mPresenter, mEntryManager);
     }
 
     @Test
     public void testLockScreenShowNotificationsChangeUpdatesNotifications() {
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
-        verify(mPresenter, times(1)).updateNotifications();
+        verify(mEntryManager, times(1)).updateNotifications();
     }
 
     @Test
@@ -123,7 +127,7 @@
     public void testSettingsObserverUpdatesNotifications() {
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
         mLockscreenUserManager.getSettingsObserverForTest().onChange(false);
-        verify(mPresenter, times(1)).updateNotifications();
+        verify(mEntryManager, times(1)).updateNotifications();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
index 142ce63..726810e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
@@ -36,7 +36,6 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.UiOffloadThread;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 import com.google.android.collect.Lists;
 
@@ -55,12 +54,15 @@
     private static final int TEST_UID = 0;
 
     @Mock private NotificationPresenter mPresenter;
-    @Mock private NotificationListener mListener;
-    @Mock private NotificationStackScrollLayout mStackScroller;
+    @Mock private NotificationListContainer mListContainer;
     @Mock private IStatusBarService mBarService;
     @Mock private NotificationData mNotificationData;
     @Mock private ExpandableNotificationRow mRow;
 
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotificationListener mListener;
+
     private NotificationData.Entry mEntry;
     private StatusBarNotification mSbn;
     private TestableNotificationLogger mLogger;
@@ -68,24 +70,25 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(NotificationListener.class, mListener);
 
-        when(mPresenter.getNotificationData()).thenReturn(mNotificationData);
+        when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
 
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
                 0, new Notification(), UserHandle.CURRENT, null, 0);
         mEntry = new NotificationData.Entry(mSbn);
         mEntry.row = mRow;
 
-        mLogger = new TestableNotificationLogger(mListener, mDependency.get(UiOffloadThread.class),
-                mBarService);
-        mLogger.setUpWithPresenter(mPresenter, mStackScroller);
+        mLogger = new TestableNotificationLogger(mBarService);
+        mLogger.setUpWithEntryManager(mEntryManager, mListContainer);
     }
 
     @Test
     public void testOnChildLocationsChangedReportsVisibilityChanged() throws Exception {
-        when(mStackScroller.isInVisibleLocation(any())).thenReturn(true);
+        when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
         when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry));
-        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller);
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         waitForIdleSync(mLogger.getHandlerForTest());
         waitForUiOffloadThread();
 
@@ -97,7 +100,7 @@
 
         // |mEntry| won't change visibility, so it shouldn't be reported again:
         Mockito.reset(mBarService);
-        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller);
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         waitForIdleSync(mLogger.getHandlerForTest());
         waitForUiOffloadThread();
 
@@ -107,9 +110,9 @@
     @Test
     public void testStoppingNotificationLoggingReportsCurrentNotifications()
             throws Exception {
-        when(mStackScroller.isInVisibleLocation(any())).thenReturn(true);
+        when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
         when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry));
-        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller);
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         waitForIdleSync(mLogger.getHandlerForTest());
         waitForUiOffloadThread();
         Mockito.reset(mBarService);
@@ -123,17 +126,13 @@
 
     private class TestableNotificationLogger extends NotificationLogger {
 
-        public TestableNotificationLogger(
-                NotificationListenerService notificationListener,
-                UiOffloadThread uiOffloadThread,
-                IStatusBarService barService) {
-            super(notificationListener, uiOffloadThread);
+        public TestableNotificationLogger(IStatusBarService barService) {
             mBarService = barService;
             // Make this on the main thread so we can wait for it during tests.
             mHandler = new Handler(Looper.getMainLooper());
         }
 
-        public NotificationStackScrollLayout.OnChildLocationsChangedListener
+        public OnChildLocationsChangedListener
                 getChildLocationsChangedListenerForTest() {
             return mNotificationLocationsChangedListener;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index b881c09..4829cb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -34,36 +34,41 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
 
-    private Handler mHandler;
-    private TestableNotificationRemoteInputManager mRemoteInputManager;
-    private StatusBarNotification mSbn;
-    private NotificationData.Entry mEntry;
-
     @Mock private NotificationPresenter mPresenter;
     @Mock private RemoteInputController.Delegate mDelegate;
-    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationRemoteInputManager.Callback mCallback;
     @Mock private RemoteInputController mController;
     @Mock private NotificationListenerService.RankingMap mRanking;
     @Mock private ExpandableNotificationRow mRow;
 
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+
+    private Handler mHandler;
+    private TestableNotificationRemoteInputManager mRemoteInputManager;
+    private StatusBarNotification mSbn;
+    private NotificationData.Entry mEntry;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
+                mLockscreenUserManager);
         mHandler = new Handler(Looper.getMainLooper());
 
         when(mPresenter.getHandler()).thenReturn(mHandler);
-        when(mPresenter.getLatestRankingMap()).thenReturn(mRanking);
+        when(mEntryManager.getLatestRankingMap()).thenReturn(mRanking);
 
-        mRemoteInputManager = new TestableNotificationRemoteInputManager(mLockscreenUserManager,
-                mContext);
+        mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
                 0, new Notification(), UserHandle.CURRENT, null, 0);
         mEntry = new NotificationData.Entry(mSbn);
         mEntry.row = mRow;
 
-        mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mCallback, mDelegate,
-                mController);
+        mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mEntryManager, mCallback,
+                mDelegate, mController);
     }
 
     @Test
@@ -97,21 +102,21 @@
 
         assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty());
         verify(mController).removeRemoteInput(mEntry, null);
-        verify(mPresenter).removeNotification(mEntry.key, mRanking);
+        verify(mEntryManager).removeNotification(mEntry.key, mRanking);
     }
 
     private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
 
-        public TestableNotificationRemoteInputManager(
-                NotificationLockscreenUserManager lockscreenUserManager, Context context) {
-            super(lockscreenUserManager, context);
+        public TestableNotificationRemoteInputManager(Context context) {
+            super(context);
         }
 
         public void setUpWithPresenterForTest(NotificationPresenter presenter,
+                NotificationEntryManager entryManager,
                 Callback callback,
                 RemoteInputController.Delegate delegate,
                 RemoteInputController controller) {
-            super.setUpWithPresenter(presenter, callback, delegate);
+            super.setUpWithPresenter(presenter, entryManager, callback, delegate);
             mRemoteInputController = controller;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
new file mode 100644
index 0000000..fbe730a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private NotificationData mNotificationData;
+    @Spy private FakeListContainer mListContainer = new FakeListContainer();
+
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
+
+    private NotificationViewHierarchyManager mViewHierarchyManager;
+    private NotificationTestHelper mHelper = new NotificationTestHelper(mContext);;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
+                mLockscreenUserManager);
+        mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
+        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+
+        when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
+
+        mViewHierarchyManager = new NotificationViewHierarchyManager(mContext);
+        mViewHierarchyManager.setUpWithPresenter(mPresenter, mEntryManager, mListContainer);
+    }
+
+    private NotificationData.Entry createEntry() throws Exception {
+        ExpandableNotificationRow row = mHelper.createRow();
+        NotificationData.Entry entry = new NotificationData.Entry(row.getStatusBarNotification());
+        entry.row = row;
+        return entry;
+    }
+
+    @Test
+    public void testNotificationsBecomingBundled() throws Exception {
+        // Tests 3 top level notifications becoming a single bundled notification with |entry0| as
+        // the summary.
+        NotificationData.Entry entry0 = createEntry();
+        NotificationData.Entry entry1 = createEntry();
+        NotificationData.Entry entry2 = createEntry();
+
+        // Set up the prior state to look like three top level notifications.
+        mListContainer.addContainerView(entry0.row);
+        mListContainer.addContainerView(entry1.row);
+        mListContainer.addContainerView(entry2.row);
+        when(mNotificationData.getActiveNotifications()).thenReturn(
+                Lists.newArrayList(entry0, entry1, entry2));
+
+        // Set up group manager to report that they should be bundled now.
+        when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(true);
+        when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(true);
+        when(mGroupManager.getGroupSummary(entry1.notification)).thenReturn(entry0.row);
+        when(mGroupManager.getGroupSummary(entry2.notification)).thenReturn(entry0.row);
+
+        // Run updateNotifications - the view hierarchy should be reorganized.
+        mViewHierarchyManager.updateNotificationViews();
+
+        verify(mListContainer).notifyGroupChildAdded(entry1.row);
+        verify(mListContainer).notifyGroupChildAdded(entry2.row);
+        assertTrue(Lists.newArrayList(entry0.row).equals(mListContainer.mRows));
+    }
+
+    @Test
+    public void testNotificationsBecomingUnbundled() throws Exception {
+        // Tests a bundled notification becoming three top level notifications.
+        NotificationData.Entry entry0 = createEntry();
+        NotificationData.Entry entry1 = createEntry();
+        NotificationData.Entry entry2 = createEntry();
+        entry0.row.addChildNotification(entry1.row);
+        entry0.row.addChildNotification(entry2.row);
+
+        // Set up the prior state to look like one top level notification.
+        mListContainer.addContainerView(entry0.row);
+        when(mNotificationData.getActiveNotifications()).thenReturn(
+                Lists.newArrayList(entry0, entry1, entry2));
+
+        // Set up group manager to report that they should not be bundled now.
+        when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(false);
+
+        // Run updateNotifications - the view hierarchy should be reorganized.
+        mViewHierarchyManager.updateNotificationViews();
+
+        verify(mListContainer).notifyGroupChildRemoved(
+                entry1.row, entry0.row.getChildrenContainer());
+        verify(mListContainer).notifyGroupChildRemoved(
+                entry2.row, entry0.row.getChildrenContainer());
+        assertTrue(Lists.newArrayList(entry0.row, entry1.row, entry2.row).equals(mListContainer.mRows));
+    }
+
+    @Test
+    public void testNotificationsBecomingSuppressed() throws Exception {
+        // Tests two top level notifications becoming a suppressed summary and a child.
+        NotificationData.Entry entry0 = createEntry();
+        NotificationData.Entry entry1 = createEntry();
+        entry0.row.addChildNotification(entry1.row);
+
+        // Set up the prior state to look like a top level notification.
+        mListContainer.addContainerView(entry0.row);
+        when(mNotificationData.getActiveNotifications()).thenReturn(
+                Lists.newArrayList(entry0, entry1));
+
+        // Set up group manager to report a suppressed summary now.
+        when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false);
+        when(mGroupManager.isSummaryOfSuppressedGroup(entry0.notification)).thenReturn(true);
+
+        // Run updateNotifications - the view hierarchy should be reorganized.
+        mViewHierarchyManager.updateNotificationViews();
+
+        verify(mListContainer).notifyGroupChildRemoved(
+                entry1.row, entry0.row.getChildrenContainer());
+        assertTrue(Lists.newArrayList(entry0.row, entry1.row).equals(mListContainer.mRows));
+        assertEquals(View.GONE, entry0.row.getVisibility());
+        assertEquals(View.VISIBLE, entry1.row.getVisibility());
+    }
+
+    private class FakeListContainer implements NotificationListContainer {
+        final LinearLayout mLayout = new LinearLayout(mContext);
+        final List<View> mRows = Lists.newArrayList();
+
+        @Override
+        public void setChildTransferInProgress(boolean childTransferInProgress) {}
+
+        @Override
+        public void changeViewPosition(View child, int newIndex) {
+            mRows.remove(child);
+            mRows.add(newIndex, child);
+        }
+
+        @Override
+        public void notifyGroupChildAdded(View row) {}
+
+        @Override
+        public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {}
+
+        @Override
+        public void generateAddAnimation(View child, boolean fromMoreCard) {}
+
+        @Override
+        public void generateChildOrderChangedEvent() {}
+
+        @Override
+        public int getContainerChildCount() {
+            return mRows.size();
+        }
+
+        @Override
+        public View getContainerChildAt(int i) {
+            return mRows.get(i);
+        }
+
+        @Override
+        public void removeContainerView(View v) {
+            mLayout.removeView(v);
+            mRows.remove(v);
+        }
+
+        @Override
+        public void addContainerView(View v) {
+            mLayout.addView(v);
+            mRows.add(v);
+        }
+
+        @Override
+        public void setMaxDisplayedNotifications(int maxNotifications) {}
+
+        @Override
+        public void snapViewIfNeeded(ExpandableNotificationRow row) {}
+
+        @Override
+        public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+            return null;
+        }
+
+        @Override
+        public void onHeightChanged(ExpandableView view, boolean animate) {}
+
+        @Override
+        public void resetExposedMenuView(boolean animate, boolean force) {}
+
+        @Override
+        public NotificationSwipeActionHelper getSwipeActionHelper() {
+            return null;
+        }
+
+        @Override
+        public void cleanUpViewState(View view) {}
+
+        @Override
+        public boolean isInVisibleLocation(ExpandableNotificationRow row) {
+            return true;
+        }
+
+        @Override
+        public void setChildLocationsChangedListener(
+                NotificationLogger.OnChildLocationsChangedListener listener) {}
+
+        @Override
+        public boolean hasPulsingNotifications() {
+            return false;
+        }
+    }
+}
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 0732866..99202f4 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
@@ -37,6 +37,7 @@
 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;
@@ -52,7 +53,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.DisplayMetrics;
 import android.util.SparseArray;
 import android.view.ViewGroup.LayoutParams;
 
@@ -61,6 +61,7 @@
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardHostView.OnDismissAction;
+import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.UiOffloadThread;
@@ -72,10 +73,18 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListContainer;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -85,6 +94,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
@@ -94,70 +105,71 @@
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class StatusBarTest extends SysuiTestCase {
+    @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock private UnlockMethodCache mUnlockMethodCache;
+    @Mock private KeyguardIndicationController mKeyguardIndicationController;
+    @Mock private NotificationStackScrollLayout mStackScroller;
+    @Mock private HeadsUpManager mHeadsUpManager;
+    @Mock private SystemServicesProxy mSystemServicesProxy;
+    @Mock private NotificationPanelView mNotificationPanelView;
+    @Mock private IStatusBarService mBarService;
+    @Mock private ScrimController mScrimController;
+    @Mock private ArrayList<Entry> mNotificationList;
+    @Mock private FingerprintUnlockController mFingerprintUnlockController;
+    @Mock private NotificationData mNotificationData;
 
-    StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    UnlockMethodCache mUnlockMethodCache;
-    KeyguardIndicationController mKeyguardIndicationController;
-    NotificationStackScrollLayout mStackScroller;
-    TestableStatusBar mStatusBar;
-    FakeMetricsLogger mMetricsLogger;
-    HeadsUpManager mHeadsUpManager;
-    NotificationData mNotificationData;
-    PowerManager mPowerManager;
-    SystemServicesProxy mSystemServicesProxy;
-    NotificationPanelView mNotificationPanelView;
-    ScrimController mScrimController;
-    IStatusBarService mBarService;
-    NotificationListener mNotificationListener;
-    NotificationLogger mNotificationLogger;
-    ArrayList<Entry> mNotificationList;
-    FingerprintUnlockController mFingerprintUnlockController;
-    private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    // Mock dependencies:
+    @Mock private NotificationViewHierarchyManager mViewHierarchyManager;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private NotificationListener mNotificationListener;
+
+    private TestableStatusBar mStatusBar;
+    private FakeMetricsLogger mMetricsLogger;
+    private PowerManager mPowerManager;
+    private TestableNotificationEntryManager mEntryManager;
+    private NotificationLogger mNotificationLogger;
 
     @Before
     public void setup() throws Exception {
-        mContext.setTheme(R.style.Theme_SystemUI_Light);
+        MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(AssistManager.class);
         mDependency.injectMockDependency(DeviceProvisionedController.class);
+        mDependency.injectMockDependency(NotificationGroupManager.class);
+        mDependency.injectMockDependency(NotificationGutsManager.class);
+        mDependency.injectMockDependency(NotificationRemoteInputManager.class);
+        mDependency.injectMockDependency(NotificationMediaManager.class);
+        mDependency.injectMockDependency(ForegroundServiceController.class);
+        mDependency.injectTestDependency(NotificationViewHierarchyManager.class,
+                mViewHierarchyManager);
+        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+        mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
         mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitorImpl.class));
-        CommandQueue commandQueue = mock(CommandQueue.class);
-        when(commandQueue.asBinder()).thenReturn(new Binder());
-        mContext.putComponent(CommandQueue.class, commandQueue);
+
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
-        mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager.class);
-        mUnlockMethodCache = mock(UnlockMethodCache.class);
-        mKeyguardIndicationController = mock(KeyguardIndicationController.class);
-        mStackScroller = mock(NotificationStackScrollLayout.class);
-        when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
+
         mMetricsLogger = new FakeMetricsLogger();
-        mHeadsUpManager = mock(HeadsUpManager.class);
-        mNotificationData = mock(NotificationData.class);
-        mSystemServicesProxy = mock(SystemServicesProxy.class);
-        mNotificationPanelView = mock(NotificationPanelView.class);
-        when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
-        mNotificationList = mock(ArrayList.class);
-        mScrimController = mock(ScrimController.class);
-        mFingerprintUnlockController = mock(FingerprintUnlockController.class);
+        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+        mNotificationLogger = new NotificationLogger();
+        mDependency.injectTestDependency(NotificationLogger.class, mNotificationLogger);
+
         IPowerManager powerManagerService = mock(IPowerManager.class);
         HandlerThread handlerThread = new HandlerThread("TestThread");
         handlerThread.start();
         mPowerManager = new PowerManager(mContext, powerManagerService,
                 new Handler(handlerThread.getLooper()));
-        when(powerManagerService.isInteractive()).thenReturn(true);
-        mBarService = mock(IStatusBarService.class);
-        mNotificationListener = mock(NotificationListener.class);
-        mNotificationLogger = new NotificationLogger(mNotificationListener, mDependency.get(
-                UiOffloadThread.class));
 
-        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
-                mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
-                mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView,
-                mBarService, mNotificationListener, mNotificationLogger, mScrimController,
-                mFingerprintUnlockController);
-        mStatusBar.mContext = mContext;
-        mStatusBar.mComponents = mContext.getComponents();
+        CommandQueue commandQueue = mock(CommandQueue.class);
+        when(commandQueue.asBinder()).thenReturn(new Binder());
+        mContext.putComponent(CommandQueue.class, commandQueue);
+
+        mContext.setTheme(R.style.Theme_SystemUI_Light);
+
+        when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
+        when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
+        when(powerManagerService.isInteractive()).thenReturn(true);
+        when(mStackScroller.getActivatedChild()).thenReturn(null);
+
         doAnswer(invocation -> {
             OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
             onDismissAction.onDismiss();
@@ -170,9 +182,19 @@
             return null;
         }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
 
-        mNotificationLogger.setUpWithPresenter(mStatusBar, mStackScroller);
+        mEntryManager = new TestableNotificationEntryManager(mSystemServicesProxy, mPowerManager,
+                mContext);
+        mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
+                mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
+                mPowerManager, mNotificationPanelView, mBarService, mNotificationListener,
+                mNotificationLogger, mVisualStabilityManager, mViewHierarchyManager,
+                mEntryManager, mScrimController, mFingerprintUnlockController);
+        mStatusBar.mContext = mContext;
+        mStatusBar.mComponents = mContext.getComponents();
+        mEntryManager.setUpForTest(mStatusBar, mStackScroller, mStatusBar, mHeadsUpManager,
+                mNotificationData);
+        mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller);
 
-        when(mStackScroller.getActivatedChild()).thenReturn(null);
         TestableLooper.get(this).setMessageHandler(m -> {
             if (m.getCallback() == mStatusBar.mNotificationLogger.getVisibilityReporter()) {
                 return false;
@@ -334,7 +356,7 @@
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
 
-        assertTrue(mStatusBar.shouldPeek(entry, sbn));
+        assertTrue(mEntryManager.shouldPeek(entry, sbn));
     }
 
     @Test
@@ -355,7 +377,7 @@
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
 
-        assertFalse(mStatusBar.shouldPeek(entry, sbn));
+        assertFalse(mEntryManager.shouldPeek(entry, sbn));
     }
 
     @Test
@@ -375,7 +397,7 @@
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
 
-        assertTrue(mStatusBar.shouldPeek(entry, sbn));
+        assertTrue(mEntryManager.shouldPeek(entry, sbn));
     }
 
     @Test
@@ -394,7 +416,7 @@
         StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
-        assertFalse(mStatusBar.shouldPeek(entry, sbn));
+        assertFalse(mEntryManager.shouldPeek(entry, sbn));
     }
     @Test
     public void testShouldPeek_suppressedScreenOff_dozing() {
@@ -412,7 +434,7 @@
         StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
-        assertFalse(mStatusBar.shouldPeek(entry, sbn));
+        assertFalse(mEntryManager.shouldPeek(entry, sbn));
     }
 
     @Test
@@ -431,7 +453,7 @@
         StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
                 UserHandle.of(0), null, 0);
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
-        assertTrue(mStatusBar.shouldPeek(entry, sbn));
+        assertTrue(mEntryManager.shouldPeek(entry, sbn));
     }
 
 
@@ -564,25 +586,28 @@
     static class TestableStatusBar extends StatusBar {
         public TestableStatusBar(StatusBarKeyguardViewManager man,
                 UnlockMethodCache unlock, KeyguardIndicationController key,
-                NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd,
-                PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView,
+                NotificationStackScrollLayout stack, HeadsUpManager hum,
+                PowerManager pm, NotificationPanelView panelView,
                 IStatusBarService barService, NotificationListener notificationListener,
-                NotificationLogger notificationLogger, ScrimController scrimController,
+                NotificationLogger notificationLogger,
+                VisualStabilityManager visualStabilityManager,
+                NotificationViewHierarchyManager viewHierarchyManager,
+                TestableNotificationEntryManager entryManager, ScrimController scrimController,
                 FingerprintUnlockController fingerprintUnlockController) {
             mStatusBarKeyguardViewManager = man;
             mUnlockMethodCache = unlock;
             mKeyguardIndicationController = key;
             mStackScroller = stack;
             mHeadsUpManager = hum;
-            mNotificationData = nd;
-            mUseHeadsUp = true;
             mPowerManager = pm;
-            mSystemServicesProxy = ssp;
             mNotificationPanel = panelView;
             mBarService = barService;
             mNotificationListener = notificationListener;
             mNotificationLogger = notificationLogger;
             mWakefulnessLifecycle = createAwakeWakefulnessLifecycle();
+            mVisualStabilityManager = visualStabilityManager;
+            mViewHierarchyManager = viewHierarchyManager;
+            mEntryManager = entryManager;
             mScrimController = scrimController;
             mFingerprintUnlockController = fingerprintUnlockController;
         }
@@ -606,5 +631,26 @@
         public void setUserSetupForTest(boolean userSetup) {
             mUserSetup = userSetup;
         }
+
+    }
+
+    private class TestableNotificationEntryManager extends NotificationEntryManager {
+
+        public TestableNotificationEntryManager(SystemServicesProxy systemServicesProxy,
+                PowerManager powerManager, Context context) {
+            super(context);
+            mSystemServicesProxy = systemServicesProxy;
+            mPowerManager = powerManager;
+        }
+
+        public void setUpForTest(NotificationPresenter presenter,
+                NotificationListContainer listContainer,
+                Callback callback,
+                HeadsUpManager headsUpManager,
+                NotificationData notificationData) {
+            super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager);
+            mNotificationData = notificationData;
+            mUseHeadsUp = true;
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
index f1965a2..56de32d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
@@ -33,6 +33,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -40,6 +42,30 @@
 
     private LeakDetector mLeakDetector;
 
+    // The references for which collection is observed are stored in fields. The allocation and
+    // of these references happens in separate methods (trackObjectWith/trackCollectionWith)
+    // from where they are set to null. The generated code might keep the allocated reference
+    // alive in a dex register when compiling in release mode. As R8 is used to compile this
+    // test the --dontoptimize flag is also required to ensure that these methods are not
+    // inlined, as that would defeat the purpose of having the mutation in methods.
+    private Object mObject;
+    private Collection<?> mCollection;
+
+    private CollectionWaiter trackObjectWith(Consumer<Object> tracker) {
+        mObject = new Object();
+        CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(mObject);
+        tracker.accept(mObject);
+        return collectionWaiter;
+    }
+
+    private CollectionWaiter trackCollectionWith(
+            BiConsumer<? super Collection<?>, String> tracker) {
+        mCollection = new ArrayList<>();
+        CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(mCollection);
+        tracker.accept(mCollection, "tag");
+        return collectionWaiter;
+    }
+
     @Before
     public void setup() {
         mLeakDetector = LeakDetector.create();
@@ -51,31 +77,22 @@
 
     @Test
     public void trackInstance_doesNotLeakTrackedObject() {
-        Object object = new Object();
-        CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object);
-
-        mLeakDetector.trackInstance(object);
-        object = null;
+        CollectionWaiter collectionWaiter = trackObjectWith(mLeakDetector::trackInstance);
+        mObject = null;
         collectionWaiter.waitForCollection();
     }
 
     @Test
     public void trackCollection_doesNotLeakTrackedObject() {
-        Collection<?> object = new ArrayList<>();
-        CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object);
-
-        mLeakDetector.trackCollection(object, "tag");
-        object = null;
+        CollectionWaiter collectionWaiter = trackCollectionWith(mLeakDetector::trackCollection);
+        mCollection = null;
         collectionWaiter.waitForCollection();
     }
 
     @Test
     public void trackGarbage_doesNotLeakTrackedObject() {
-        Object object = new Object();
-        CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object);
-
-        mLeakDetector.trackGarbage(object);
-        object = null;
+        CollectionWaiter collectionWaiter = trackObjectWith(mLeakDetector::trackGarbage);
+        mObject = null;
         collectionWaiter.waitForCollection();
     }
 
@@ -108,4 +125,4 @@
         FileOutputStream fos = new FileOutputStream("/dev/null");
         mLeakDetector.dump(fos.getFD(), new PrintWriter(fos), new String[0]);
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java
index 9787df9..ce6212e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java
@@ -41,6 +41,13 @@
         mMap = new WeakIdentityHashMap<>();
     }
 
+    private CollectionWaiter addObjectToMap(WeakIdentityHashMap<Object, Object> map) {
+      Object object = new Object();
+      CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object);
+      map.put(object, "value");
+      return collectionWaiter;
+    }
+
     @Test
     public void testUsesIdentity() {
         String a1 = new String("a");
@@ -56,11 +63,12 @@
 
     @Test
     public void testWeaklyReferences() {
-        Object object = new Object();
-        CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object);
-
-        mMap.put(object, "value");
-        object = null;
+        // Allocate and add an object to the weak map in a separate method to avoid a live
+        // reference to the allocated object in a dex register. As R8 is used to compile this
+        // test the --dontoptimize flag is also required to ensure that the method is not
+        // inlined, as that would defeat the purpose of having the allocation in a separate
+        // method.
+        CollectionWaiter collectionWaiter = addObjectToMap(mMap);
 
         // Wait until object has been collected. We'll also need to wait for mMap to become empty,
         // because our collection waiter may be told about the collection earlier than mMap.
@@ -70,4 +78,4 @@
         assertEquals(0, mMap.size());
         assertTrue(mMap.isEmpty());
     }
-}
\ No newline at end of file
+}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 935b787..4f04d36 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5094,6 +5094,41 @@
     // Tag used to report autofill field classification scores
     FIELD_AUTOFILL_MATCH_SCORE = 1274;
 
+    // ACTION: Usb config has been changed to charging
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_USB_CONFIG_CHARGING = 1275;
+
+    // ACTION: Usb config has been changed to mtp (file transfer)
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_USB_CONFIG_MTP = 1276;
+
+    // ACTION: Usb config has been changed to ptp (photo transfer)
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_USB_CONFIG_PTP = 1277;
+
+    // ACTION: Usb config has been changed to rndis (usb tethering)
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_USB_CONFIG_RNDIS = 1278;
+
+    // ACTION: Usb config has been changed to midi
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_USB_CONFIG_MIDI = 1279;
+
+    // ACTION: Usb config has been changed to accessory
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_USB_CONFIG_ACCESSORY = 1280;
+
+    // OPEN: Settings > Battery > Smart Battery
+    // CATEGORY: SETTINGS
+    // OS: P
+    FUELGAUGE_SMART_BATTERY = 1281;
+
     // ---- End P Constants, all P constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 3d7d6b7..ed068b9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Region;
 import android.os.Binder;
@@ -740,6 +741,9 @@
 
     @Override
     public boolean isFingerprintGestureDetectionAvailable() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            return false;
+        }
         if (isCapturingFingerprintGestures()) {
             FingerprintGestureDispatcher dispatcher =
                     mSystemSupport.getFingerprintGestureDispatcher();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d83f6ae..ba8ce59 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility;
 
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
@@ -64,7 +65,9 @@
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -299,6 +302,14 @@
         return state;
     }
 
+    boolean getBindInstantServiceAllowed(int userId) {
+        return  mSecurityPolicy.getBindInstantServiceAllowed(userId);
+    }
+
+    void setBindInstantServiceAllowed(int userId, boolean allowed) {
+        mSecurityPolicy.setBindInstantServiceAllowed(userId, allowed);
+    }
+
     private void registerBroadcastReceivers() {
         PackageMonitor monitor = new PackageMonitor() {
             @Override
@@ -752,7 +763,6 @@
                 mPictureInPictureActionReplacingConnection = wrapper;
                 wrapper.linkToDeath();
             }
-            mSecurityPolicy.notifyWindowsChanged();
         }
     }
 
@@ -1218,14 +1228,18 @@
     private boolean readInstalledAccessibilityServiceLocked(UserState userState) {
         mTempAccessibilityServiceInfoList.clear();
 
+        int flags = PackageManager.GET_SERVICES
+                | PackageManager.GET_META_DATA
+                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
+        if (userState.mBindInstantServiceAllowed) {
+            flags |= PackageManager.MATCH_INSTANT;
+        }
+
         List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
-                new Intent(AccessibilityService.SERVICE_INTERFACE),
-                PackageManager.GET_SERVICES
-                        | PackageManager.GET_META_DATA
-                        | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                mCurrentUserId);
+                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId);
 
         for (int i = 0, count = installedServices.size(); i < count; i++) {
             ResolveInfo resolveInfo = installedServices.get(i);
@@ -2269,6 +2283,14 @@
         }
     }
 
+    private void sendAccessibilityEventLocked(AccessibilityEvent event, int userId) {
+        // Resync to avoid calling out with the lock held
+        event.setEventTime(SystemClock.uptimeMillis());
+        mMainHandler.obtainMessage(
+                MainHandler.MSG_SEND_ACCESSIBILITY_EVENT, userId, 0 /* unused */, event)
+                .sendToTarget();
+    }
+
     /**
      * AIDL-exposed method. System only.
      * Inform accessibility that a fingerprint gesture was performed
@@ -2405,6 +2427,7 @@
         public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13;
         public static final int MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER = 14;
         public static final int MSG_INIT_SERVICE = 15;
+        public static final int MSG_SEND_ACCESSIBILITY_EVENT = 16;
 
         public MainHandler(Looper looper) {
             super(looper);
@@ -2505,6 +2528,12 @@
                             (AccessibilityServiceConnection) msg.obj;
                     service.initializeService();
                 } break;
+
+                case MSG_SEND_ACCESSIBILITY_EVENT: {
+                    final AccessibilityEvent event = (AccessibilityEvent) msg.obj;
+                    final int userId = msg.arg1;
+                    sendAccessibilityEvent(event, userId);
+                }
             }
         }
 
@@ -2519,7 +2548,7 @@
                     AccessibilityEvent event = AccessibilityEvent.obtain(
                             AccessibilityEvent.TYPE_ANNOUNCEMENT);
                     event.getText().add(message);
-                    sendAccessibilityEvent(event, mCurrentUserId);
+                    sendAccessibilityEventLocked(event, mCurrentUserId);
                 }
             }
         }
@@ -2709,6 +2738,14 @@
         }
     }
 
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ShellCallback callback,
+            ResultReceiver resultReceiver) {
+        new AccessibilityShellCommand(this).exec(this, in, out, err, args,
+                callback, resultReceiver);
+    }
+
     final class WindowsForAccessibilityCallback implements
             WindowManagerInternal.WindowsForAccessibilityCallback {
 
@@ -2939,21 +2976,21 @@
     public class SecurityPolicy {
         public static final int INVALID_WINDOW_ID = -1;
 
-        private static final int RETRIEVAL_ALLOWING_EVENT_TYPES =
-            AccessibilityEvent.TYPE_VIEW_CLICKED
-            | AccessibilityEvent.TYPE_VIEW_FOCUSED
-            | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
-            | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
-            | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
-            | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
-            | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
-            | AccessibilityEvent.TYPE_VIEW_SELECTED
-            | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
-            | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
-            | AccessibilityEvent.TYPE_VIEW_SCROLLED
-            | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
-            | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
-            | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
+        private static final int KEEP_SOURCE_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED
+                | AccessibilityEvent.TYPE_VIEW_FOCUSED
+                | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
+                | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+                | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
+                | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+                | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+                | AccessibilityEvent.TYPE_WINDOWS_CHANGED
+                | AccessibilityEvent.TYPE_VIEW_SELECTED
+                | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+                | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+                | AccessibilityEvent.TYPE_VIEW_SCROLLED
+                | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+                | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+                | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
 
         // In Z order
         public List<AccessibilityWindowInfo> mWindows;
@@ -3076,6 +3113,32 @@
             return uidPackages;
         }
 
+        private boolean getBindInstantServiceAllowed(int userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+                    "getBindInstantServiceAllowed");
+            UserState state = mUserStates.get(userId);
+            return (state != null) && state.mBindInstantServiceAllowed;
+        }
+
+        private void setBindInstantServiceAllowed(int userId, boolean allowed) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+                    "setBindInstantServiceAllowed");
+            UserState state = mUserStates.get(userId);
+            if (state == null) {
+                if (!allowed) {
+                    return;
+                }
+                state = new UserState(userId);
+                mUserStates.put(userId, state);
+            }
+            if (state.mBindInstantServiceAllowed != allowed) {
+                state.mBindInstantServiceAllowed = allowed;
+                onUserStateChangedLocked(state);
+            }
+        }
+
         public void clearWindowsLocked() {
             List<WindowInfo> windows = Collections.emptyList();
             final int activeWindowId = mActiveWindowId;
@@ -3089,10 +3152,10 @@
                 mWindows = new ArrayList<>();
             }
 
-            final int oldWindowCount = mWindows.size();
-            for (int i = oldWindowCount - 1; i >= 0; i--) {
-                mWindows.remove(i).recycle();
-            }
+            List<AccessibilityWindowInfo> oldWindowList = new ArrayList<>(mWindows);
+            SparseArray<AccessibilityWindowInfo> oldWindowsById = mA11yWindowInfoById.clone();
+
+            mWindows.clear();
             mA11yWindowInfoById.clear();
 
             for (int i = 0; i < mWindowInfoById.size(); i++) {
@@ -3154,7 +3217,49 @@
                 }
             }
 
-            notifyWindowsChanged();
+            sendEventsForChangedWindowsLocked(oldWindowList, oldWindowsById);
+
+            final int oldWindowCount = oldWindowList.size();
+            for (int i = oldWindowCount - 1; i >= 0; i--) {
+                oldWindowList.remove(i).recycle();
+            }
+        }
+
+        private void sendEventsForChangedWindowsLocked(List<AccessibilityWindowInfo> oldWindows,
+                SparseArray<AccessibilityWindowInfo> oldWindowsById) {
+            List<AccessibilityEvent> events = new ArrayList<>();
+            // Send events for all removed windows
+            final int oldWindowsCount = oldWindows.size();
+            for (int i = 0; i < oldWindowsCount; i++) {
+                final AccessibilityWindowInfo window = oldWindows.get(i);
+                if (mA11yWindowInfoById.get(window.getId()) == null) {
+                    events.add(AccessibilityEvent.obtainWindowsChangedEvent(
+                            window.getId(), AccessibilityEvent.WINDOWS_CHANGE_REMOVED));
+                }
+            }
+
+            // Look for other changes
+            int oldWindowIndex = 0;
+            final int newWindowCount = mWindows.size();
+            for (int i = 0; i < newWindowCount; i++) {
+                final AccessibilityWindowInfo newWindow = mWindows.get(i);
+                final AccessibilityWindowInfo oldWindow = oldWindowsById.get(newWindow.getId());
+                if (oldWindow == null) {
+                    events.add(AccessibilityEvent.obtainWindowsChangedEvent(
+                            newWindow.getId(), AccessibilityEvent.WINDOWS_CHANGE_ADDED));
+                } else {
+                    int changes = newWindow.differenceFrom(oldWindow);
+                    if (changes !=  0) {
+                        events.add(AccessibilityEvent.obtainWindowsChangedEvent(
+                                newWindow.getId(), changes));
+                    }
+                }
+            }
+
+            final int numEvents = events.size();
+            for (int i = 0; i < numEvents; i++) {
+                sendAccessibilityEventLocked(events.get(i), mCurrentUserId);
+            }
         }
 
         public boolean computePartialInteractiveRegionForWindowLocked(int windowId,
@@ -3195,7 +3300,7 @@
         }
 
         public void updateEventSourceLocked(AccessibilityEvent event) {
-            if ((event.getEventType() & RETRIEVAL_ALLOWING_EVENT_TYPES) == 0) {
+            if ((event.getEventType() & KEEP_SOURCE_EVENT_TYPES) == 0) {
                 event.setSource((View) null);
             }
         }
@@ -3309,46 +3414,55 @@
 
         private void setActiveWindowLocked(int windowId) {
             if (mActiveWindowId != windowId) {
+                sendAccessibilityEventLocked(
+                        AccessibilityEvent.obtainWindowsChangedEvent(
+                                mActiveWindowId, AccessibilityEvent.WINDOWS_CHANGE_ACTIVE),
+                        mCurrentUserId);
+
                 mActiveWindowId = windowId;
                 if (mWindows != null) {
                     final int windowCount = mWindows.size();
                     for (int i = 0; i < windowCount; i++) {
                         AccessibilityWindowInfo window = mWindows.get(i);
-                        window.setActive(window.getId() == windowId);
+                        if (window.getId() == windowId) {
+                            window.setActive(true);
+                            sendAccessibilityEventLocked(
+                                    AccessibilityEvent.obtainWindowsChangedEvent(windowId,
+                                            AccessibilityEvent.WINDOWS_CHANGE_ACTIVE),
+                                    mCurrentUserId);
+                        } else {
+                            window.setActive(false);
+                        }
                     }
                 }
-                notifyWindowsChanged();
             }
         }
 
         private void setAccessibilityFocusedWindowLocked(int windowId) {
             if (mAccessibilityFocusedWindowId != windowId) {
+                sendAccessibilityEventLocked(
+                        AccessibilityEvent.obtainWindowsChangedEvent(
+                                mAccessibilityFocusedWindowId,
+                                WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
+                        mCurrentUserId);
+
                 mAccessibilityFocusedWindowId = windowId;
                 if (mWindows != null) {
                     final int windowCount = mWindows.size();
                     for (int i = 0; i < windowCount; i++) {
                         AccessibilityWindowInfo window = mWindows.get(i);
-                        window.setAccessibilityFocused(window.getId() == windowId);
+                        if (window.getId() == windowId) {
+                            window.setAccessibilityFocused(true);
+                            sendAccessibilityEventLocked(
+                                    AccessibilityEvent.obtainWindowsChangedEvent(
+                                            windowId, WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
+                                    mCurrentUserId);
+
+                        } else {
+                            window.setAccessibilityFocused(false);
+                        }
                     }
                 }
-
-                notifyWindowsChanged();
-            }
-        }
-
-        public void notifyWindowsChanged() {
-            if (mWindowsForAccessibilityCallback == null) {
-                return;
-            }
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                // Let the client know the windows changed.
-                AccessibilityEvent event = AccessibilityEvent.obtain(
-                        AccessibilityEvent.TYPE_WINDOWS_CHANGED);
-                event.setEventTime(SystemClock.uptimeMillis());
-                sendAccessibilityEvent(event, mCurrentUserId);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
             }
         }
 
@@ -3558,6 +3672,8 @@
         public boolean mIsFilterKeyEventsEnabled;
         public boolean mAccessibilityFocusOnlyInActiveWindow;
 
+        public boolean mBindInstantServiceAllowed;
+
         public UserState(int userId) {
             mUserId = userId;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 5f6efb6..96b8979 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -91,10 +91,12 @@
         if (userState == null) return;
         final long identity = Binder.clearCallingIdentity();
         try {
+            int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
+            if (userState.mBindInstantServiceAllowed) {
+                flags |= Context.BIND_ALLOW_INSTANT;
+            }
             if (mService == null && mContext.bindServiceAsUser(
-                    mIntent, this,
-                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                    new UserHandle(userState.mUserId))) {
+                    mIntent, this, flags, new UserHandle(userState.mUserId))) {
                 userState.getBindingServicesLocked().add(mComponentName);
             }
         } finally {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
new file mode 100644
index 0000000..ff59c24
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -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.
+ */
+
+package com.android.server.accessibility;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell command implementation for the accessibility manager service
+ */
+final class AccessibilityShellCommand extends ShellCommand {
+    final @NonNull AccessibilityManagerService mService;
+
+    AccessibilityShellCommand(@NonNull AccessibilityManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        switch (cmd) {
+            case "get-bind-instant-service-allowed": {
+                return runGetBindInstantServiceAllowed();
+            }
+            case "set-bind-instant-service-allowed": {
+                return runSetBindInstantServiceAllowed();
+            }
+        }
+        return -1;
+    }
+
+    private int runGetBindInstantServiceAllowed() {
+        final Integer userId = parseUserId();
+        if (userId == null) {
+            return -1;
+        }
+        getOutPrintWriter().println(Boolean.toString(
+                mService.getBindInstantServiceAllowed(userId)));
+        return 0;
+    }
+
+    private int runSetBindInstantServiceAllowed() {
+        final Integer userId = parseUserId();
+        if (userId == null) {
+            return -1;
+        }
+        final String allowed = getNextArgRequired();
+        if (allowed == null) {
+            getErrPrintWriter().println("Error: no true/false specified");
+            return -1;
+        }
+        mService.setBindInstantServiceAllowed(userId,
+                Boolean.parseBoolean(allowed));
+        return 0;
+    }
+
+    private Integer parseUserId() {
+        final String option = getNextOption();
+        if (option != null) {
+            if (option.equals("--user")) {
+                return UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Unknown option: " + option);
+                return null;
+            }
+        }
+        return UserHandle.USER_SYSTEM;
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Accessibility service (accessibility) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  set-bind-instant-service-allowed [--user <USER_ID>] true|false ");
+        pw.println("    Set whether binding to services provided by instant apps is allowed.");
+        pw.println("  get-bind-instant-service-allowed [--user <USER_ID>]");
+        pw.println("    Get whether binding to services provided by instant apps is allowed.");
+    }
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
index abfdb68..d5b53bc 100644
--- a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
@@ -40,12 +40,6 @@
         return (deltaTime >= timeout);
     }
 
-    public static boolean isSamePointerContext(MotionEvent first, MotionEvent second) {
-        return (first.getPointerIdBits() == second.getPointerIdBits()
-                && first.getPointerId(first.getActionIndex())
-                        == second.getPointerId(second.getActionIndex()));
-    }
-
     /**
      * Determines whether a two pointer gesture is a dragging one.
      *
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 9b2b4eb..74d2ddd 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -229,7 +229,7 @@
     }
 
     void clearAndTransitionToStateDetecting() {
-        mCurrentState = mDelegatingState;
+        mCurrentState = mDetectingState;
         mDetectingState.clear();
         mViewportDraggingState.clear();
         mPanningScalingState.clear();
@@ -649,14 +649,19 @@
                 break;
                 case ACTION_MOVE: {
                     if (isFingerDown()
-                            && distance(mLastDown, /* move */ event) > mSwipeMinDistance
-                            // For convenience, viewport dragging on 3tap&hold takes precedence
-                            // over insta-delegating on 3tap&swipe
-                            // (which is a rare combo to be used aside from magnification)
-                            && !isMultiTapTriggered(2 /* taps */)) {
+                            && distance(mLastDown, /* move */ event) > mSwipeMinDistance) {
 
-                        // Swipe detected - delegate skipping timeout
-                        transitionToDelegatingStateAndClear();
+                        // Swipe detected - transition immediately
+
+                        // For convenience, viewport dragging takes precedence
+                        // over insta-delegating on 3tap&swipe
+                        // (which is a rare combo to be used aside from magnification)
+                        if (isMultiTapTriggered(2 /* taps */)) {
+                            transitionTo(mViewportDraggingState);
+                            clear();
+                        } else {
+                            transitionToDelegatingStateAndClear();
+                        }
                     }
                 }
                 break;
@@ -755,10 +760,10 @@
                 int policyFlags) {
             if (event.getActionMasked() == ACTION_DOWN) {
                 mPreLastDown = mLastDown;
-                mLastDown = event;
+                mLastDown = MotionEvent.obtain(event);
             } else if (event.getActionMasked() == ACTION_UP) {
                 mPreLastUp = mLastUp;
-                mLastUp = event;
+                mLastUp = MotionEvent.obtain(event);
             }
 
             MotionEventInfo info = MotionEventInfo.obtain(event, rawEvent,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index e1cb154..cac7fed 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -479,7 +479,7 @@
         if (service != null) {
             service.destroySessionsLocked();
             service.updateLocked(disabled);
-            if (!service.isEnabled()) {
+            if (!service.isEnabledLocked()) {
                 removeCachedServiceLocked(userId);
             }
         }
@@ -621,6 +621,34 @@
         }
 
         @Override
+        public String getDefaultFieldClassificationAlgorithm() throws RemoteException {
+            final int userId = UserHandle.getCallingUserId();
+
+            synchronized (mLock) {
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+                if (service != null) {
+                    return service.getDefaultFieldClassificationAlgorithm(getCallingUid());
+                }
+            }
+
+            return null;
+        }
+
+        @Override
+        public List<String> getAvailableFieldClassificationAlgorithms() throws RemoteException {
+            final int userId = UserHandle.getCallingUserId();
+
+            synchronized (mLock) {
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+                if (service != null) {
+                    return service.getAvailableFieldClassificationAlgorithms(getCallingUid());
+                }
+            }
+
+            return null;
+        }
+
+        @Override
         public ComponentName getAutofillServiceComponentName() throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
 
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 9ecf63d..da74dba 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -43,14 +43,20 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.Parcelable.Creator;
+import android.os.RemoteCallback;
 import android.provider.Settings;
 import android.service.autofill.AutofillService;
 import android.service.autofill.AutofillServiceInfo;
+import android.service.autofill.Dataset;
+import android.service.autofill.EditDistanceScorer;
 import android.service.autofill.FieldClassification;
 import android.service.autofill.FieldClassification.Match;
 import android.service.autofill.FillEventHistory;
@@ -63,6 +69,8 @@
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.LocalLog;
+import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -81,6 +89,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
 
@@ -105,7 +114,10 @@
     private final AutoFillUI mUi;
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
+    @GuardedBy("mLock")
     private RemoteCallbackList<IAutoFillManagerClient> mClients;
+
+    @GuardedBy("mLock")
     private AutofillServiceInfo mInfo;
 
     private static final Random sRandom = new Random();
@@ -113,25 +125,160 @@
     private final LocalLog mRequestsHistory;
     private final LocalLog mUiLatencyHistory;
 
+    // TODO(b/70939974): temporary, will be moved to ExtServices
+    static final class FieldClassificationAlgorithmService {
+
+        static final String EXTRA_SCORES = "scores";
+
+        /**
+         * Gets the name of all available algorithms.
+         */
+        @NonNull
+        public List<String> getAvailableAlgorithms() {
+            return Arrays.asList(EditDistanceScorer.NAME);
+        }
+
+        /**
+         * Gets the default algorithm that's used when an algorithm is not specified or is invalid.
+         */
+        @NonNull
+        public String getDefaultAlgorithm() {
+            return EditDistanceScorer.NAME;
+        }
+
+        /**
+         * Gets the field classification scores.
+         *
+         * @param algorithmName algorithm to be used. If invalid, the default algorithm will be used
+         * instead.
+         * @param algorithmArgs optional arguments to be passed to the algorithm.
+         * @param currentValues values entered by the user.
+         * @param userValues values from the user data.
+         * @param callback returns a nullable bundle with the parcelable results on
+         * {@link #EXTRA_SCORES}.
+         */
+        @Nullable
+        void getScores(@NonNull String algorithmName, @Nullable Bundle algorithmArgs,
+                List<AutofillValue> currentValues, @NonNull String[] userValues,
+                @NonNull RemoteCallback callback) {
+            if (currentValues == null || userValues == null) {
+                // TODO(b/70939974): use preconditions / add unit test
+                throw new IllegalArgumentException("values cannot be null");
+            }
+            if (currentValues.isEmpty() || userValues.length == 0) {
+                Slog.w(TAG, "getScores(): empty currentvalues (" + currentValues
+                        + ") or userValues (" + Arrays.toString(userValues) + ")");
+                // TODO(b/70939974): add unit test
+                callback.sendResult(null);
+            }
+            String actualAlgorithName = algorithmName;
+            if (!EditDistanceScorer.NAME.equals(algorithmName)) {
+                Slog.w(TAG, "Ignoring invalid algorithm (" + algorithmName + ") and using "
+                        + EditDistanceScorer.NAME + " instead");
+                actualAlgorithName = EditDistanceScorer.NAME;
+            }
+            final int currentValuesSize = currentValues.size();
+            if (sDebug) {
+                Log.d(TAG, "getScores() will return a " + currentValuesSize + "x"
+                        + userValues.length + " matrix for " + actualAlgorithName);
+            }
+            final FieldClassificationScores scores = new FieldClassificationScores(
+                    actualAlgorithName, currentValuesSize, userValues.length);
+            final EditDistanceScorer algorithm = EditDistanceScorer.getInstance();
+            for (int i = 0; i < currentValuesSize; i++) {
+                for (int j = 0; j < userValues.length; j++) {
+                    final float score = algorithm.getScore(currentValues.get(i), userValues[j]);
+                    scores.scores[i][j] = score;
+                }
+            }
+            final Bundle result = new Bundle();
+            result.putParcelable(EXTRA_SCORES, scores);
+            callback.sendResult(result);
+        }
+    }
+
+    // TODO(b/70939974): temporary, will be moved to ExtServices
+    public static final class FieldClassificationScores implements Parcelable {
+        public final String algorithmName;
+        public final float[][] scores;
+
+        public FieldClassificationScores(String algorithmName, int size1, int size2) {
+            this.algorithmName = algorithmName;
+            scores = new float[size1][size2];
+        }
+
+        public FieldClassificationScores(Parcel parcel) {
+            algorithmName = parcel.readString();
+            final int size1 = parcel.readInt();
+            final int size2 = parcel.readInt();
+            scores = new float[size1][size2];
+            for (int i = 0; i < size1; i++) {
+                for (int j = 0; j < size2; j++) {
+                    scores[i][j] = parcel.readFloat();
+                }
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeString(algorithmName);
+            int size1 = scores.length;
+            int size2 = scores[0].length;
+            parcel.writeInt(size1);
+            parcel.writeInt(size2);
+            for (int i = 0; i < size1; i++) {
+                for (int j = 0; j < size2; j++) {
+                    parcel.writeFloat(scores[i][j]);
+                }
+            }
+        }
+
+        public static final Creator<FieldClassificationScores> CREATOR = new Creator<FieldClassificationScores>() {
+
+            @Override
+            public FieldClassificationScores createFromParcel(Parcel parcel) {
+                return new FieldClassificationScores(parcel);
+            }
+
+            @Override
+            public FieldClassificationScores[] newArray(int size) {
+                return new FieldClassificationScores[size];
+            }
+
+        };
+    }
+
+    private final FieldClassificationAlgorithmService mFcService =
+            new FieldClassificationAlgorithmService();
+
     /**
      * Apps disabled by the service; key is package name, value is when they will be enabled again.
      */
+    @GuardedBy("mLock")
     private ArrayMap<String, Long> mDisabledApps;
 
     /**
      * Activities disabled by the service; key is component name, value is when they will be enabled
      * again.
      */
+    @GuardedBy("mLock")
     private ArrayMap<ComponentName, Long> mDisabledActivities;
 
     /**
      * Whether service was disabled for user due to {@link UserManager} restrictions.
      */
+    @GuardedBy("mLock")
     private boolean mDisabled;
 
     /**
      * Data used for field classification.
      */
+    @GuardedBy("mLock")
     private UserData mUserData;
 
     /**
@@ -235,7 +382,7 @@
     }
 
     void updateLocked(boolean disabled) {
-        final boolean wasEnabled = isEnabled();
+        final boolean wasEnabled = isEnabledLocked();
         if (sVerbose) {
             Slog.v(TAG, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled
                     + ", mSetupComplete= " + mSetupComplete
@@ -274,7 +421,7 @@
             Slog.e(TAG, "Bad AutofillServiceInfo for '" + componentName + "': " + e);
             mInfo = null;
         }
-        final boolean isEnabled = isEnabled();
+        final boolean isEnabled = isEnabledLocked();
         if (wasEnabled != isEnabled) {
             if (!isEnabled) {
                 final int sessionCount = mSessions.size();
@@ -292,7 +439,7 @@
             mClients = new RemoteCallbackList<>();
         }
         mClients.register(client);
-        return isEnabled();
+        return isEnabledLocked();
     }
 
     void removeClientLocked(IAutoFillManagerClient client) {
@@ -302,7 +449,7 @@
     }
 
     void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
-        if (!isEnabled()) {
+        if (!isEnabledLocked()) {
             return;
         }
         final Session session = mSessions.get(sessionId);
@@ -312,7 +459,7 @@
     }
 
     void setHasCallback(int sessionId, int uid, boolean hasIt) {
-        if (!isEnabled()) {
+        if (!isEnabledLocked()) {
             return;
         }
         final Session session = mSessions.get(sessionId);
@@ -327,7 +474,7 @@
             @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
             @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
             int flags, @NonNull ComponentName componentName) {
-        if (!isEnabled()) {
+        if (!isEnabledLocked()) {
             return 0;
         }
 
@@ -388,7 +535,7 @@
     }
 
     void finishSessionLocked(int sessionId, int uid) {
-        if (!isEnabled()) {
+        if (!isEnabledLocked()) {
             return;
         }
 
@@ -400,7 +547,7 @@
             return;
         }
 
-        session.logContextCommittedLocked();
+        session.logContextCommitted();
 
         final boolean finished = session.showSaveLocked();
         if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
@@ -411,7 +558,7 @@
     }
 
     void cancelSessionLocked(int sessionId, int uid) {
-        if (!isEnabled()) {
+        if (!isEnabledLocked()) {
             return;
         }
 
@@ -713,7 +860,20 @@
     /**
      * Updates the last fill response when an autofill context is committed.
      */
-    void logContextCommitted(int sessionId, @Nullable Bundle clientState,
+    void logContextCommittedLocked(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,
+            @NonNull String appPackageName) {
+        logContextCommittedLocked(sessionId, clientState, selectedDatasets, ignoredDatasets,
+                changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
+                manuallyFilledDatasetIds, null, null, appPackageName);
+    }
+
+    void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
             @Nullable ArrayList<String> selectedDatasets,
             @Nullable ArraySet<String> ignoredDatasets,
             @Nullable ArrayList<AutofillId> changedFieldIds,
@@ -723,44 +883,52 @@
             @Nullable ArrayList<AutofillId> detectedFieldIdsList,
             @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList,
             @NonNull String appPackageName) {
-        synchronized (mLock) {
-            if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
-                AutofillId[] detectedFieldsIds = null;
-                FieldClassification[] detectedFieldClassifications = null;
-                if (detectedFieldIdsList != null) {
-                    detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
-                    detectedFieldIdsList.toArray(detectedFieldsIds);
-                    detectedFieldClassifications =
-                            new FieldClassification[detectedFieldClassificationsList.size()];
-                    detectedFieldClassificationsList.toArray(detectedFieldClassifications);
-
-                    final int numberFields = detectedFieldsIds.length;
-                    int totalSize = 0;
-                    float totalScore = 0;
-                    for (int i = 0; i < numberFields; i++) {
-                        final FieldClassification fc = detectedFieldClassifications[i];
-                        final List<Match> matches = fc.getMatches();
-                        final int size = matches.size();
-                        totalSize += size;
-                        for (int j = 0; j < size; j++) {
-                            totalScore += matches.get(j).getScore();
-                        }
-                    }
-
-                    final int averageScore = (int) ((totalScore * 100) / totalSize);
-                    mMetricsLogger.write(Helper
-                            .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
-                                    appPackageName, getServicePackageName())
-                            .setCounterValue(numberFields)
-                            .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
-                                    averageScore));
-                }
-                mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
-                        clientState, selectedDatasets, ignoredDatasets,
-                        changedFieldIds, changedDatasetIds,
-                        manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                        detectedFieldsIds, detectedFieldClassifications));
+        if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
+            if (sVerbose) {
+                Slog.v(TAG, "logContextCommitted() with FieldClassification: id=" + sessionId
+                        + ", selectedDatasets=" + selectedDatasets
+                        + ", ignoredDatasetIds=" + ignoredDatasets
+                        + ", changedAutofillIds=" + changedFieldIds
+                        + ", changedDatasetIds=" + changedDatasetIds
+                        + ", manuallyFilledFieldIds=" + manuallyFilledFieldIds
+                        + ", detectedFieldIds=" + detectedFieldIdsList
+                        + ", detectedFieldClassifications=" + detectedFieldClassificationsList);
             }
+            AutofillId[] detectedFieldsIds = null;
+            FieldClassification[] detectedFieldClassifications = null;
+            if (detectedFieldIdsList != null) {
+                detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
+                detectedFieldIdsList.toArray(detectedFieldsIds);
+                detectedFieldClassifications =
+                        new FieldClassification[detectedFieldClassificationsList.size()];
+                detectedFieldClassificationsList.toArray(detectedFieldClassifications);
+
+                final int numberFields = detectedFieldsIds.length;
+                int totalSize = 0;
+                float totalScore = 0;
+                for (int i = 0; i < numberFields; i++) {
+                    final FieldClassification fc = detectedFieldClassifications[i];
+                    final List<Match> matches = fc.getMatches();
+                    final int size = matches.size();
+                    totalSize += size;
+                    for (int j = 0; j < size; j++) {
+                        totalScore += matches.get(j).getScore();
+                    }
+                }
+
+                final int averageScore = (int) ((totalScore * 100) / totalSize);
+                mMetricsLogger.write(Helper
+                        .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
+                                appPackageName, getServicePackageName())
+                        .setCounterValue(numberFields)
+                        .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
+                                averageScore));
+            }
+            mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
+                    clientState, selectedDatasets, ignoredDatasets,
+                    changedFieldIds, changedDatasetIds,
+                    manuallyFilledFieldIds, manuallyFilledDatasetIds,
+                    detectedFieldsIds, detectedFieldClassifications));
         }
     }
 
@@ -801,14 +969,15 @@
     // Called by AutofillManager
     void setUserData(int callingUid, UserData userData) {
         synchronized (mLock) {
-            if (isCalledByServiceLocked("setUserData", callingUid)) {
-                mUserData = userData;
-                // Log it
-                int numberFields = mUserData == null ? 0: mUserData.getRemoteIds().length;
-                mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_USERDATA_UPDATED,
-                        getServicePackageName(), null)
-                        .setCounterValue(numberFields));
+            if (!isCalledByServiceLocked("setUserData", callingUid)) {
+                return;
             }
+            mUserData = userData;
+            // Log it
+            int numberFields = mUserData == null ? 0: mUserData.getRemoteIds().length;
+            mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_USERDATA_UPDATED,
+                    getServicePackageName(), null)
+                    .setCounterValue(numberFields));
         }
     }
 
@@ -919,6 +1088,11 @@
             pw.println();
             mUserData.dump(prefix2, pw);
         }
+
+        pw.print(prefix); pw.print("Available Field Classification algorithms: ");
+        pw.println(mFcService.getAvailableAlgorithms());
+        pw.print(prefix); pw.print("Default Field Classification algorithm: ");
+        pw.println(mFcService.getDefaultAlgorithm());
     }
 
     void destroySessionsLocked() {
@@ -966,11 +1140,13 @@
                 final IAutoFillManagerClient client = clients.getBroadcastItem(i);
                 try {
                     final boolean resetSession;
+                    final boolean isEnabled;
                     synchronized (mLock) {
                         resetSession = resetClient || isClientSessionDestroyedLocked(client);
+                        isEnabled = isEnabledLocked();
                     }
                     int flags = 0;
-                    if (isEnabled()) {
+                    if (isEnabled) {
                         flags |= AutofillManager.SET_STATE_FLAG_ENABLED;
                     }
                     if (resetSession) {
@@ -1006,7 +1182,7 @@
         return true;
     }
 
-    boolean isEnabled() {
+    boolean isEnabledLocked() {
         return mSetupComplete && mInfo != null && !mDisabled;
     }
 
@@ -1095,9 +1271,9 @@
     }
 
     // Called by AutofillManager, checks UID.
-    boolean isFieldClassificationEnabled(int uid) {
+    boolean isFieldClassificationEnabled(int callingUid) {
         synchronized (mLock) {
-            if (!isCalledByServiceLocked("isFieldClassificationEnabled", uid)) {
+            if (!isCalledByServiceLocked("isFieldClassificationEnabled", callingUid)) {
                 return false;
             }
             return isFieldClassificationEnabledLocked();
@@ -1112,6 +1288,28 @@
                 mUserId) == 1;
     }
 
+    FieldClassificationAlgorithmService getFieldClassificationService() {
+        return mFcService;
+    }
+
+    List<String> getAvailableFieldClassificationAlgorithms(int callingUid) {
+        synchronized (mLock) {
+            if (!isCalledByServiceLocked("getFCAlgorithms()", callingUid)) {
+                return null;
+            }
+        }
+        return mFcService.getAvailableAlgorithms();
+    }
+
+    String getDefaultFieldClassificationAlgorithm(int callingUid) {
+        synchronized (mLock) {
+            if (!isCalledByServiceLocked("getDefaultFCAlgorithm()", callingUid)) {
+                return null;
+            }
+        }
+        return mFcService.getDefaultAlgorithm();
+    }
+
     @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 ad43ec2..f5d1336 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -25,11 +25,11 @@
 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
 
+import static com.android.server.autofill.AutofillManagerServiceImpl.FieldClassificationAlgorithmService.EXTRA_SCORES;
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sPartitionMaxCount;
 import static com.android.server.autofill.Helper.sVerbose;
 import static com.android.server.autofill.Helper.toArray;
-import static com.android.server.autofill.ViewState.STATE_AUTOFILLED;
 import static com.android.server.autofill.ViewState.STATE_RESTARTED_SESSION;
 
 import android.annotation.NonNull;
@@ -51,11 +51,14 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcelable;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
+import android.service.autofill.FieldClassification;
 import android.service.autofill.FieldClassification.Match;
+import android.service.carrier.CarrierMessagingService.ResultCallback;
 import android.service.autofill.FillContext;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
@@ -65,8 +68,6 @@
 import android.service.autofill.SaveRequest;
 import android.service.autofill.UserData;
 import android.service.autofill.ValueFinder;
-import android.service.autofill.EditDistanceScorer;
-import android.service.autofill.FieldClassification;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LocalLog;
@@ -85,16 +86,20 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.autofill.AutofillManagerServiceImpl.FieldClassificationAlgorithmService;
+import com.android.server.autofill.AutofillManagerServiceImpl.FieldClassificationScores;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.PendingUi;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * A session for a given activity.
@@ -907,7 +912,15 @@
      * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
      * when necessary.
      */
-    public void logContextCommittedLocked() {
+    public void logContextCommitted() {
+        mHandlerCaller.getHandler().post(() -> {
+            synchronized (mLock) {
+                logContextCommittedLocked();
+            }
+        });
+    }
+
+    private void logContextCommittedLocked() {
         final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
         if (lastResponse == null) return;
 
@@ -963,18 +976,6 @@
 
         final UserData userData = mService.getUserData();
 
-        final ArrayList<AutofillId> detectedFieldIds;
-        final ArrayList<FieldClassification> detectedFieldClassifications;
-
-        if (userData != null) {
-            final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize();
-            detectedFieldIds = new ArrayList<>(maxFieldsSize);
-            detectedFieldClassifications = new ArrayList<>(maxFieldsSize);
-        } else {
-            detectedFieldIds = null;
-            detectedFieldClassifications = null;
-        }
-
         for (int i = 0; i < mViewStates.size(); i++) {
             final ViewState viewState = mViewStates.valueAt(i);
             final int state = viewState.getState();
@@ -1079,31 +1080,14 @@
                         } // for j
                     }
 
-                    // Sets field classification score for field
-                    if (userData!= null) {
-                        setScore(detectedFieldIds, detectedFieldClassifications, userData,
-                                viewState.id, currentValue);
-                    }
                 } // else
             } // else
         }
 
-        if (sVerbose) {
-            Slog.v(TAG, "logContextCommitted(): id=" + id
-                    + ", selectedDatasetids=" + mSelectedDatasetIds
-                    + ", ignoredDatasetIds=" + ignoredDatasets
-                    + ", changedAutofillIds=" + changedFieldIds
-                    + ", changedDatasetIds=" + changedDatasetIds
-                    + ", manuallyFilledIds=" + manuallyFilledIds
-                    + ", detectedFieldIds=" + detectedFieldIds
-                    + ", detectedFieldClassifications=" + detectedFieldClassifications
-                    );
-        }
-
         ArrayList<AutofillId> manuallyFilledFieldIds = null;
         ArrayList<ArrayList<String>> manuallyFilledDatasetIds = null;
 
-        // Must "flatten" the map to the parcellable collection primitives
+        // Must "flatten" the map to the parcelable collection primitives
         if (manuallyFilledIds != null) {
             final int size = manuallyFilledIds.size();
             manuallyFilledFieldIds = new ArrayList<>(size);
@@ -1116,20 +1100,35 @@
             }
         }
 
-        mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
-                changedFieldIds, changedDatasetIds,
-                manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                detectedFieldIds, detectedFieldClassifications, mComponentName.getPackageName());
+        // Sets field classification scores
+        final FieldClassificationAlgorithmService fcService =
+                mService.getFieldClassificationService();
+        if (userData != null && fcService != null) {
+            logFieldClassificationScoreLocked(fcService, ignoredDatasets, changedFieldIds,
+                    changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds,
+                    manuallyFilledIds, userData,
+                    mViewStates.values());
+        } else {
+            mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
+                    ignoredDatasets, changedFieldIds, changedDatasetIds,
+                    manuallyFilledFieldIds, manuallyFilledDatasetIds,
+                    mComponentName.getPackageName());
+        }
     }
 
     /**
      * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for
      * {@code fieldId} based on its {@code currentValue} and {@code userData}.
      */
-    private static void setScore(@NonNull ArrayList<AutofillId> detectedFieldIds,
-            @NonNull ArrayList<FieldClassification> detectedFieldClassifications,
-            @NonNull UserData userData, @NonNull AutofillId fieldId,
-            @NonNull AutofillValue currentValue) {
+    private void logFieldClassificationScoreLocked(
+            @NonNull AutofillManagerServiceImpl.FieldClassificationAlgorithmService fcService,
+            @NonNull ArraySet<String> ignoredDatasets,
+            @NonNull ArrayList<AutofillId> changedFieldIds,
+            @NonNull ArrayList<String> changedDatasetIds,
+            @NonNull ArrayList<AutofillId> manuallyFilledFieldIds,
+            @NonNull ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
+            @NonNull ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds,
+            @NonNull UserData userData, @NonNull Collection<ViewState> viewStates) {
 
         final String[] userValues = userData.getValues();
         final String[] remoteIds = userData.getRemoteIds();
@@ -1143,26 +1142,72 @@
             return;
         }
 
-        ArrayList<Match> matches = null;
-        for (int i = 0; i < userValues.length; i++) {
-            String remoteId = remoteIds[i];
-            final String value = userValues[i];
-            final float score = userData.getScorer().getScore(currentValue, value);
-            if (score > 0) {
-                if (sVerbose) {
-                    Slog.v(TAG, "adding score " + score + " at index " + i + " and id " + fieldId);
-                }
-                if (matches == null) {
-                    matches = new ArrayList<>(userValues.length);
-                }
-                matches.add(new Match(remoteId, score));
+        final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize();
+
+        final ArrayList<AutofillId> detectedFieldIds = new ArrayList<>(maxFieldsSize);
+        final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>(
+                maxFieldsSize);
+
+        final String algorithm = userData.getFieldClassificationAlgorithm();
+        final Bundle algorithmArgs = userData.getAlgorithmArgs();
+        final int viewsSize = viewStates.size();
+
+        // First, we get all scores.
+        final AutofillId[] fieldIds = new AutofillId[viewsSize];
+        final ArrayList<AutofillValue> currentValues = new ArrayList<>(viewsSize);
+        int k = 0;
+        for (ViewState viewState : viewStates) {
+            currentValues.add(viewState.getCurrentValue());
+            fieldIds[k++] = viewState.id;
+        }
+
+        final RemoteCallback callback = new RemoteCallback((result) -> {
+            if (result == null) {
+                if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
+                mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
+                        ignoredDatasets, changedFieldIds, changedDatasetIds,
+                        manuallyFilledFieldIds, manuallyFilledDatasetIds,
+                        mComponentName.getPackageName());
+                return;
             }
-            else if (sVerbose) Slog.v(TAG, "skipping score 0 at index " + i + " and id " + fieldId);
-        }
-        if (matches != null) {
-            detectedFieldIds.add(fieldId);
-            detectedFieldClassifications.add(new FieldClassification(matches));
-        }
+            final FieldClassificationScores matrix = result.getParcelable(EXTRA_SCORES);
+
+            // Then use the results.
+            for (int i = 0; i < viewsSize; i++) {
+                final AutofillId fieldId = fieldIds[i];
+
+                ArrayList<Match> matches = null;
+                for (int j = 0; j < userValues.length; j++) {
+                    String remoteId = remoteIds[j];
+                    final String actualAlgorithm = matrix.algorithmName;
+                    final float score = matrix.scores[i][j];
+                    if (score > 0) {
+                        if (sVerbose) {
+                            Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
+                                    + fieldId);
+                        }
+                        if (matches == null) {
+                            matches = new ArrayList<>(userValues.length);
+                        }
+                        matches.add(new Match(remoteId, score, actualAlgorithm));
+                    }
+                    else if (sVerbose) {
+                        Slog.v(TAG, "skipping score 0 at index " + j + " and id " + fieldId);
+                    }
+                }
+                if (matches != null) {
+                    detectedFieldIds.add(fieldId);
+                    detectedFieldClassifications.add(new FieldClassification(matches));
+                }
+            }
+
+            mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
+                    ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
+                    manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
+                    mComponentName.getPackageName());
+        });
+
+        fcService.getScores(algorithm, algorithmArgs, currentValues, userValues, callback);
     }
 
     /**
@@ -1360,7 +1405,10 @@
                 }
 
                 if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!");
-                mService.logSaveShown(id, mClientState);
+
+                // Use handler so logContextCommitted() is logged first
+                mHandlerCaller.getHandler().post(() -> mService.logSaveShown(id, mClientState));
+
                 final IAutoFillManagerClient client = getClient();
                 mPendingSaveUi = new PendingUi(mActivityToken, id, client);
                 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
@@ -1560,7 +1608,7 @@
      *
      * <p>A new request will be started in 2 scenarios:
      * <ol>
-     *   <li>If the user manually requested autofill after the view was already filled.
+     *   <li>If the user manually requested autofill.
      *   <li>If the view is part of a new partition.
      * </ol>
      *
@@ -1568,14 +1616,10 @@
      * @param viewState The view that is entered.
      * @param flags The flag that was passed by the AutofillManager.
      */
-    private void requestNewFillResponseIfNecessaryLocked(@NonNull AutofillId id,
+    private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
             @NonNull ViewState viewState, int flags) {
-        // First check if this is a manual request after view was autofilled.
-        final int state = viewState.getState();
-        final boolean restart = (state & STATE_AUTOFILLED) != 0
-                && (flags & FLAG_MANUAL_REQUEST) != 0;
-        if (restart) {
-            if (sDebug) Slog.d(TAG, "Re-starting session on view  " + id);
+        if ((flags & FLAG_MANUAL_REQUEST) != 0) {
+            if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
             viewState.setState(STATE_RESTARTED_SESSION);
             requestNewFillResponseLocked(flags);
             return;
@@ -1730,7 +1774,7 @@
                 if (sVerbose && virtualBounds != null) {
                     Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds);
                 }
-                requestNewFillResponseIfNecessaryLocked(id, viewState, flags);
+                requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
 
                 // Remove the UI if the ViewState has changed.
                 if (mCurrentViewId != viewState.id) {
diff --git a/services/backup/java/com/android/server/backup/BackupManagerConstants.java b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
index 245241c..1a54e95 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerConstants.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
@@ -24,6 +24,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.KeyValueListParser;
 import android.util.Slog;
 
@@ -51,6 +52,8 @@
             "full_backup_require_charging";
     private static final String FULL_BACKUP_REQUIRED_NETWORK_TYPE =
             "full_backup_required_network_type";
+    private static final String BACKUP_FINISHED_NOTIFICATION_RECEIVERS =
+            "backup_finished_notification_receivers";
 
     // Hard coded default values.
     private static final long DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS =
@@ -63,6 +66,7 @@
             24 * AlarmManager.INTERVAL_HOUR;
     private static final boolean DEFAULT_FULL_BACKUP_REQUIRE_CHARGING = true;
     private static final int DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE = 2;
+    private static final String DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS = "";
 
     // Backup manager constants.
     private long mKeyValueBackupIntervalMilliseconds;
@@ -72,6 +76,7 @@
     private long mFullBackupIntervalMilliseconds;
     private boolean mFullBackupRequireCharging;
     private int mFullBackupRequiredNetworkType;
+    private String[] mBackupFinishedNotificationReceivers;
 
     private ContentResolver mResolver;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -80,9 +85,6 @@
         super(handler);
         mResolver = resolver;
         updateSettings();
-    }
-
-    public void start() {
         mResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.BACKUP_MANAGER_CONSTANTS), false, this);
     }
@@ -116,6 +118,14 @@
                 DEFAULT_FULL_BACKUP_REQUIRE_CHARGING);
         mFullBackupRequiredNetworkType = mParser.getInt(FULL_BACKUP_REQUIRED_NETWORK_TYPE,
                 DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE);
+        String backupFinishedNotificationReceivers = mParser.getString(
+                BACKUP_FINISHED_NOTIFICATION_RECEIVERS,
+                DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS);
+        if (backupFinishedNotificationReceivers.isEmpty()) {
+            mBackupFinishedNotificationReceivers = new String[] {};
+        } else {
+            mBackupFinishedNotificationReceivers = backupFinishedNotificationReceivers.split(":");
+        }
     }
 
     // The following are access methods for the individual parameters.
@@ -167,7 +177,6 @@
             Slog.v(TAG, "getFullBackupRequireCharging(...) returns " + mFullBackupRequireCharging);
         }
         return mFullBackupRequireCharging;
-
     }
 
     public synchronized int getFullBackupRequiredNetworkType() {
@@ -177,4 +186,15 @@
         }
         return mFullBackupRequiredNetworkType;
     }
+
+    /**
+     * Returns an array of package names that should be notified whenever a backup finishes.
+     */
+    public synchronized String[] getBackupFinishedNotificationReceivers() {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
+            Slog.v(TAG, "getBackupFinishedNotificationReceivers(...) returns "
+                    + TextUtils.join(", ", mBackupFinishedNotificationReceivers));
+        }
+        return mBackupFinishedNotificationReceivers;
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java b/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
index 86462d8..7b021c6 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
@@ -186,6 +186,8 @@
 
   boolean isAppEligibleForBackup(String packageName);
 
+  String[] filterAppsEligibleForBackup(String[] packages);
+
   void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 
   IBackupManager getBackupManagerBinder();
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 2d2993d..3cf374f 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -70,12 +70,28 @@
     private static final String DEFAULT_HOME_KEY = "@home@";
 
     // Sentinel: start of state file, followed by a version number
+    // Note that STATE_FILE_VERSION=2 is tied to UNDEFINED_ANCESTRAL_RECORD_VERSION=-1 *as well as*
+    // ANCESTRAL_RECORD_VERSION=1 (introduced Android P).
+    // Should the ANCESTRAL_RECORD_VERSION be bumped up in the future, STATE_FILE_VERSION will also
+    // need bumping up, assuming more data needs saving to the state file.
     private static final String STATE_FILE_HEADER = "=state=";
     private static final int STATE_FILE_VERSION = 2;
 
-    // Current version of the saved ancestral-dataset file format
+    // key under which we store the saved ancestral-dataset format (starting from Android P)
+    // IMPORTANT: this key needs to come first in the restore data stream (to find out
+    // whether this version of Android knows how to restore the incoming data set), so it needs
+    // to be always the first one in alphabetical order of all the keys
+    private static final String ANCESTRAL_RECORD_KEY = "@ancestral_record@";
+
+    // Current version of the saved ancestral-dataset format
+    // Note that this constant was not used until Android P, and started being used
+    // to version @pm@ data for forwards-compatibility.
     private static final int ANCESTRAL_RECORD_VERSION = 1;
 
+    // Undefined version of the saved ancestral-dataset file format means that the restore data
+    // is coming from pre-Android P device.
+    private static final int UNDEFINED_ANCESTRAL_RECORD_VERSION = -1;
+
     private List<PackageInfo> mAllPackages;
     private PackageManager mPackageManager;
     // version & signature info of each app in a restore set
@@ -175,9 +191,8 @@
         // additional involvement by the transport to obtain.
         return mRestoredSignatures.keySet();
     }
-    
-    // The backed up data is the signature block for each app, keyed by
-    // the package name.
+
+    // The backed up data is the signature block for each app, keyed by the package name.
     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
             ParcelFileDescriptor newState) {
         if (DEBUG) Slog.v(TAG, "onBackup()");
@@ -196,6 +211,22 @@
             mExisting.clear();
         }
 
+        /*
+         * Ancestral record version:
+         *
+         * int ancestralRecordVersion -- the version of the format in which this backup set is
+         *                               produced
+         */
+        try {
+            if (DEBUG) Slog.v(TAG, "Storing ancestral record version key");
+            outputBufferStream.writeInt(ANCESTRAL_RECORD_VERSION);
+            writeEntity(data, ANCESTRAL_RECORD_KEY, outputBuffer.toByteArray());
+        } catch (IOException e) {
+            // Real error writing data
+            Slog.e(TAG, "Unable to write package backup data file!");
+            return;
+        }
+
         long homeVersion = 0;
         ArrayList<byte[]> homeSigHashes = null;
         PackageInfo homeInfo = null;
@@ -230,6 +261,7 @@
                     Slog.i(TAG, "Home preference changed; backing up new state " + home);
                 }
                 if (home != null) {
+                    outputBuffer.reset();
                     outputBufferStream.writeUTF(home.flattenToString());
                     outputBufferStream.writeLong(homeVersion);
                     outputBufferStream.writeUTF(homeInstaller != null ? homeInstaller : "" );
@@ -244,8 +276,8 @@
              * Global metadata:
              *
              * int SDKversion -- the SDK version of the OS itself on the device
-             *                   that produced this backup set.  Used to reject
-             *                   backups from later OSes onto earlier ones.
+             *                   that produced this backup set. Before Android P it was used to
+             *                   reject backups from later OSes onto earlier ones.
              * String incremental -- the incremental release name of the OS stored in
              *                       the backup set.
              */
@@ -354,7 +386,7 @@
         // Finally, write the new state blob -- just the list of all apps we handled
         writeStateFile(mAllPackages, home, homeVersion, homeSigHashes, newState);
     }
-    
+
     private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
             throws IOException {
         data.writeEntityHeader(key, bytes.length);
@@ -366,83 +398,57 @@
     // image.  We'll use those later to determine what we can legitimately restore.
     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
             throws IOException {
-        List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
-        HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
         if (DEBUG) Slog.v(TAG, "onRestore()");
-        int storedSystemVersion = -1;
 
-        while (data.readNextHeader()) {
+        // we expect the ANCESTRAL_RECORD_KEY ("@ancestral_record@") to always come first in the
+        // restore set - based on that value we use different mechanisms to consume the data;
+        // if the ANCESTRAL_RECORD_KEY is missing in the restore set, it means that the data is
+        // is coming from a pre-Android P device, and we consume the header data in the legacy way
+        // TODO: add a CTS test to verify that backups of PMBA generated on Android P+ always
+        //       contain the ANCESTRAL_RECORD_KEY, and it's always the first key
+        int ancestralRecordVersion = getAncestralRecordVersionValue(data);
+
+        RestoreDataConsumer consumer = getRestoreDataConsumer(ancestralRecordVersion);
+        if (consumer == null) {
+            Slog.w(TAG, "Ancestral restore set version is unknown"
+                    + " to this Android version; not restoring");
+            return;
+        } else {
+            consumer.consumeRestoreData(data);
+        }
+    }
+
+    private int getAncestralRecordVersionValue(BackupDataInput data) throws IOException {
+        int ancestralRecordVersionValue = UNDEFINED_ANCESTRAL_RECORD_VERSION;
+        if (data.readNextHeader()) {
             String key = data.getKey();
             int dataSize = data.getDataSize();
 
             if (DEBUG) Slog.v(TAG, "   got key=" + key + " dataSize=" + dataSize);
 
-            // generic setup to parse any entity data
-            byte[] inputBytes = new byte[dataSize];
-            data.readEntityData(inputBytes, 0, dataSize);
-            ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
-            DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
+            if (ANCESTRAL_RECORD_KEY.equals(key)) {
+                // generic setup to parse any entity data
+                byte[] inputBytes = new byte[dataSize];
+                data.readEntityData(inputBytes, 0, dataSize);
+                ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
+                DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
 
-            if (key.equals(GLOBAL_METADATA_KEY)) {
-                int storedSdkVersion = inputBufferStream.readInt();
-                if (DEBUG) Slog.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
-                if (storedSystemVersion > Build.VERSION.SDK_INT) {
-                    // returning before setting the sig map means we rejected the restore set
-                    Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
-                    return;
-                }
-                mStoredSdkVersion = storedSdkVersion;
-                mStoredIncrementalVersion = inputBufferStream.readUTF();
-                mHasMetadata = true;
-                if (DEBUG) {
-                    Slog.i(TAG, "Restore set version " + storedSystemVersion
-                            + " is compatible with OS version " + Build.VERSION.SDK_INT
-                            + " (" + mStoredIncrementalVersion + " vs "
-                            + Build.VERSION.INCREMENTAL + ")");
-                }
-            } else if (key.equals(DEFAULT_HOME_KEY)) {
-                String cn = inputBufferStream.readUTF();
-                mRestoredHome = ComponentName.unflattenFromString(cn);
-                mRestoredHomeVersion = inputBufferStream.readLong();
-                mRestoredHomeInstaller = inputBufferStream.readUTF();
-                mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
-                if (DEBUG) {
-                    Slog.i(TAG, "   read preferred home app " + mRestoredHome
-                            + " version=" + mRestoredHomeVersion
-                            + " installer=" + mRestoredHomeInstaller
-                            + " sig=" + mRestoredHomeSigHashes);
-                }
-            } else {
-                // it's a file metadata record
-                int versionCodeInt = inputBufferStream.readInt();
-                long versionCode;
-                if (versionCodeInt == Integer.MIN_VALUE) {
-                    versionCode = inputBufferStream.readLong();
-                } else {
-                    versionCode = versionCodeInt;
-                }
-                ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
-                if (DEBUG) {
-                    Slog.i(TAG, "   read metadata for " + key
-                            + " dataSize=" + dataSize
-                            + " versionCode=" + versionCode + " sigs=" + sigs);
-                }
-                
-                if (sigs == null || sigs.size() == 0) {
-                    Slog.w(TAG, "Not restoring package " + key
-                            + " since it appears to have no signatures.");
-                    continue;
-                }
-
-                ApplicationInfo app = new ApplicationInfo();
-                app.packageName = key;
-                restoredApps.add(app);
-                sigMap.put(key, new Metadata(versionCode, sigs));
+                ancestralRecordVersionValue = inputBufferStream.readInt();
             }
         }
+        return ancestralRecordVersionValue;
+    }
 
-        // On successful completion, cache the signature map for the Backup Manager to use
-        mRestoredSignatures = sigMap;
+    private RestoreDataConsumer getRestoreDataConsumer(int ancestralRecordVersion) {
+        switch (ancestralRecordVersion) {
+            case UNDEFINED_ANCESTRAL_RECORD_VERSION:
+                return new LegacyRestoreDataConsumer();
+            case 1:
+                return new AncestralVersion1RestoreDataConsumer();
+            default:
+                Slog.e(TAG, "Unrecognized ANCESTRAL_RECORD_VERSION: " + ancestralRecordVersion);
+                return null;
+        }
     }
 
     private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
@@ -639,4 +645,173 @@
             Slog.e(TAG, "Unable to write package manager state file!");
         }
     }
+
+    interface RestoreDataConsumer {
+        void consumeRestoreData(BackupDataInput data) throws IOException;
+    }
+
+    private class LegacyRestoreDataConsumer implements RestoreDataConsumer {
+
+        public void consumeRestoreData(BackupDataInput data) throws IOException {
+            List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
+            HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
+            int storedSystemVersion = -1;
+
+            if (DEBUG) Slog.i(TAG, "Using LegacyRestoreDataConsumer");
+            // we already have the first header read and "cached", since ANCESTRAL_RECORD_KEY
+            // was missing
+            while (true) {
+                String key = data.getKey();
+                int dataSize = data.getDataSize();
+
+                if (DEBUG) Slog.v(TAG, "   got key=" + key + " dataSize=" + dataSize);
+
+                // generic setup to parse any entity data
+                byte[] inputBytes = new byte[dataSize];
+                data.readEntityData(inputBytes, 0, dataSize);
+                ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
+                DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
+
+                if (key.equals(GLOBAL_METADATA_KEY)) {
+                    int storedSdkVersion = inputBufferStream.readInt();
+                    if (DEBUG) Slog.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
+                    mStoredSdkVersion = storedSdkVersion;
+                    mStoredIncrementalVersion = inputBufferStream.readUTF();
+                    mHasMetadata = true;
+                    if (DEBUG) {
+                        Slog.i(TAG, "Restore set version " + storedSystemVersion
+                                + " is compatible with OS version " + Build.VERSION.SDK_INT
+                                + " (" + mStoredIncrementalVersion + " vs "
+                                + Build.VERSION.INCREMENTAL + ")");
+                    }
+                } else if (key.equals(DEFAULT_HOME_KEY)) {
+                    String cn = inputBufferStream.readUTF();
+                    mRestoredHome = ComponentName.unflattenFromString(cn);
+                    mRestoredHomeVersion = inputBufferStream.readLong();
+                    mRestoredHomeInstaller = inputBufferStream.readUTF();
+                    mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
+                    if (DEBUG) {
+                        Slog.i(TAG, "   read preferred home app " + mRestoredHome
+                                + " version=" + mRestoredHomeVersion
+                                + " installer=" + mRestoredHomeInstaller
+                                + " sig=" + mRestoredHomeSigHashes);
+                    }
+                } else {
+                    // it's a file metadata record
+                    int versionCodeInt = inputBufferStream.readInt();
+                    long versionCode;
+                    if (versionCodeInt == Integer.MIN_VALUE) {
+                        versionCode = inputBufferStream.readLong();
+                    } else {
+                        versionCode = versionCodeInt;
+                    }
+                    ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
+                    if (DEBUG) {
+                        Slog.i(TAG, "   read metadata for " + key
+                                + " dataSize=" + dataSize
+                                + " versionCode=" + versionCode + " sigs=" + sigs);
+                    }
+
+                    if (sigs == null || sigs.size() == 0) {
+                        Slog.w(TAG, "Not restoring package " + key
+                                + " since it appears to have no signatures.");
+                        continue;
+                    }
+
+                    ApplicationInfo app = new ApplicationInfo();
+                    app.packageName = key;
+                    restoredApps.add(app);
+                    sigMap.put(key, new Metadata(versionCode, sigs));
+                }
+
+                boolean readNextHeader = data.readNextHeader();
+                if (!readNextHeader) {
+                    if (DEBUG) Slog.v(TAG, "LegacyRestoreDataConsumer:"
+                            + " we're done reading all the headers");
+                    break;
+                }
+            }
+
+            // On successful completion, cache the signature map for the Backup Manager to use
+            mRestoredSignatures = sigMap;
+        }
+    }
+
+    private class AncestralVersion1RestoreDataConsumer implements RestoreDataConsumer {
+
+        public void consumeRestoreData(BackupDataInput data) throws IOException {
+            List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
+            HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
+            int storedSystemVersion = -1;
+
+            if (DEBUG) Slog.i(TAG, "Using AncestralVersion1RestoreDataConsumer");
+            while (data.readNextHeader()) {
+                String key = data.getKey();
+                int dataSize = data.getDataSize();
+
+                if (DEBUG) Slog.v(TAG, "   got key=" + key + " dataSize=" + dataSize);
+
+                // generic setup to parse any entity data
+                byte[] inputBytes = new byte[dataSize];
+                data.readEntityData(inputBytes, 0, dataSize);
+                ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
+                DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
+
+                if (key.equals(GLOBAL_METADATA_KEY)) {
+                    int storedSdkVersion = inputBufferStream.readInt();
+                    if (DEBUG) Slog.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
+                    mStoredSdkVersion = storedSdkVersion;
+                    mStoredIncrementalVersion = inputBufferStream.readUTF();
+                    mHasMetadata = true;
+                    if (DEBUG) {
+                        Slog.i(TAG, "Restore set version " + storedSystemVersion
+                                + " is compatible with OS version " + Build.VERSION.SDK_INT
+                                + " (" + mStoredIncrementalVersion + " vs "
+                                + Build.VERSION.INCREMENTAL + ")");
+                    }
+                } else if (key.equals(DEFAULT_HOME_KEY)) {
+                    String cn = inputBufferStream.readUTF();
+                    mRestoredHome = ComponentName.unflattenFromString(cn);
+                    mRestoredHomeVersion = inputBufferStream.readLong();
+                    mRestoredHomeInstaller = inputBufferStream.readUTF();
+                    mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
+                    if (DEBUG) {
+                        Slog.i(TAG, "   read preferred home app " + mRestoredHome
+                                + " version=" + mRestoredHomeVersion
+                                + " installer=" + mRestoredHomeInstaller
+                                + " sig=" + mRestoredHomeSigHashes);
+                    }
+                } else {
+                    // it's a file metadata record
+                    int versionCodeInt = inputBufferStream.readInt();
+                    long versionCode;
+                    if (versionCodeInt == Integer.MIN_VALUE) {
+                        versionCode = inputBufferStream.readLong();
+                    } else {
+                        versionCode = versionCodeInt;
+                    }
+                    ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
+                    if (DEBUG) {
+                        Slog.i(TAG, "   read metadata for " + key
+                                + " dataSize=" + dataSize
+                                + " versionCode=" + versionCode + " sigs=" + sigs);
+                    }
+
+                    if (sigs == null || sigs.size() == 0) {
+                        Slog.w(TAG, "Not restoring package " + key
+                                + " since it appears to have no signatures.");
+                        continue;
+                    }
+
+                    ApplicationInfo app = new ApplicationInfo();
+                    app.packageName = key;
+                    restoredApps.add(app);
+                    sigMap.put(key, new Metadata(versionCode, sigs));
+                }
+            }
+
+            // On successful completion, cache the signature map for the Backup Manager to use
+            mRestoredSignatures = sigMap;
+        }
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 94b06b6..03591a8 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -148,6 +148,7 @@
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
 import java.util.Random;
@@ -203,6 +204,8 @@
 
     public static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
     public static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
+    public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
+    public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
 
     // Timeout interval for deciding that a bind or clear-data has taken too long
     private static final long TIMEOUT_INTERVAL = 10 * 1000;
@@ -1084,34 +1087,34 @@
     }
 
     /**
-     * Maintain persistent state around whether need to do an initialize operation.
-     * Must be called with the queue lock held.
+     * Maintain persistent state around whether need to do an initialize operation. This will lock
+     * on {@link #getQueueLock()}.
      */
-    @GuardedBy("mQueueLock")
-    public void recordInitPendingLocked(
+    public void recordInitPending(
             boolean isPending, String transportName, String transportDirName) {
-        if (MORE_DEBUG) {
-            Slog.i(TAG, "recordInitPendingLocked: " + isPending
-                    + " on transport " + transportName);
-        }
-
-        File stateDir = new File(mBaseStateDir, transportDirName);
-        File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
-
-        if (isPending) {
-            // We need an init before we can proceed with sending backup data.
-            // Record that with an entry in our set of pending inits, as well as
-            // journaling it via creation of a sentinel file.
-            mPendingInits.add(transportName);
-            try {
-                (new FileOutputStream(initPendingFile)).close();
-            } catch (IOException ioe) {
-                // Something is badly wrong with our permissions; just try to move on
+        synchronized (mQueueLock) {
+            if (MORE_DEBUG) {
+                Slog.i(TAG, "recordInitPending(" + isPending + ") on transport " + transportName);
             }
-        } else {
-            // No more initialization needed; wipe the journal and reset our state.
-            initPendingFile.delete();
-            mPendingInits.remove(transportName);
+
+            File stateDir = new File(mBaseStateDir, transportDirName);
+            File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+
+            if (isPending) {
+                // We need an init before we can proceed with sending backup data.
+                // Record that with an entry in our set of pending inits, as well as
+                // journaling it via creation of a sentinel file.
+                mPendingInits.add(transportName);
+                try {
+                    (new FileOutputStream(initPendingFile)).close();
+                } catch (IOException ioe) {
+                    // Something is badly wrong with our permissions; just try to move on
+                }
+            } else {
+                // No more initialization needed; wipe the journal and reset our state.
+                initPendingFile.delete();
+                mPendingInits.remove(transportName);
+            }
         }
     }
 
@@ -1418,6 +1421,16 @@
     public void logBackupComplete(String packageName) {
         if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return;
 
+        for (String receiver : mConstants.getBackupFinishedNotificationReceivers()) {
+            final Intent notification = new Intent();
+            notification.setAction(BACKUP_FINISHED_ACTION);
+            notification.setPackage(receiver);
+            notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES |
+                    Intent.FLAG_RECEIVER_FOREGROUND);
+            notification.putExtra(BACKUP_FINISHED_PACKAGE_EXTRA, packageName);
+            mContext.sendBroadcastAsUser(notification, UserHandle.OWNER);
+        }
+
         mProcessedPackagesJournal.addPackage(packageName);
     }
 
@@ -1593,9 +1606,15 @@
             return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
         }
 
-        TransportClient transportClient =
-                mTransportManager.getCurrentTransportClient("BMS.requestBackup()");
-        if (transportClient == null) {
+        final TransportClient transportClient;
+        final String transportDirName;
+        try {
+            transportDirName =
+                    mTransportManager.getTransportDirName(
+                            mTransportManager.getCurrentTransportName());
+            transportClient =
+                    mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()");
+        } catch (TransportNotRegisteredException e) {
             BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
             monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
                     BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
@@ -1640,12 +1659,11 @@
                     + " k/v backups");
         }
 
-        String dirName = transportClient.getTransportDirName();
         boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
 
         Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
-        msg.obj = new BackupParams(transportClient, dirName, kvBackupList, fullBackupList, observer,
-                monitor, listener, true, nonIncrementalBackup);
+        msg.obj = new BackupParams(transportClient, transportDirName, kvBackupList, fullBackupList,
+                observer, monitor, listener, true, nonIncrementalBackup);
         mBackupHandler.sendMessage(msg);
         return BackupManager.SUCCESS;
     }
@@ -2311,7 +2329,9 @@
         final long oldId = Binder.clearCallingIdentity();
         try {
             mWakelock.acquire();
-            mBackupHandler.post(new PerformInitializeTask(this, transportNames, observer));
+            OnTaskFinishedListener listener = caller -> mWakelock.release();
+            mBackupHandler.post(
+                    new PerformInitializeTask(this, transportNames, observer, listener));
         } finally {
             Binder.restoreCallingIdentity(oldId);
         }
@@ -2802,7 +2822,7 @@
 
                         // build the set of transports for which we are posting an init
                         for (int i = 0; i < transportNames.size(); i++) {
-                            recordInitPendingLocked(
+                            recordInitPending(
                                     true,
                                     transportNames.get(i),
                                     transportDirNames.get(i));
@@ -2981,63 +3001,63 @@
         }
     }
 
-    // Select which transport to use for the next backup operation.
+    /** Selects transport {@code transportName} and returns previous selected transport. */
     @Override
-    public String selectBackupTransport(String transport) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
-                "selectBackupTransport");
+    public String selectBackupTransport(String transportName) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BACKUP, "selectBackupTransport");
 
         final long oldId = Binder.clearCallingIdentity();
         try {
-            String prevTransport = mTransportManager.selectTransport(transport);
-            updateStateForTransport(transport);
-            Slog.v(TAG, "selectBackupTransport() set " + mTransportManager.getCurrentTransportName()
-                    + " returning " + prevTransport);
-            return prevTransport;
+            String previousTransportName = mTransportManager.selectTransport(transportName);
+            updateStateForTransport(transportName);
+            Slog.v(TAG, "selectBackupTransport(transport = " + transportName
+                    + "): previous transport = " + previousTransportName);
+            return previousTransportName;
         } finally {
             Binder.restoreCallingIdentity(oldId);
         }
     }
 
     @Override
-    public void selectBackupTransportAsync(final ComponentName transport,
-            final ISelectBackupTransportCallback listener) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
-                "selectBackupTransportAsync");
+    public void selectBackupTransportAsync(
+            ComponentName transportComponent, ISelectBackupTransportCallback listener) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BACKUP, "selectBackupTransportAsync");
 
         final long oldId = Binder.clearCallingIdentity();
-
-        Slog.v(TAG, "selectBackupTransportAsync() called with transport " +
-                transport.flattenToShortString());
-
-        mTransportManager.ensureTransportReady(transport,
-                new TransportManager.TransportReadyCallback() {
-                    @Override
-                    public void onSuccess(String transportName) {
-                        mTransportManager.selectTransport(transportName);
-                        updateStateForTransport(mTransportManager.getCurrentTransportName());
-                        Slog.v(TAG, "Transport successfully selected: "
-                                + transport.flattenToShortString());
-                        try {
-                            listener.onSuccess(transportName);
-                        } catch (RemoteException e) {
-                            // Nothing to do here.
+        try {
+            String transportString = transportComponent.flattenToShortString();
+            Slog.v(TAG, "selectBackupTransportAsync(transport = " + transportString + ")");
+            mBackupHandler.post(
+                    () -> {
+                        String transportName = null;
+                        int result =
+                                mTransportManager.registerAndSelectTransport(transportComponent);
+                        if (result == BackupManager.SUCCESS) {
+                            try {
+                                transportName =
+                                        mTransportManager.getTransportName(transportComponent);
+                                updateStateForTransport(transportName);
+                            } catch (TransportNotRegisteredException e) {
+                                Slog.e(TAG, "Transport got unregistered");
+                                result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
+                            }
                         }
-                    }
 
-                    @Override
-                    public void onFailure(int reason) {
-                        Slog.v(TAG,
-                                "Failed to select transport: " + transport.flattenToShortString());
                         try {
-                            listener.onFailure(reason);
+                            if (transportName != null) {
+                                listener.onSuccess(transportName);
+                            } else {
+                                listener.onFailure(result);
+                            }
                         } catch (RemoteException e) {
-                            // Nothing to do here.
+                            Slog.e(TAG, "ISelectBackupTransportCallback listener not available");
                         }
-                    }
-                });
-
-        Binder.restoreCallingIdentity(oldId);
+                    });
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
+        }
     }
 
     private void updateStateForTransport(String newTransportName) {
@@ -3046,18 +3066,23 @@
                 Settings.Secure.BACKUP_TRANSPORT, newTransportName);
 
         // And update our current-dataset bookkeeping
-        IBackupTransport transport = mTransportManager.getTransportBinder(newTransportName);
-        if (transport != null) {
+        String callerLogString = "BMS.updateStateForTransport()";
+        TransportClient transportClient =
+                mTransportManager.getTransportClient(newTransportName, callerLogString);
+        if (transportClient != null) {
             try {
+                IBackupTransport transport = transportClient.connectOrThrow(callerLogString);
                 mCurrentToken = transport.getCurrentRestoreSet();
             } catch (Exception e) {
                 // Oops.  We can't know the current dataset token, so reset and figure it out
                 // when we do the next k/v backup operation on this transport.
                 mCurrentToken = 0;
+                Slog.w(TAG, "Transport " + newTransportName + " not available: current token = 0");
             }
         } else {
-            // The named transport isn't bound at this particular moment, so we can't
-            // know yet what its current dataset token is.  Reset as above.
+            Slog.w(TAG, "Transport " + newTransportName + " not registered: current token = 0");
+            // The named transport isn't registered, so we can't know what its current dataset token
+            // is. Reset as above.
             mCurrentToken = 0;
         }
     }
@@ -3081,29 +3106,30 @@
         }
     }
 
-    // Supply the configuration summary string for the given transport.  If the name is
-    // not one of the available transports, or if the transport does not supply any
-    // summary / destination string, the method can return null.
-    //
-    // This string is used VERBATIM as the summary text of the relevant Settings item!
+    /**
+     * Supply the current destination string for the given transport. If the name is not one of the
+     * registered transports the method will return null.
+     *
+     * <p>This string is used VERBATIM as the summary text of the relevant Settings item.
+     *
+     * @param transportName The name of the registered transport.
+     * @return The current destination string or null if the transport is not registered.
+     */
     @Override
     public String getDestinationString(String transportName) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
-                "getDestinationString");
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BACKUP, "getDestinationString");
 
-        final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
-        if (transport != null) {
-            try {
-                final String text = transport.currentDestinationString();
-                if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
-                return text;
-            } catch (Exception e) {
-                /* fall through to return null */
-                Slog.e(TAG, "Unable to get string from transport: " + e.getMessage());
+        try {
+            String string = mTransportManager.getTransportCurrentDestinationString(transportName);
+            if (MORE_DEBUG) {
+                Slog.d(TAG, "getDestinationString() returning " + string);
             }
+            return string;
+        } catch (TransportNotRegisteredException e) {
+            Slog.e(TAG, "Unable to get destination string from transport: " + e.getMessage());
+            return null;
         }
-
-        return null;
     }
 
     // Supply the manage-data intent for the given transport.
@@ -3373,31 +3399,41 @@
 
     @Override
     public boolean isAppEligibleForBackup(String packageName) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
-                "isAppEligibleForBackup");
-        try {
-            PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
-                    PackageManager.GET_SIGNATURES);
-            if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
-                            mPackageManager) ||
-                    AppBackupUtils.appIsStopped(packageInfo.applicationInfo) ||
-                    AppBackupUtils.appIsDisabled(packageInfo.applicationInfo, mPackageManager)) {
-                return false;
-            }
-            IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
-            if (transport != null) {
-                try {
-                    return transport.isAppEligibleForBackup(packageInfo,
-                            AppBackupUtils.appGetsFullBackup(packageInfo));
-                } catch (Exception e) {
-                    Slog.e(TAG, "Unable to ask about eligibility: " + e.getMessage());
-                }
-            }
-            // If transport is not present we couldn't tell that the package is not eligible.
-            return true;
-        } catch (NameNotFoundException e) {
-            return false;
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BACKUP, "isAppEligibleForBackup");
+
+        String callerLogString = "BMS.isAppEligibleForBackup";
+        TransportClient transportClient =
+                mTransportManager.getCurrentTransportClient(callerLogString);
+        boolean eligible =
+                AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport(
+                        transportClient, packageName, mPackageManager);
+        if (transportClient != null) {
+            mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
         }
+        return eligible;
+    }
+
+    @Override
+    public String[] filterAppsEligibleForBackup(String[] packages) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BACKUP, "filterAppsEligibleForBackup");
+
+        String callerLogString = "BMS.filterAppsEligibleForBackup";
+        TransportClient transportClient =
+                mTransportManager.getCurrentTransportClient(callerLogString);
+        List<String> eligibleApps = new LinkedList<>();
+        for (String packageName : packages) {
+            if (AppBackupUtils
+                    .appIsRunningAndEligibleForBackupWithTransport(
+                            transportClient, packageName, mPackageManager)) {
+                eligibleApps.add(packageName);
+            }
+        }
+        if (transportClient != null) {
+            mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
+        }
+        return eligibleApps.toArray(new String[eligibleApps.size()]);
     }
 
     @Override
@@ -3461,10 +3497,10 @@
                     pw.println((t.equals(mTransportManager.getCurrentTransportName()) ? "  * "
                             : "    ") + t);
                     try {
-                        IBackupTransport transport = mTransportManager.getTransportBinder(t);
                         File dir = new File(mBaseStateDir,
                                 mTransportManager.getTransportDirName(t));
-                        pw.println("       destination: " + transport.currentDestinationString());
+                        pw.println("       destination: "
+                                + mTransportManager.getTransportCurrentDestinationString(t));
                         pw.println("       intent: "
                                 + mTransportManager.getTransportConfigurationIntent(t));
                         for (File f : dir.listFiles()) {
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 94a26275..a628c9d 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -452,6 +452,12 @@
     }
 
     @Override
+    public String[] filterAppsEligibleForBackup(String[] packages) {
+        BackupManagerServiceInterface svc = mService;
+        return (svc != null) ? svc.filterAppsEligibleForBackup(packages) : null;
+    }
+
+    @Override
     public int requestBackup(String[] packages, IBackupObserver observer,
             IBackupManagerMonitor monitor, int flags) throws RemoteException {
         BackupManagerServiceInterface svc = mService;
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index fbdb183..34b8935 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -49,6 +49,7 @@
 import com.android.server.backup.transport.TransportClient;
 import com.android.server.backup.transport.TransportClientManager;
 import com.android.server.backup.transport.TransportConnectionListener;
+import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 
 import java.util.ArrayList;
@@ -111,23 +112,6 @@
     @GuardedBy("mTransportLock")
     private volatile String mCurrentTransportName;
 
-
-    /**
-     * Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}.
-     */
-    public interface TransportReadyCallback {
-
-        /**
-         * Will be called when the transport is ready.
-         */
-        void onSuccess(String transportName);
-
-        /**
-         * Will be called when it's not possible to make transport ready.
-         */
-        void onFailure(int reason);
-    }
-
     TransportManager(
             Context context,
             Set<ComponentName> whitelist,
@@ -239,6 +223,17 @@
     }
 
     /**
+     * Returns the transport name associated with {@code transportComponent}.
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportName(ComponentName transportComponent)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent).name;
+        }
+    }
+
+    /**
      * Retrieve the configuration intent of {@code transportName}.
      * @throws TransportNotRegisteredException if the transport is not registered.
      */
@@ -265,6 +260,18 @@
     }
 
     /**
+     * Retrieve the current destination string of {@code transportName}.
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportCurrentDestinationString(String transportName)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
+                    .currentDestinationString;
+        }
+    }
+
+    /**
      * Retrieve the data management label of {@code transportName}.
      * @throws TransportNotRegisteredException if the transport is not registered.
      */
@@ -290,6 +297,18 @@
     }
 
     /**
+     * Retrieve the transport dir name of {@code transportComponent}.
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportDirName(ComponentName transportComponent)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent)
+                    .transportDirName;
+        }
+    }
+
+    /**
      * Execute {@code transportConsumer} for each registered transport passing the transport name.
      * This is called with an internal lock held, ensuring that the transport will remain registered
      * while {@code transportConsumer} is being executed. Don't do heavy operations in
@@ -315,23 +334,6 @@
         return null;
     }
 
-    /**
-     * Returns the transport name associated with {@param transportComponent} or {@code null} if not
-     * found.
-     */
-    @Nullable
-    public String getTransportName(ComponentName transportComponent) {
-        synchronized (mTransportLock) {
-            TransportDescription description =
-                    mRegisteredTransportsDescriptionMap.get(transportComponent);
-            if (description == null) {
-                Slog.e(TAG, "Trying to find name of unregistered transport " + transportComponent);
-                return null;
-            }
-            return description.name;
-        }
-    }
-
     @GuardedBy("mTransportLock")
     @Nullable
     private ComponentName getRegisteredTransportComponentLocked(String transportName) {
@@ -358,7 +360,6 @@
         return description;
     }
 
-
     @GuardedBy("mTransportLock")
     @Nullable
     private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked(
@@ -373,17 +374,35 @@
         return null;
     }
 
+    @GuardedBy("mTransportLock")
+    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
+            ComponentName transportComponent) throws TransportNotRegisteredException {
+        TransportDescription description =
+                mRegisteredTransportsDescriptionMap.get(transportComponent);
+        if (description == null) {
+            throw new TransportNotRegisteredException(transportComponent);
+        }
+        return description;
+    }
+
     @Nullable
     public TransportClient getTransportClient(String transportName, String caller) {
+        try {
+            return getTransportClientOrThrow(transportName, caller);
+        } catch (TransportNotRegisteredException e) {
+            Slog.w(TAG, "Transport " + transportName + " not registered");
+            return null;
+        }
+    }
+
+    public TransportClient getTransportClientOrThrow(String transportName, String caller)
+            throws TransportNotRegisteredException {
         synchronized (mTransportLock) {
             ComponentName component = getRegisteredTransportComponentLocked(transportName);
             if (component == null) {
-                Slog.w(TAG, "Transport " + transportName + " not registered");
-                return null;
+                throw new TransportNotRegisteredException(transportName);
             }
-            TransportDescription description = mRegisteredTransportsDescriptionMap.get(component);
-            return mTransportClientManager.getTransportClient(
-                    component, description.transportDirName, caller);
+            return mTransportClientManager.getTransportClient(component, caller);
         }
     }
 
@@ -403,7 +422,25 @@
      */
     @Nullable
     public TransportClient getCurrentTransportClient(String caller) {
-        return getTransportClient(mCurrentTransportName, caller);
+        synchronized (mTransportLock) {
+            return getTransportClient(mCurrentTransportName, caller);
+        }
+    }
+
+    /**
+     * Returns a {@link TransportClient} for the current transport or throws if not registered.
+     *
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient}.
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public TransportClient getCurrentTransportClientOrThrow(String caller)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getTransportClientOrThrow(mCurrentTransportName, caller);
+        }
     }
 
     /**
@@ -492,29 +529,6 @@
         return mCurrentTransportName;
     }
 
-    String selectTransport(String transport) {
-        synchronized (mTransportLock) {
-            String prevTransport = mCurrentTransportName;
-            mCurrentTransportName = transport;
-            return prevTransport;
-        }
-    }
-
-    void ensureTransportReady(ComponentName transportComponent,
-            TransportReadyCallback listener) {
-        synchronized (mTransportLock) {
-            TransportConnection conn = mValidTransports.get(transportComponent);
-            if (conn == null) {
-                listener.onFailure(BackupManager.ERROR_TRANSPORT_UNAVAILABLE);
-                return;
-            }
-            // Transport can be unbound if the process hosting it crashed.
-            conn.bindIfUnbound();
-            conn.addListener(listener);
-        }
-    }
-
-
     // This is for mocking, Mockito can't mock if package-protected and in the same package but
     // different class loaders. Checked with the debugger and class loaders are different
     // See https://github.com/mockito/mockito/issues/796
@@ -614,6 +628,90 @@
                 createSystemUserHandle());
     }
 
+    String selectTransport(String transportName) {
+        synchronized (mTransportLock) {
+            String prevTransport = mCurrentTransportName;
+            mCurrentTransportName = transportName;
+            return prevTransport;
+        }
+    }
+
+    /**
+     * Tries to register the transport if not registered. If successful also selects the transport.
+     *
+     * @param transportComponent Host of the transport.
+     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
+     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
+     */
+    public int registerAndSelectTransport(ComponentName transportComponent) {
+        synchronized (mTransportLock) {
+            if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) {
+                int result = registerTransport(transportComponent);
+                if (result != BackupManager.SUCCESS) {
+                    return result;
+                }
+            }
+
+            try {
+                selectTransport(getTransportName(transportComponent));
+                return BackupManager.SUCCESS;
+            } catch (TransportNotRegisteredException e) {
+                // Shouldn't happen because we are holding the lock
+                Slog.wtf(TAG, "Transport unexpectedly not registered");
+                return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
+            }
+        }
+    }
+
+    /**
+     * Tries to register transport represented by {@code transportComponent}.
+     *
+     * @param transportComponent Host of the transport that we want to register.
+     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
+     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
+     */
+    private int registerTransport(ComponentName transportComponent) {
+        String transportString = transportComponent.flattenToShortString();
+
+        String callerLogString = "TransportManager.registerTransport()";
+        TransportClient transportClient =
+                mTransportClientManager.getTransportClient(transportComponent, callerLogString);
+
+        final IBackupTransport transport;
+        try {
+            transport = transportClient.connectOrThrow(callerLogString);
+        } catch (TransportNotAvailableException e) {
+            Slog.e(TAG, "Couldn't connect to transport " + transportString + " for registration");
+            mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
+            return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
+        }
+
+        EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 1);
+
+        int result;
+        if (isTransportValid(transport)) {
+            try {
+                registerTransport(transportComponent, transport);
+                // If registerTransport() hasn't thrown...
+                result = BackupManager.SUCCESS;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Transport " + transportString + " died while registering");
+                result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
+            }
+        } else {
+            Slog.w(TAG, "Can't register invalid transport " + transportString);
+            result = BackupManager.ERROR_TRANSPORT_INVALID;
+        }
+
+        mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
+        if (result == BackupManager.SUCCESS) {
+            Slog.d(TAG, "Transport " + transportString + " registered");
+        } else {
+            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0);
+        }
+        return result;
+    }
+
     /** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
     private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
             throws RemoteException {
@@ -630,11 +728,18 @@
         }
     }
 
+    private boolean isTransportValid(IBackupTransport transport) {
+        if (mTransportBoundListener == null) {
+            Slog.w(TAG, "setTransportBoundListener() not called, assuming transport invalid");
+            return false;
+        }
+        return mTransportBoundListener.onTransportBound(transport);
+    }
+
     private class TransportConnection implements ServiceConnection {
 
         // Hold mTransportLock to access these fields so as to provide a consistent view of them.
         private volatile IBackupTransport mBinder;
-        private final List<TransportReadyCallback> mListeners = new ArrayList<>();
         private volatile String mTransportName;
 
         private final ComponentName mTransportComponent;
@@ -656,15 +761,7 @@
                     mTransportName = mBinder.name();
                     // BackupManager requests some fields from the transport. If they are
                     // invalid, throw away this transport.
-                    final boolean valid;
-                    if (mTransportBoundListener != null) {
-                        valid = mTransportBoundListener.onTransportBound(mBinder);
-                    } else {
-                        Slog.w(TAG, "setTransportBoundListener() not called, assuming transport "
-                                + component + " valid");
-                        valid = true;
-                    }
-                    if (valid) {
+                    if (isTransportValid(mBinder)) {
                         // We're now using the always-bound connection to do the registration but
                         // when we remove the always-bound code this will be in the first binding
                         // TODO: Move registration to first binding
@@ -682,9 +779,6 @@
                     if (success) {
                         Slog.d(TAG, "Bound to transport: " + componentShortString);
                         mBoundTransports.put(mTransportName, component);
-                        for (TransportReadyCallback listener : mListeners) {
-                            listener.onSuccess(mTransportName);
-                        }
                         // cancel rebinding on timeout for this component as we've already connected
                         mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
                     } else {
@@ -696,11 +790,7 @@
                         mValidTransports.remove(component);
                         mEligibleTransports.remove(component);
                         mBinder = null;
-                        for (TransportReadyCallback listener : mListeners) {
-                            listener.onFailure(BackupManager.ERROR_TRANSPORT_INVALID);
-                        }
                     }
-                    mListeners.clear();
                 }
             }
         }
@@ -755,19 +845,6 @@
             }
         }
 
-        private void addListener(TransportReadyCallback listener) {
-            synchronized (mTransportLock) {
-                if (mBinder == null) {
-                    // We are waiting for bind to complete. If mBinder is set to null after the bind
-                    // is complete due to transport being invalid, we won't find 'this' connection
-                    // object in mValidTransports list and this function can't be called.
-                    mListeners.add(listener);
-                } else {
-                    listener.onSuccess(mTransportName);
-                }
-            }
-        }
-
         private long getRebindTimeout() {
             final boolean isDeviceProvisioned = Settings.Global.getInt(
                     mContext.getContentResolver(),
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 a002334..c232241 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -560,16 +560,9 @@
                 }
                 backupManagerService.addBackupTrace("init required; rerunning");
                 try {
-                    final String name = backupManagerService.getTransportManager()
+                    String name = backupManagerService.getTransportManager()
                             .getTransportName(mTransportClient.getTransportComponent());
-                    if (name != null) {
-                        backupManagerService.getPendingInits().add(name);
-                    } else {
-                        if (DEBUG) {
-                            Slog.w(TAG, "Couldn't find name of transport "
-                                    + mTransportClient.getTransportComponent() + " for init");
-                        }
-                    }
+                    backupManagerService.getPendingInits().add(name);
                 } catch (Exception e) {
                     Slog.w(TAG, "Failed to query transport name for init: " + e.getMessage());
                     // swallow it and proceed; we don't rely on this
diff --git a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
index 84ca59b..140d7286 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
@@ -23,12 +23,14 @@
 
 import com.android.internal.backup.IBackupTransport;
 import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.TransportManager;
 import com.android.server.backup.transport.TransportClient;
 
 import java.io.File;
 
 public class PerformClearTask implements Runnable {
     private final RefactoredBackupManagerService mBackupManagerService;
+    private final TransportManager mTransportManager;
     private final TransportClient mTransportClient;
     private final PackageInfo mPackage;
     private final OnTaskFinishedListener mListener;
@@ -37,6 +39,7 @@
             TransportClient transportClient, PackageInfo packageInfo,
             OnTaskFinishedListener listener) {
         mBackupManagerService = backupManagerService;
+        mTransportManager = backupManagerService.getTransportManager();
         mTransportClient = transportClient;
         mPackage = packageInfo;
         mListener = listener;
@@ -47,8 +50,9 @@
         IBackupTransport transport = null;
         try {
             // Clear the on-device backup state to ensure a full backup next time
-            File stateDir = new File(mBackupManagerService.getBaseStateDir(),
-                    mTransportClient.getTransportDirName());
+            String transportDirName =
+                    mTransportManager.getTransportDirName(mTransportClient.getTransportComponent());
+            File stateDir = new File(mBackupManagerService.getBaseStateDir(), transportDirName);
             File stateFile = new File(stateDir, mPackage.packageName);
             stateFile.delete();
 
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 b21b072..2f2af98 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.backup.RefactoredBackupManagerService.TAG;
 
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.backup.BackupTransport;
 import android.app.backup.IBackupObserver;
@@ -26,23 +27,63 @@
 import android.util.EventLog;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
 import com.android.server.EventLogTags;
 import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportClient;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 
+/**
+ * Attempts to call {@link BackupTransport#initializeDevice()} followed by
+ * {@link BackupTransport#finishBackup()} for the transport names passed in with the intent of
+ * wiping backup data from the transport.
+ *
+ * If the transport returns error, it will record the operation as pending and schedule it to run in
+ * a future time according to {@link BackupTransport#requestBackupTime()}. The result status
+ * reported to observers will be the last unsuccessful status reported by the transports. If every
+ * operation was successful then it's {@link BackupTransport#TRANSPORT_OK}.
+ */
 public class PerformInitializeTask implements Runnable {
+    private final RefactoredBackupManagerService mBackupManagerService;
+    private final TransportManager mTransportManager;
+    private final String[] mQueue;
+    private final File mBaseStateDir;
+    private final OnTaskFinishedListener mListener;
+    @Nullable private IBackupObserver mObserver;
 
-    private RefactoredBackupManagerService backupManagerService;
-    String[] mQueue;
-    IBackupObserver mObserver;
+    public PerformInitializeTask(
+            RefactoredBackupManagerService backupManagerService,
+            String[] transportNames,
+            @Nullable IBackupObserver observer,
+            OnTaskFinishedListener listener) {
+        this(
+                backupManagerService,
+                backupManagerService.getTransportManager(),
+                transportNames,
+                observer,
+                listener,
+                backupManagerService.getBaseStateDir());
+    }
 
-    public PerformInitializeTask(RefactoredBackupManagerService backupManagerService,
-            String[] transportNames, IBackupObserver observer) {
-        this.backupManagerService = backupManagerService;
+    @VisibleForTesting
+    PerformInitializeTask(
+            RefactoredBackupManagerService backupManagerService,
+            TransportManager transportManager,
+            String[] transportNames,
+            @Nullable IBackupObserver observer,
+            OnTaskFinishedListener listener,
+            File baseStateDir) {
+        mBackupManagerService = backupManagerService;
+        mTransportManager = transportManager;
         mQueue = transportNames;
         mObserver = observer;
+        mListener = listener;
+        mBaseStateDir = baseStateDir;
     }
 
     private void notifyResult(String target, int status) {
@@ -67,21 +108,27 @@
 
     public void run() {
         // mWakelock is *acquired* when execution begins here
+        String callerLogString = "PerformInitializeTask.run()";
+        List<TransportClient> transportClientsToDisposeOf = new ArrayList<>(mQueue.length);
         int result = BackupTransport.TRANSPORT_OK;
         try {
             for (String transportName : mQueue) {
-                IBackupTransport transport =
-                        backupManagerService.getTransportManager().getTransportBinder(
-                                transportName);
-                if (transport == null) {
+                TransportClient transportClient =
+                        mTransportManager.getTransportClient(transportName, callerLogString);
+                if (transportClient == null) {
                     Slog.e(TAG, "Requested init for " + transportName + " but not found");
                     continue;
                 }
+                transportClientsToDisposeOf.add(transportClient);
 
                 Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName);
-                String transportDirName = transport.transportDirName();
+                String transportDirName =
+                        mTransportManager.getTransportDirName(
+                                transportClient.getTransportComponent());
                 EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
                 long startRealtime = SystemClock.elapsedRealtime();
+
+                IBackupTransport transport = transportClient.connectOrThrow(callerLogString);
                 int status = transport.initializeDevice();
 
                 if (status == BackupTransport.TRANSPORT_OK) {
@@ -93,42 +140,38 @@
                     Slog.i(TAG, "Device init successful");
                     int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
                     EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
-                    backupManagerService
-                            .resetBackupState(new File(backupManagerService.getBaseStateDir(),
-                                    transportDirName));
+                    File stateFileDir = new File(mBaseStateDir, transportDirName);
+                    mBackupManagerService.resetBackupState(stateFileDir);
                     EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis);
-                    synchronized (backupManagerService.getQueueLock()) {
-                        backupManagerService.recordInitPendingLocked(
-                                false, transportName, transportDirName);
-                    }
+                    mBackupManagerService.recordInitPending(false, transportName, transportDirName);
                     notifyResult(transportName, BackupTransport.TRANSPORT_OK);
                 } else {
                     // If this didn't work, requeue this one and try again
                     // after a suitable interval
                     Slog.e(TAG, "Transport error in initializeDevice()");
                     EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
-                    synchronized (backupManagerService.getQueueLock()) {
-                        backupManagerService.recordInitPendingLocked(
-                                true, transportName, transportDirName);
-                    }
+                    mBackupManagerService.recordInitPending(true, transportName, transportDirName);
                     notifyResult(transportName, status);
                     result = status;
 
                     // do this via another alarm to make sure of the wakelock states
                     long delay = transport.requestBackupTime();
                     Slog.w(TAG, "Init failed on " + transportName + " resched in " + delay);
-                    backupManagerService.getAlarmManager().set(AlarmManager.RTC_WAKEUP,
+                    mBackupManagerService.getAlarmManager().set(
+                            AlarmManager.RTC_WAKEUP,
                             System.currentTimeMillis() + delay,
-                            backupManagerService.getRunInitIntent());
+                            mBackupManagerService.getRunInitIntent());
                 }
             }
         } catch (Exception e) {
             Slog.e(TAG, "Unexpected error performing init", e);
             result = BackupTransport.TRANSPORT_ERROR;
         } finally {
-            // Done; release the wakelock
+            for (TransportClient transportClient : transportClientsToDisposeOf) {
+                mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
+            }
             notifyFinished(result);
-            backupManagerService.getWakelock().release();
+            mListener.onFinished(callerLogString);
         }
     }
 }
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 1df0bf0..6c160a3 100644
--- a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
+++ b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
@@ -23,37 +23,41 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.PowerManager;
 import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.server.backup.RefactoredBackupManagerService;
 
 public class RunInitializeReceiver extends BroadcastReceiver {
-
-    private RefactoredBackupManagerService backupManagerService;
+    private final RefactoredBackupManagerService mBackupManagerService;
 
     public RunInitializeReceiver(RefactoredBackupManagerService backupManagerService) {
-        this.backupManagerService = backupManagerService;
+        mBackupManagerService = backupManagerService;
     }
 
     public void onReceive(Context context, Intent intent) {
         if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) {
-            synchronized (backupManagerService.getQueueLock()) {
-                final ArraySet<String> pendingInits = backupManagerService.getPendingInits();
+            synchronized (mBackupManagerService.getQueueLock()) {
+                final ArraySet<String> pendingInits = mBackupManagerService.getPendingInits();
                 if (DEBUG) {
                     Slog.v(TAG, "Running a device init; " + pendingInits.size() + " pending");
                 }
 
                 if (pendingInits.size() > 0) {
-                    final String[] transports = pendingInits.toArray(new String[pendingInits.size()]);
-                    PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService,
-                            transports, null);
+                    final String[] transports =
+                            pendingInits.toArray(new String[pendingInits.size()]);
 
-                    // 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);
+                    mBackupManagerService.clearPendingInits();
+
+                    PowerManager.WakeLock wakelock = mBackupManagerService.getWakelock();
+                    wakelock.acquire();
+                    OnTaskFinishedListener listener = caller -> wakelock.release();
+
+                    Runnable task =
+                            new PerformInitializeTask(
+                                    mBackupManagerService, transports, null, listener);
+                    mBackupManagerService.getBackupHandler().post(task);
                 }
             }
         }
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 7efe5ca..2c8b5b4 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -66,7 +66,6 @@
     // Task in charge of monitoring timeouts
     private final BackupRestoreTask mMonitorTask;
 
-    private final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver();
     private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
 
     // Dedicated observer, if any
@@ -249,13 +248,12 @@
                                     Slog.d(TAG, "APK file; installing");
                                 }
                                 // Try to install the app.
-                                String installerName = mPackageInstallers.get(pkg);
+                                String installerPackageName = mPackageInstallers.get(pkg);
                                 boolean isSuccessfullyInstalled = RestoreUtils.installApk(
-                                        instream, mBackupManagerService.getPackageManager(),
-                                        mInstallObserver, mDeleteObserver, mManifestSignatures,
-                                        mPackagePolicies, info, installerName,
-                                        bytesReadListener, mBackupManagerService.getDataDir()
-                                                                                         );
+                                        instream, mBackupManagerService.getContext(),
+                                        mDeleteObserver, mManifestSignatures,
+                                        mPackagePolicies, info, installerPackageName,
+                                        bytesReadListener);
                                 // good to go; promote to ACCEPT
                                 mPackagePolicies.put(pkg, isSuccessfullyInstalled
                                         ? RestorePolicy.ACCEPT
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index 3dc242f..54c2746 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -88,7 +88,6 @@
     private final String mDecryptPassword;
     private final AtomicBoolean mLatchObject;
     private final PackageManagerBackupAgent mPackageManagerBackupAgent;
-    private final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver();
     private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
 
     private IFullBackupRestoreObserver mObserver;
@@ -513,13 +512,11 @@
                                     Slog.d(TAG, "APK file; installing");
                                 }
                                 // Try to install the app.
-                                String installerName = mPackageInstallers.get(pkg);
-                                boolean isSuccessfullyInstalled = RestoreUtils.installApk(
-                                        instream, mBackupManagerService.getPackageManager(),
-                                        mInstallObserver, mDeleteObserver, mManifestSignatures,
-                                        mPackagePolicies, info, installerName,
-                                        bytesReadListener, mBackupManagerService.getDataDir()
-                                                                                         );
+                                String installerPackageName = mPackageInstallers.get(pkg);
+                                boolean isSuccessfullyInstalled = RestoreUtils.installApk(instream,
+                                        mBackupManagerService.getContext(),
+                                        mDeleteObserver, mManifestSignatures, mPackagePolicies,
+                                        info, installerPackageName, bytesReadListener);
                                 // good to go; promote to ACCEPT
                                 mPackagePolicies.put(pkg, isSuccessfullyInstalled
                                         ? RestorePolicy.ACCEPT
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 86866dc..88f9ead 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -61,6 +61,7 @@
 import com.android.server.backup.PackageManagerBackupAgent;
 import com.android.server.backup.PackageManagerBackupAgent.Metadata;
 import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.TransportManager;
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.transport.TransportClient;
 import com.android.server.backup.utils.AppBackupUtils;
@@ -78,6 +79,7 @@
 public class PerformUnifiedRestoreTask implements BackupRestoreTask {
 
     private RefactoredBackupManagerService backupManagerService;
+    private final TransportManager mTransportManager;
     // Transport client we're working with to do the restore
     private final TransportClient mTransportClient;
 
@@ -164,6 +166,7 @@
             int pmToken, boolean isFullSystemRestore, String[] filterSet,
             OnTaskFinishedListener listener) {
         this.backupManagerService = backupManagerService;
+        mTransportManager = backupManagerService.getTransportManager();
         mEphemeralOpToken = backupManagerService.generateRandomIntegerToken();
         mState = UnifiedRestoreState.INITIAL;
         mStartRealtime = SystemClock.elapsedRealtime();
@@ -349,8 +352,9 @@
         }
 
         try {
-            String transportDir = mTransportClient.getTransportDirName();
-            mStateDir = new File(backupManagerService.getBaseStateDir(), transportDir);
+            String transportDirName =
+                    mTransportManager.getTransportDirName(mTransportClient.getTransportComponent());
+            mStateDir = new File(backupManagerService.getBaseStateDir(), transportDirName);
 
             // Fetch the current metadata from the dataset first
             PackageInfo pmPackage = new PackageInfo();
diff --git a/services/backup/java/com/android/server/backup/restore/RestoreInstallObserver.java b/services/backup/java/com/android/server/backup/restore/RestoreInstallObserver.java
deleted file mode 100644
index 3d506f1..0000000
--- a/services/backup/java/com/android/server/backup/restore/RestoreInstallObserver.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.server.backup.restore;
-
-import android.app.PackageInstallObserver;
-import android.os.Bundle;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Synchronous implementation of PackageInstallObserver.
- *
- * Allows the caller to synchronously wait for package install event.
- */
-public class RestoreInstallObserver extends PackageInstallObserver {
-
-    @GuardedBy("mDone")
-    private final AtomicBoolean mDone = new AtomicBoolean();
-
-    private String mPackageName;
-    private int mResult;
-
-    public RestoreInstallObserver() {
-    }
-
-    /**
-     * Resets the observer to prepare for another installation.
-     */
-    public void reset() {
-        synchronized (mDone) {
-            mDone.set(false);
-        }
-    }
-
-    /**
-     * Synchronously waits for completion.
-     */
-    public void waitForCompletion() {
-        synchronized (mDone) {
-            while (mDone.get() == false) {
-                try {
-                    mDone.wait();
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns result code.
-     */
-    public int getResult() {
-        return mResult;
-    }
-
-    /**
-     * Returns installed package name.
-     */
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    @Override
-    public void onPackageInstalled(String packageName, int returnCode,
-            String msg, Bundle extras) {
-        synchronized (mDone) {
-            mResult = returnCode;
-            mPackageName = packageName;
-            mDone.set(true);
-            mDone.notifyAll();
-        }
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
index 2c7a0eb..7bd9111 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -68,7 +68,6 @@
     private final Intent mBindIntent;
     private final String mIdentifier;
     private final ComponentName mTransportComponent;
-    private final String mTransportDirName;
     private final Handler mListenerHandler;
     private final String mPrefixForLog;
     private final Object mStateLock = new Object();
@@ -87,13 +86,11 @@
             Context context,
             Intent bindIntent,
             ComponentName transportComponent,
-            String transportDirName,
             String identifier) {
         this(
                 context,
                 bindIntent,
                 transportComponent,
-                transportDirName,
                 identifier,
                 new Handler(Looper.getMainLooper()));
     }
@@ -103,12 +100,10 @@
             Context context,
             Intent bindIntent,
             ComponentName transportComponent,
-            String transportDirName,
             String identifier,
             Handler listenerHandler) {
         mContext = context;
         mTransportComponent = transportComponent;
-        mTransportDirName = transportDirName;
         mBindIntent = bindIntent;
         mIdentifier = identifier;
         mListenerHandler = listenerHandler;
@@ -122,10 +117,6 @@
         return mTransportComponent;
     }
 
-    public String getTransportDirName() {
-        return mTransportDirName;
-    }
-
     // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one
     // of these calls, if a binding happen again the new service can be a different instance. Since
     // transports are stateful, we don't want a new instance responding for an old instance's state.
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
index bb550f6..1cbe7471 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
@@ -23,7 +23,6 @@
 import android.content.Intent;
 import android.util.Log;
 
-import com.android.internal.backup.IBackupTransport;
 import com.android.server.backup.TransportManager;
 
 /**
@@ -48,17 +47,12 @@
      * transportComponent}.
      *
      * @param transportComponent The {@link ComponentName} of the transport.
-     * @param transportDirName The {@link String} returned by
-     *     {@link IBackupTransport#transportDirName()} at registration.
      * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
      *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
      *     details.
      * @return A {@link TransportClient}.
      */
-    public TransportClient getTransportClient(
-            ComponentName transportComponent,
-            String transportDirName,
-            String caller) {
+    public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
         Intent bindIntent =
                 new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
         synchronized (mTransportClientsLock) {
@@ -67,7 +61,6 @@
                             mContext,
                             bindIntent,
                             transportComponent,
-                            transportDirName,
                             Integer.toString(mTransportClientsCreated));
             mTransportClientsCreated++;
             TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient);
diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java b/services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java
index 26bf92c..02766de 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java
@@ -16,6 +16,7 @@
 
 package com.android.server.backup.transport;
 
+import android.content.ComponentName;
 import android.util.AndroidException;
 
 import com.android.server.backup.TransportManager;
@@ -32,4 +33,8 @@
     public TransportNotRegisteredException(String transportName) {
         super("Transport " + transportName + " not registered");
     }
+
+    public TransportNotRegisteredException(ComponentName transportComponent) {
+        super("Transport for host " + transportComponent + " not registered");
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
index d7cac77..dbf1a826 100644
--- a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
@@ -20,6 +20,8 @@
 import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 import static com.android.server.backup.RefactoredBackupManagerService.TAG;
 
+import android.annotation.Nullable;
+import android.app.backup.BackupTransport;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -27,7 +29,9 @@
 import android.os.Process;
 import android.util.Slog;
 
+import com.android.internal.backup.IBackupTransport;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.backup.transport.TransportClient;
 
 /**
  * Utility methods wrapping operations on ApplicationInfo and PackageInfo.
@@ -71,6 +75,44 @@
         return !appIsDisabled(app, pm);
     }
 
+    /**
+     * Returns whether an app is eligible for backup at runtime. That is, the app has to:
+     * <ol>
+     *     <li>Return true for {@link #appIsEligibleForBackup(ApplicationInfo, PackageManager)}
+     *     <li>Return false for {@link #appIsStopped(ApplicationInfo)}
+     *     <li>Return false for {@link #appIsDisabled(ApplicationInfo, PackageManager)}
+     *     <li>Be eligible for the transport via
+     *         {@link BackupTransport#isAppEligibleForBackup(PackageInfo, boolean)}
+     * </ol>
+     */
+    public static boolean appIsRunningAndEligibleForBackupWithTransport(
+            @Nullable TransportClient transportClient, String packageName, PackageManager pm) {
+        try {
+            PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+            ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+            if (!appIsEligibleForBackup(applicationInfo, pm)
+                    || appIsStopped(applicationInfo)
+                    || appIsDisabled(applicationInfo, pm)) {
+                return false;
+            }
+            if (transportClient != null) {
+                try {
+                    IBackupTransport transport =
+                            transportClient.connectOrThrow(
+                                    "AppBackupUtils.appIsEligibleForBackupAtRuntime");
+                    return transport.isAppEligibleForBackup(
+                            packageInfo, AppBackupUtils.appGetsFullBackup(packageInfo));
+                } catch (Exception e) {
+                    Slog.e(TAG, "Unable to ask about eligibility: " + e.getMessage());
+                }
+            }
+            // If transport is not present we couldn't tell that the package is not eligible.
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
     /** Avoid backups of 'disabled' apps. */
     public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
         switch (pm.getApplicationEnabledSetting(app.packageName)) {
diff --git a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
index ee4201e..632f5b5 100644
--- a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
@@ -19,23 +19,31 @@
 import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
 import static com.android.server.backup.RefactoredBackupManagerService.TAG;
 
+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.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.Session;
+import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
-import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Process;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.backup.FileMetadata;
 import com.android.server.backup.restore.RestoreDeleteObserver;
-import com.android.server.backup.restore.RestoreInstallObserver;
 import com.android.server.backup.restore.RestorePolicy;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.HashMap;
 
 /**
@@ -47,62 +55,73 @@
      * Reads apk contents from input stream and installs the apk.
      *
      * @param instream - input stream to read apk data from.
-     * @param packageManager - {@link PackageManager} instance.
-     * @param installObserver - {@link RestoreInstallObserver} instance.
+     * @param context - installing context
      * @param deleteObserver - {@link RestoreDeleteObserver} instance.
      * @param manifestSignatures - manifest signatures.
      * @param packagePolicies - package policies.
      * @param info - backup file info.
-     * @param installerPackage - installer package.
+     * @param installerPackageName - package name of installer.
      * @param bytesReadListener - listener to be called for counting bytes read.
-     * @param dataDir - directory where to create apk file.
      * @return true if apk was successfully read and installed and false otherwise.
      */
     // TODO: Refactor to get rid of unneeded params.
-    public static boolean installApk(InputStream instream, PackageManager packageManager,
-            RestoreInstallObserver installObserver, RestoreDeleteObserver deleteObserver,
+    public static boolean installApk(InputStream instream, Context context,
+            RestoreDeleteObserver deleteObserver,
             HashMap<String, Signature[]> manifestSignatures,
             HashMap<String, RestorePolicy> packagePolicies,
             FileMetadata info,
-            String installerPackage, BytesReadListener bytesReadListener,
-            File dataDir) {
+            String installerPackageName, BytesReadListener bytesReadListener) {
         boolean okay = true;
 
         if (DEBUG) {
             Slog.d(TAG, "Installing from backup: " + info.packageName);
         }
 
-        // The file content is an .apk file.  Copy it out to a staging location and
-        // attempt to install it.
-        File apkFile = new File(dataDir, info.packageName);
         try {
-            FileOutputStream apkStream = new FileOutputStream(apkFile);
-            byte[] buffer = new byte[32 * 1024];
-            long size = info.size;
-            while (size > 0) {
-                long toRead = (buffer.length < size) ? buffer.length : size;
-                int didRead = instream.read(buffer, 0, (int) toRead);
-                if (didRead >= 0) {
-                    bytesReadListener.onBytesRead(didRead);
+            LocalIntentReceiver receiver = new LocalIntentReceiver();
+            PackageManager packageManager = context.getPackageManager();
+            PackageInstaller installer = packageManager.getPackageInstaller();
+
+            SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+            params.setInstallerPackageName(installerPackageName);
+            int sessionId = installer.createSession(params);
+            try {
+                try (Session session = installer.openSession(sessionId)) {
+                    try (OutputStream apkStream = session.openWrite(info.packageName, 0,
+                            info.size)) {
+                        byte[] buffer = new byte[32 * 1024];
+                        long size = info.size;
+                        while (size > 0) {
+                            long toRead = (buffer.length < size) ? buffer.length : size;
+                            int didRead = instream.read(buffer, 0, (int) toRead);
+                            if (didRead >= 0) {
+                                bytesReadListener.onBytesRead(didRead);
+                            }
+                            apkStream.write(buffer, 0, didRead);
+                            size -= didRead;
+                        }
+                    }
+
+                    // Installation is current disabled
+                    session.abandon();
+                    // session.commit(receiver.getIntentSender());
                 }
-                apkStream.write(buffer, 0, didRead);
-                size -= didRead;
+            } catch (Exception t) {
+                installer.abandonSession(sessionId);
+
+                throw t;
             }
-            apkStream.close();
 
-            // make sure the installer can read it
-            apkFile.setReadable(true, false);
+            // Installation is current disabled
+            Intent result = null;
+            // Intent result = receiver.getResult();
 
-            // Now install it
-            Uri packageUri = Uri.fromFile(apkFile);
-            installObserver.reset();
-            // TODO: PackageManager.installPackage() is deprecated, refactor.
-            packageManager.installPackage(packageUri, installObserver,
-                    PackageManager.INSTALL_REPLACE_EXISTING | PackageManager.INSTALL_FROM_ADB,
-                    installerPackage);
-            installObserver.waitForCompletion();
+            // Installation is current disabled
+            int status = PackageInstaller.STATUS_FAILURE;
+            // int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+            //        PackageInstaller.STATUS_FAILURE);
 
-            if (installObserver.getResult() != PackageManager.INSTALL_SUCCEEDED) {
+            if (status != PackageInstaller.STATUS_SUCCESS) {
                 // The only time we continue to accept install of data even if the
                 // apk install failed is if we had already determined that we could
                 // accept the data regardless.
@@ -112,10 +131,12 @@
             } else {
                 // Okay, the install succeeded.  Make sure it was the right app.
                 boolean uninstall = false;
-                if (!installObserver.getPackageName().equals(info.packageName)) {
+                final String installedPackageName = result.getStringExtra(
+                        PackageInstaller.EXTRA_PACKAGE_NAME);
+                if (!installedPackageName.equals(info.packageName)) {
                     Slog.w(TAG, "Restore stream claimed to include apk for "
                             + info.packageName + " but apk was really "
-                            + installObserver.getPackageName());
+                            + installedPackageName);
                     // delete the package we just put in place; it might be fraudulent
                     okay = false;
                     uninstall = true;
@@ -161,7 +182,7 @@
                 if (uninstall) {
                     deleteObserver.reset();
                     packageManager.deletePackage(
-                            installObserver.getPackageName(),
+                            installedPackageName,
                             deleteObserver, 0);
                     deleteObserver.waitForCompletion();
                 }
@@ -169,10 +190,44 @@
         } catch (IOException e) {
             Slog.e(TAG, "Unable to transcribe restored apk for install");
             okay = false;
-        } finally {
-            apkFile.delete();
         }
 
         return okay;
     }
+
+    private static class LocalIntentReceiver {
+        private final Object mLock = new Object();
+
+        @GuardedBy("mLock")
+        private Intent mResult = null;
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                synchronized (mLock) {
+                    mResult = intent;
+                    mLock.notifyAll();
+                }
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+
+        public Intent getResult() {
+            synchronized (mLock) {
+                while (mResult == null) {
+                    try {
+                        mLock.wait();
+                    } catch (InterruptedException e) {
+                        // ignored
+                    }
+                }
+
+                return mResult;
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 06cf982..a024d5a 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -149,6 +149,9 @@
     private long mNextNonWakeup;
     private long mLastWakeupSet;
     private long mLastWakeup;
+    private long mLastTickSet;
+    private long mLastTickIssued; // elapsed
+    private long mLastTickReceived;
     int mBroadcastRefCount = 0;
     PowerManager.WakeLock mWakeLock;
     boolean mLastWakeLockUnimportantForLogging;
@@ -1596,6 +1599,7 @@
             pw.println();
 
             mForceAppStandbyTracker.dump(pw, "  ");
+            pw.println();
 
             final long nowRTC = System.currentTimeMillis();
             final long nowELAPSED = SystemClock.elapsedRealtime();
@@ -1607,8 +1611,12 @@
             pw.println();
             pw.print("  mLastTimeChangeClockTime="); pw.print(mLastTimeChangeClockTime);
             pw.print("="); pw.println(sdf.format(new Date(mLastTimeChangeClockTime)));
-            pw.print("  mLastTimeChangeRealtime=");
-            TimeUtils.formatDuration(mLastTimeChangeRealtime, pw);
+            pw.print("  mLastTimeChangeRealtime="); pw.println(mLastTimeChangeRealtime);
+            pw.print("  mLastTickIssued=");
+            TimeUtils.formatDuration(mLastTickIssued - nowELAPSED, pw);
+            pw.println();
+            pw.print("  mLastTickReceived="); pw.println(sdf.format(new Date(mLastTickReceived)));
+            pw.print("  mLastTickSet="); pw.println(sdf.format(new Date(mLastTickSet)));
             pw.println();
             if (!mInteractive) {
                 pw.print("  Time since non-interactive: ");
@@ -3035,15 +3043,8 @@
                     Slog.v(TAG, "sending alarm " + alarm);
                 }
                 if (RECORD_ALARMS_IN_HISTORY) {
-                    if (alarm.workSource != null && alarm.workSource.size() > 0) {
-                        for (int wi=0; wi<alarm.workSource.size(); wi++) {
-                            ActivityManager.noteAlarmStart(
-                                    alarm.operation, alarm.workSource.get(wi), alarm.statsTag);
-                        }
-                    } else {
-                        ActivityManager.noteAlarmStart(
-                                alarm.operation, alarm.uid, alarm.statsTag);
-                    }
+                    ActivityManager.noteAlarmStart(alarm.operation, alarm.workSource, alarm.uid,
+                            alarm.statsTag);
                 }
                 mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
             } catch (RuntimeException e) {
@@ -3291,6 +3292,9 @@
                 if (DEBUG_BATCH) {
                     Slog.v(TAG, "Received TIME_TICK alarm; rescheduling");
                 }
+                synchronized (mLock) {
+                    mLastTickReceived = System.currentTimeMillis();
+                }
                 scheduleTimeTickEvent();
             } else if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) {
                 // Since the kernel does not keep track of DST, we need to
@@ -3316,6 +3320,11 @@
             setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
                     0, mTimeTickSender, null, null, AlarmManager.FLAG_STANDALONE, workSource,
                     null, Process.myUid(), "android");
+
+            // Finally, remember when we set the tick alarm
+            synchronized (mLock) {
+                mLastTickSet = currentTime;
+            }
         }
 
         public void scheduleDateChangedEvent() {
@@ -3553,15 +3562,8 @@
                 fs.aggregateTime += nowELAPSED - fs.startTime;
             }
             if (RECORD_ALARMS_IN_HISTORY) {
-                if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
-                    for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
-                        ActivityManager.noteAlarmFinish(
-                                inflight.mPendingIntent, inflight.mWorkSource.get(wi), inflight.mTag);
-                    }
-                } else {
-                    ActivityManager.noteAlarmFinish(
-                            inflight.mPendingIntent, inflight.mUid, inflight.mTag);
-                }
+                ActivityManager.noteAlarmFinish(inflight.mPendingIntent, inflight.mWorkSource,
+                        inflight.mUid, inflight.mTag);
             }
         }
 
@@ -3674,6 +3676,11 @@
             if (alarm.operation != null) {
                 // PendingIntent alarm
                 mSendCount++;
+
+                if (alarm.priorityClass.priority == PRIO_TICK) {
+                    mLastTickIssued = nowELAPSED;
+                }
+
                 try {
                     alarm.operation.send(getContext(), 0,
                             mBackgroundIntent.putExtra(
@@ -3771,18 +3778,9 @@
                     || alarm.type == RTC_WAKEUP) {
                 bs.numWakeup++;
                 fs.numWakeup++;
-                if (alarm.workSource != null && alarm.workSource.size() > 0) {
-                    for (int wi=0; wi<alarm.workSource.size(); wi++) {
-                        final String wsName = alarm.workSource.getName(wi);
-                        ActivityManager.noteWakeupAlarm(
-                                alarm.operation, alarm.workSource.get(wi),
-                                (wsName != null) ? wsName : alarm.packageName,
-                                alarm.statsTag);
-                    }
-                } else {
-                    ActivityManager.noteWakeupAlarm(
-                            alarm.operation, alarm.uid, alarm.packageName, alarm.statsTag);
-                }
+                ActivityManager.noteWakeupAlarm(
+                        alarm.operation, alarm.workSource, alarm.uid, alarm.packageName,
+                        alarm.statsTag);
             }
         }
     }
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 6f697c4..985f16d 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -795,64 +795,65 @@
                     Slog.e(TAG, "Bad device idle settings", e);
                 }
 
-                LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(
+                LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getDurationMillis(
                         KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L);
-                LIGHT_PRE_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_PRE_IDLE_TIMEOUT,
+                LIGHT_PRE_IDLE_TIMEOUT = mParser.getDurationMillis(KEY_LIGHT_PRE_IDLE_TIMEOUT,
                         !COMPRESS_TIME ? 10 * 60 * 1000L : 30 * 1000L);
-                LIGHT_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_TIMEOUT,
+                LIGHT_IDLE_TIMEOUT = mParser.getDurationMillis(KEY_LIGHT_IDLE_TIMEOUT,
                         !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L);
                 LIGHT_IDLE_FACTOR = mParser.getFloat(KEY_LIGHT_IDLE_FACTOR,
                         2f);
-                LIGHT_MAX_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_MAX_IDLE_TIMEOUT,
+                LIGHT_MAX_IDLE_TIMEOUT = mParser.getDurationMillis(KEY_LIGHT_MAX_IDLE_TIMEOUT,
                         !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L);
-                LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mParser.getLong(
+                LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mParser.getDurationMillis(
                         KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,
                         !COMPRESS_TIME ? 1 * 60 * 1000L : 15 * 1000L);
-                LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mParser.getLong(
+                LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mParser.getDurationMillis(
                         KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET,
                         !COMPRESS_TIME ? 5 * 60 * 1000L : 30 * 1000L);
-                MIN_LIGHT_MAINTENANCE_TIME = mParser.getLong(
+                MIN_LIGHT_MAINTENANCE_TIME = mParser.getDurationMillis(
                         KEY_MIN_LIGHT_MAINTENANCE_TIME,
                         !COMPRESS_TIME ? 5 * 1000L : 1 * 1000L);
-                MIN_DEEP_MAINTENANCE_TIME = mParser.getLong(
+                MIN_DEEP_MAINTENANCE_TIME = mParser.getDurationMillis(
                         KEY_MIN_DEEP_MAINTENANCE_TIME,
                         !COMPRESS_TIME ? 30 * 1000L : 5 * 1000L);
                 long inactiveTimeoutDefault = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
-                INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
+                INACTIVE_TIMEOUT = mParser.getDurationMillis(KEY_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? inactiveTimeoutDefault : (inactiveTimeoutDefault / 10));
-                SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
+                SENSING_TIMEOUT = mParser.getDurationMillis(KEY_SENSING_TIMEOUT,
                         !DEBUG ? 4 * 60 * 1000L : 60 * 1000L);
-                LOCATING_TIMEOUT = mParser.getLong(KEY_LOCATING_TIMEOUT,
+                LOCATING_TIMEOUT = mParser.getDurationMillis(KEY_LOCATING_TIMEOUT,
                         !DEBUG ? 30 * 1000L : 15 * 1000L);
                 LOCATION_ACCURACY = mParser.getFloat(KEY_LOCATION_ACCURACY, 20);
-                MOTION_INACTIVE_TIMEOUT = mParser.getLong(KEY_MOTION_INACTIVE_TIMEOUT,
+                MOTION_INACTIVE_TIMEOUT = mParser.getDurationMillis(KEY_MOTION_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? 10 * 60 * 1000L : 60 * 1000L);
                 long idleAfterInactiveTimeout = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
-                IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
+                IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getDurationMillis(
+                        KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? idleAfterInactiveTimeout
                                        : (idleAfterInactiveTimeout / 10));
-                IDLE_PENDING_TIMEOUT = mParser.getLong(KEY_IDLE_PENDING_TIMEOUT,
+                IDLE_PENDING_TIMEOUT = mParser.getDurationMillis(KEY_IDLE_PENDING_TIMEOUT,
                         !COMPRESS_TIME ? 5 * 60 * 1000L : 30 * 1000L);
-                MAX_IDLE_PENDING_TIMEOUT = mParser.getLong(KEY_MAX_IDLE_PENDING_TIMEOUT,
+                MAX_IDLE_PENDING_TIMEOUT = mParser.getDurationMillis(KEY_MAX_IDLE_PENDING_TIMEOUT,
                         !COMPRESS_TIME ? 10 * 60 * 1000L : 60 * 1000L);
                 IDLE_PENDING_FACTOR = mParser.getFloat(KEY_IDLE_PENDING_FACTOR,
                         2f);
-                IDLE_TIMEOUT = mParser.getLong(KEY_IDLE_TIMEOUT,
+                IDLE_TIMEOUT = mParser.getDurationMillis(KEY_IDLE_TIMEOUT,
                         !COMPRESS_TIME ? 60 * 60 * 1000L : 6 * 60 * 1000L);
-                MAX_IDLE_TIMEOUT = mParser.getLong(KEY_MAX_IDLE_TIMEOUT,
+                MAX_IDLE_TIMEOUT = mParser.getDurationMillis(KEY_MAX_IDLE_TIMEOUT,
                         !COMPRESS_TIME ? 6 * 60 * 60 * 1000L : 30 * 60 * 1000L);
                 IDLE_FACTOR = mParser.getFloat(KEY_IDLE_FACTOR,
                         2f);
-                MIN_TIME_TO_ALARM = mParser.getLong(KEY_MIN_TIME_TO_ALARM,
+                MIN_TIME_TO_ALARM = mParser.getDurationMillis(KEY_MIN_TIME_TO_ALARM,
                         !COMPRESS_TIME ? 60 * 60 * 1000L : 6 * 60 * 1000L);
-                MAX_TEMP_APP_WHITELIST_DURATION = mParser.getLong(
+                MAX_TEMP_APP_WHITELIST_DURATION = mParser.getDurationMillis(
                         KEY_MAX_TEMP_APP_WHITELIST_DURATION, 5 * 60 * 1000L);
-                MMS_TEMP_APP_WHITELIST_DURATION = mParser.getLong(
+                MMS_TEMP_APP_WHITELIST_DURATION = mParser.getDurationMillis(
                         KEY_MMS_TEMP_APP_WHITELIST_DURATION, 60 * 1000L);
-                SMS_TEMP_APP_WHITELIST_DURATION = mParser.getLong(
+                SMS_TEMP_APP_WHITELIST_DURATION = mParser.getDurationMillis(
                         KEY_SMS_TEMP_APP_WHITELIST_DURATION, 20 * 1000L);
-                NOTIFICATION_WHITELIST_DURATION = mParser.getLong(
+                NOTIFICATION_WHITELIST_DURATION = mParser.getDurationMillis(
                         KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L);
             }
         }
diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java
index 9877717..5e6e9d34 100644
--- a/services/core/java/com/android/server/EntropyMixer.java
+++ b/services/core/java/com/android/server/EntropyMixer.java
@@ -196,11 +196,14 @@
      * Mixes in the output from HW RNG (if present) into the Linux RNG.
      */
     private void addHwRandomEntropy() {
+        if (!new File(hwRandomDevice).exists()) {
+            // HW RNG not present/exposed -- ignore
+            return;
+        }
+
         try {
             RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false);
             Slog.i(TAG, "Added HW RNG output to entropy pool");
-        } catch (FileNotFoundException ignored) {
-            // HW RNG not present/exposed -- ignore
         } catch (IOException e) {
             Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e);
         }
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 6174aec..8361132 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -261,6 +261,10 @@
 2756 fstrim_finish (time|2|3)
 
 # ---------------------------
+# Job scheduler
+# ---------------------------
+8000 job_deferred_execution (time|2|3)
+
 # AudioService.java
 # ---------------------------
 40000 volume_changed (stream|1), (prev_level|1), (level|1), (max_level|1), (caller|3)
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index d3ab125..02cfe3d 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.DUMP;
 import static android.net.IpSecManager.INVALID_RESOURCE_ID;
 import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.EINVAL;
 import static android.system.OsConstants.IPPROTO_UDP;
 import static android.system.OsConstants.SOCK_DGRAM;
 import static com.android.internal.util.Preconditions.checkNotNull;
@@ -51,6 +52,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -101,8 +103,14 @@
     /* Binder context for this service */
     private final Context mContext;
 
-    /** Should be a never-repeating global ID for resources */
-    private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
+    /**
+     * The next non-repeating global ID for tracking resources between users, this service,
+     * and kernel data structures. Accessing this variable is not thread safe, so it is
+     * only read or modified within blocks synchronized on IpSecService.this. We want to
+     * avoid -1 (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it).
+     */
+    @GuardedBy("IpSecService.this")
+    private int mNextResourceId = 1;
 
     interface IpSecServiceConfiguration {
         INetd getNetdInstance() throws RemoteException;
@@ -855,7 +863,7 @@
         checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
 
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
-        int resourceId = mNextResourceId.getAndIncrement();
+        final int resourceId = mNextResourceId++;
 
         int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
         String localAddress = "";
@@ -978,7 +986,7 @@
 
         int callingUid = Binder.getCallingUid();
         UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
-        int resourceId = mNextResourceId.getAndIncrement();
+        final int resourceId = mNextResourceId++;
         FileDescriptor sockFd = null;
         try {
             if (!userRecord.mSocketQuotaTracker.isAvailable()) {
@@ -1023,6 +1031,30 @@
         releaseResource(userRecord.mEncapSocketRecords, resourceId);
     }
 
+    @VisibleForTesting
+    void validateAlgorithms(IpSecConfig config, int direction) throws IllegalArgumentException {
+            IpSecAlgorithm auth = config.getAuthentication(direction);
+            IpSecAlgorithm crypt = config.getEncryption(direction);
+            IpSecAlgorithm aead = config.getAuthenticatedEncryption(direction);
+
+            // Validate the algorithm set
+            Preconditions.checkArgument(
+                    aead != null || crypt != null || auth != null,
+                    "No Encryption or Authentication algorithms specified");
+            Preconditions.checkArgument(
+                    auth == null || auth.isAuthentication(),
+                    "Unsupported algorithm for Authentication");
+            Preconditions.checkArgument(
+                crypt == null || crypt.isEncryption(), "Unsupported algorithm for Encryption");
+            Preconditions.checkArgument(
+                    aead == null || aead.isAead(),
+                    "Unsupported algorithm for Authenticated Encryption");
+            Preconditions.checkArgument(
+                    aead == null || (auth == null && crypt == null),
+                    "Authenticated Encryption is mutually exclusive with other Authentication "
+                                    + "or Encryption algorithms");
+    }
+
     /**
      * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
      * IllegalArgumentException if they are not.
@@ -1072,17 +1104,7 @@
         }
 
         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");
-            }
+            validateAlgorithms(config, direction);
 
             // Retrieve SPI record; will throw IllegalArgumentException if not found
             userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction));
@@ -1101,7 +1123,7 @@
             IpSecConfig c, IBinder binder) throws RemoteException {
         checkIpSecConfig(c);
         checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
-        int resourceId = mNextResourceId.getAndIncrement();
+        final int resourceId = mNextResourceId++;
 
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
 
@@ -1220,7 +1242,11 @@
                                 info.getSpiRecord(direction).getSpi());
             }
         } catch (ServiceSpecificException e) {
-            // FIXME: get the error code and throw is at an IOException from Errno Exception
+            if (e.errorCode == EINVAL) {
+                throw new IllegalArgumentException(e.toString());
+            } else {
+                throw e;
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 0a86281..ea748db 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1144,7 +1144,7 @@
     }
 
     /**
-     * Returns the system information of the GNSS hardware.
+     * Returns the year of the GNSS hardware.
      */
     @Override
     public int getGnssYearOfHardware() {
@@ -1155,6 +1155,19 @@
         }
     }
 
+
+    /**
+     * Returns the model name of the GNSS hardware.
+     */
+    @Override
+    public String getGnssHardwareModelName() {
+        if (mGnssSystemInfoProvider != null) {
+            return mGnssSystemInfoProvider.getGnssHardwareModelName();
+        } else {
+            return LocationManager.GNSS_HARDWARE_MODEL_NAME_UNKNOWN;
+        }
+    }
+
     /**
      * Runs some checks for GNSS (FINE) level permissions, used by several methods which directly
      * (try to) access GNSS information at this layer.
@@ -2241,6 +2254,64 @@
         }
     }
 
+    /**
+     * Provides an interface to inject and set the last location if location is not available
+     * currently.
+     *
+     * This helps in cases where the product (Cars for example) has saved the last known location
+     * before powering off.  This interface lets the client inject the saved location while the GPS
+     * chipset is getting its first fix, there by improving user experience.
+     *
+     * @param location - Location object to inject
+     * @return true if update was successful, false if not
+     */
+    @Override
+    public boolean injectLocation(Location location) {
+        mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
+                "Location Hardware permission not granted to inject location");
+        mContext.enforceCallingPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
+                "Access Fine Location permission not granted to inject Location");
+
+        if (location == null) {
+            if (D) {
+                Log.d(TAG, "injectLocation(): called with null location");
+            }
+            return false;
+        }
+        LocationProviderInterface p = null;
+        String provider = location.getProvider();
+        if (provider != null) {
+            p = mProvidersByName.get(provider);
+        }
+        if (p == null) {
+            if (D) {
+                Log.d(TAG, "injectLocation(): unknown provider");
+            }
+            return false;
+        }
+        synchronized (mLock) {
+            if (!isAllowedByCurrentUserSettingsLocked(provider)) {
+                if (D) {
+                    Log.d(TAG, "Location disabled in Settings for current user:" + mCurrentUserId);
+                }
+                return false;
+            } else {
+                // NOTE: If last location is already available, location is not injected.  If
+                // provider's normal source (like a GPS chipset) have already provided an output,
+                // there is no need to inject this location.
+                if (mLastLocation.get(provider) == null) {
+                    updateLastLocationLocked(location, provider);
+                } else {
+                    if (D) {
+                        Log.d(TAG, "injectLocation(): Location exists. Not updating");
+                    }
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     @Override
     public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
             String packageName) {
@@ -2601,30 +2672,18 @@
 
     private void handleLocationChangedLocked(Location location, boolean passive) {
         if (D) Log.d(TAG, "incoming location: " + location);
-
         long now = SystemClock.elapsedRealtime();
         String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider());
-
         // Skip if the provider is unknown.
         LocationProviderInterface p = mProvidersByName.get(provider);
         if (p == null) return;
-
-        // Update last known locations
-        Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
-        Location lastNoGPSLocation;
+        updateLastLocationLocked(location, provider);
+        // mLastLocation should have been updated from the updateLastLocationLocked call above.
         Location lastLocation = mLastLocation.get(provider);
         if (lastLocation == null) {
-            lastLocation = new Location(provider);
-            mLastLocation.put(provider, lastLocation);
-        } else {
-            lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
-            if (noGPSLocation == null && lastNoGPSLocation != null) {
-                // New location has no no-GPS location: adopt last no-GPS location. This is set
-                // directly into location because we do not want to notify COARSE clients.
-                location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation);
-            }
+            Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed");
+            return;
         }
-        lastLocation.set(location);
 
         // Update last known coarse interval location if enough time has passed.
         Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider);
@@ -2640,7 +2699,7 @@
         // Don't ever return a coarse location that is more recent than the allowed update
         // interval (i.e. don't allow an app to keep registering and unregistering for
         // location updates to overcome the minimum interval).
-        noGPSLocation =
+        Location noGPSLocation =
                 lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
 
         // Skip if there are no UpdateRecords for this provider.
@@ -2765,6 +2824,30 @@
         }
     }
 
+    /**
+     * Updates last location with the given location
+     *
+     * @param location             new location to update
+     * @param provider             Location provider to update for
+     */
+    private void updateLastLocationLocked(Location location, String provider) {
+        Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+        Location lastNoGPSLocation;
+        Location lastLocation = mLastLocation.get(provider);
+        if (lastLocation == null) {
+            lastLocation = new Location(provider);
+            mLastLocation.put(provider, lastLocation);
+        } else {
+            lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+            if (noGPSLocation == null && lastNoGPSLocation != null) {
+                // New location has no no-GPS location: adopt last no-GPS location. This is set
+                // directly into location because we do not want to notify COARSE clients.
+                location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation);
+            }
+        }
+        lastLocation.set(location);
+    }
+
     private class LocationWorkerHandler extends Handler {
         public LocationWorkerHandler(Looper looper) {
             super(looper, null, true);
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 44c0227..33f7769 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -26,6 +26,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.database.ContentObserver;
 import android.location.LocationManager;
 import android.net.INetworkRecommendationProvider;
@@ -50,14 +51,17 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.TransferPipe;
+import com.android.internal.telephony.SmsApplication;
 import com.android.internal.util.DumpUtils;
 
 import java.io.FileDescriptor;
@@ -91,7 +95,8 @@
     private final Object mPackageMonitorLock = new Object();
     private final Object mServiceConnectionLock = new Object();
     private final Handler mHandler;
-    private final DispatchingContentObserver mContentObserver;
+    private final DispatchingContentObserver mRecommendationSettingsObserver;
+    private final ContentObserver mUseOpenWifiPackageObserver;
     private final Function<NetworkScorerAppData, ScoringServiceConnection> mServiceConnProducer;
 
     @GuardedBy("mPackageMonitorLock")
@@ -255,8 +260,40 @@
         mContext.registerReceiverAsUser(
                 mLocationModeReceiver, UserHandle.SYSTEM, locationModeFilter,
                 null /* broadcastPermission*/, mHandler);
-        mContentObserver = new DispatchingContentObserver(context, mHandler);
+        mRecommendationSettingsObserver = new DispatchingContentObserver(context, mHandler);
         mServiceConnProducer = serviceConnProducer;
+        mUseOpenWifiPackageObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri, int userId) {
+                Uri useOpenWifiPkgUri = Global.getUriFor(Global.USE_OPEN_WIFI_PACKAGE);
+                if (useOpenWifiPkgUri.equals(uri)) {
+                    String useOpenWifiPackage = Global.getString(mContext.getContentResolver(),
+                            Global.USE_OPEN_WIFI_PACKAGE);
+                    if (!TextUtils.isEmpty(useOpenWifiPackage)) {
+                        LocalServices.getService(PackageManagerInternal.class)
+                                .grantDefaultPermissionsToDefaultUseOpenWifiApp(useOpenWifiPackage,
+                                        userId);
+                    }
+                }
+            }
+        };
+        mContext.getContentResolver().registerContentObserver(
+                Global.getUriFor(Global.USE_OPEN_WIFI_PACKAGE),
+                false /*notifyForDescendants*/,
+                mUseOpenWifiPackageObserver);
+        // Set a callback for the package manager to query the use open wifi app.
+        LocalServices.getService(PackageManagerInternal.class).setUseOpenWifiAppPackagesProvider(
+                new PackageManagerInternal.PackagesProvider() {
+                    @Override
+                    public String[] getPackages(int userId) {
+                        String useOpenWifiPackage = Global.getString(mContext.getContentResolver(),
+                                Global.USE_OPEN_WIFI_PACKAGE);
+                        if (!TextUtils.isEmpty(useOpenWifiPackage)) {
+                            return new String[]{useOpenWifiPackage};
+                        }
+                        return null;
+                    }
+                });
     }
 
     /** Called when the system is ready to run third-party code but before it actually does so. */
@@ -287,11 +324,11 @@
 
     private void registerRecommendationSettingsObserver() {
         final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE);
-        mContentObserver.observe(packageNameUri,
+        mRecommendationSettingsObserver.observe(packageNameUri,
                 ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED);
 
         final Uri settingUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
-        mContentObserver.observe(settingUri,
+        mRecommendationSettingsObserver.observe(settingUri,
                 ServiceHandler.MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED);
     }
 
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 6a0d3ff..dc2f2a5 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -96,6 +96,7 @@
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
+import android.util.DataUnit;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -968,11 +969,6 @@
                         || mForceAdoptable) {
                     flags |= DiskInfo.FLAG_ADOPTABLE;
                 }
-                // Adoptable storage isn't currently supported on FBE devices
-                if (StorageManager.isFileEncryptedNativeOnly()
-                        && !SystemProperties.getBoolean(StorageManager.PROP_ADOPTABLE_FBE, false)) {
-                    flags &= ~DiskInfo.FLAG_ADOPTABLE;
-                }
                 mDisks.put(diskId, new DiskInfo(diskId, flags));
             }
         }
@@ -1910,12 +1906,6 @@
         }
 
         if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
-            if (StorageManager.isFileEncryptedNativeOnly()
-                    && !SystemProperties.getBoolean(StorageManager.PROP_ADOPTABLE_FBE, false)) {
-                throw new IllegalStateException(
-                        "Adoptable storage not available on device with native FBE");
-            }
-
             synchronized (mLock) {
                 mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
 
@@ -2201,7 +2191,7 @@
         }
 
         try {
-            mVold.fdeEnable(type, password, IVold.ENCRYPTION_FLAG_IN_PLACE);
+            mVold.fdeEnable(type, password, 0);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
             return -1;
@@ -3519,8 +3509,8 @@
                 pw.print(") total size: ");
                 pw.print(pair.second);
                 pw.print(" (");
-                pw.print((float) pair.second / TrafficStats.GB_IN_BYTES);
-                pw.println(" GB)");
+                pw.print(DataUnit.MEBIBYTES.toBytes(pair.second));
+                pw.println(" MiB)");
             }
             pw.println("Force adoptable: " + mForceAdoptable);
             pw.println();
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 0e51fda..c1cda98 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -26,6 +26,7 @@
 import android.database.ContentObserver;
 import android.hardware.input.InputManager;
 import android.hardware.vibrator.V1_0.Constants.EffectStrength;
+import android.icu.text.DateFormat;
 import android.media.AudioManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerSaveState;
@@ -62,6 +63,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.LinkedList;
+import java.util.Date;
 
 public class VibratorService extends IVibratorService.Stub
         implements InputManager.InputDeviceListener {
@@ -69,6 +71,8 @@
     private static final boolean DEBUG = false;
     private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
 
+    private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 };
+
     private final LinkedList<VibrationInfo> mPreviousVibrations;
     private final int mPreviousVibrationsLimit;
     private final boolean mAllowPriorityVibrationsInLowPowerMode;
@@ -110,7 +114,12 @@
     private class Vibration implements IBinder.DeathRecipient {
         private final IBinder mToken;
         private final VibrationEffect mEffect;
+        // Start time in CLOCK_BOOTTIME base.
         private final long mStartTime;
+        // Start time in unix epoch time. Only to be used for debugging purposes and to correlate
+        // with other system events, any duration calculations should be done use mStartTime so as
+        // not to be affected by discontinuities created by RTC adjustments.
+        private final long mStartTimeDebug;
         private final int mUsageHint;
         private final int mUid;
         private final String mOpPkg;
@@ -119,7 +128,8 @@
                 int usageHint, int uid, String opPkg) {
             mToken = token;
             mEffect = effect;
-            mStartTime = SystemClock.uptimeMillis();
+            mStartTime = SystemClock.elapsedRealtime();
+            mStartTimeDebug = System.currentTimeMillis();
             mUsageHint = usageHint;
             mUid = uid;
             mOpPkg = opPkg;
@@ -153,18 +163,22 @@
             return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg))
                     && !repeating;
         }
+
+        public VibrationInfo toInfo() {
+            return new VibrationInfo(mStartTimeDebug, mEffect, mUsageHint, mUid, mOpPkg);
+        }
     }
 
     private static class VibrationInfo {
-        private final long mStartTime;
+        private final long mStartTimeDebug;
         private final VibrationEffect mEffect;
         private final int mUsageHint;
         private final int mUid;
         private final String mOpPkg;
 
-        public VibrationInfo(long startTime, VibrationEffect effect,
+        public VibrationInfo(long startTimeDebug, VibrationEffect effect,
                 int usageHint, int uid, String opPkg) {
-            mStartTime = startTime;
+            mStartTimeDebug = startTimeDebug;
             mEffect = effect;
             mUsageHint = usageHint;
             mUid = uid;
@@ -174,8 +188,8 @@
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append(", startTime: ")
-                    .append(mStartTime)
+                    .append("startTime: ")
+                    .append(DateFormat.getDateTimeInstance().format(new Date(mStartTimeDebug)))
                     .append(", effect: ")
                     .append(mEffect)
                     .append(", usageHint: ")
@@ -225,7 +239,7 @@
                 com.android.internal.R.array.config_virtualKeyVibePattern);
         VibrationEffect clickEffect = createEffect(clickEffectTimings);
         VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
-                new long[] {0, 30, 100, 30} /*timings*/, -1);
+               DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS, -1 /*repeatIndex*/);
         long[] tickEffectTimings = getLongIntArray(context.getResources(),
                 com.android.internal.R.array.config_clockTickVibePattern);
         VibrationEffect tickEffect = createEffect(tickEffectTimings);
@@ -392,17 +406,7 @@
         }
 
         Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
-
-        // Only link against waveforms since they potentially don't have a finish if
-        // they're repeating. Let other effects just play out until they're done.
-        if (effect instanceof VibrationEffect.Waveform) {
-            try {
-                token.linkToDeath(vib, 0);
-            } catch (RemoteException e) {
-                return;
-            }
-        }
-
+        linkVibration(vib);
 
         long ident = Binder.clearCallingIdentity();
         try {
@@ -430,8 +434,7 @@
         if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
             mPreviousVibrations.removeFirst();
         }
-        mPreviousVibrations.addLast(new VibrationInfo(
-                    vib.mStartTime, vib.mEffect, vib.mUsageHint, vib.mUid, vib.mOpPkg));
+        mPreviousVibrations.addLast(vib.toInfo());
     }
 
     @Override // Binder call
@@ -589,10 +592,23 @@
                         AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
                         mCurrentVibration.mOpPkg);
             } catch (RemoteException e) { }
+            unlinkVibration(mCurrentVibration);
             mCurrentVibration = null;
         }
     }
 
+    private void linkVibration(Vibration vib) {
+        // Only link against waveforms since they potentially don't have a finish if
+        // they're repeating. Let other effects just play out until they're done.
+        if (vib.mEffect instanceof VibrationEffect.Waveform) {
+            try {
+                vib.mToken.linkToDeath(vib, 0);
+            } catch (RemoteException e) {
+                return;
+            }
+        }
+    }
+
     private void unlinkVibration(Vibration vib) {
         if (vib.mEffect instanceof VibrationEffect.Waveform) {
             vib.mToken.unlinkToDeath(vib, 0);
@@ -742,8 +758,7 @@
         synchronized (mInputDeviceVibrators) {
             VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect;
             // Input devices don't support prebaked effect, so skip trying it with them.
-            final int vibratorCount = mInputDeviceVibrators.size();
-            if (vibratorCount == 0) {
+            if (mInputDeviceVibrators.isEmpty()) {
                 long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM);
                 if (timeout > 0) {
                     noteVibratorOnLocked(vib.mUid, timeout);
@@ -753,12 +768,11 @@
             if (!prebaked.shouldFallback()) {
                 return 0;
             }
-            final int id = prebaked.getId();
-            if (id < 0 || id >= mFallbackEffects.length || mFallbackEffects[id] == null) {
+            VibrationEffect effect = getFallbackEffect(prebaked.getId());
+            if (effect == null) {
                 Slog.w(TAG, "Failed to play prebaked effect, no fallback");
                 return 0;
             }
-            VibrationEffect effect = mFallbackEffects[id];
             Vibration fallbackVib =
                     new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg);
             startVibrationInnerLocked(fallbackVib);
@@ -766,6 +780,13 @@
         return 0;
     }
 
+    private VibrationEffect getFallbackEffect(int effectId) {
+        if (effectId < 0 || effectId >= mFallbackEffects.length) {
+            return null;
+        }
+        return mFallbackEffects[effectId];
+    }
+
     private void noteVibratorOnLocked(int uid, long millis) {
         try {
             mBatteryStatsService.noteVibratorOn(uid, millis);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 31aea63..8d2e3a2 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2007,11 +2007,11 @@
                     getAccountRemovedReceivers(accountToRename, accounts);
                 accounts.accountsDb.beginTransaction();
                 Account renamedAccount = new Account(newName, accountToRename.type);
-                if ((accounts.accountsDb.findCeAccountId(renamedAccount) >= 0)) {
-                    Log.e(TAG, "renameAccount failed - account with new name already exists");
-                    return null;
-                }
                 try {
+                    if ((accounts.accountsDb.findCeAccountId(renamedAccount) >= 0)) {
+                        Log.e(TAG, "renameAccount failed - account with new name already exists");
+                        return null;
+                    }
                     final long accountId = accounts.accountsDb.findDeAccountId(accountToRename);
                     if (accountId >= 0) {
                         accounts.accountsDb.renameCeAccount(accountId, newName);
@@ -5595,25 +5595,26 @@
         long ident = Binder.clearCallingIdentity();
         try {
             packages = mPackageManager.getPackagesForUid(callingUid);
+            if (packages != null) {
+                for (String name : packages) {
+                    try {
+                        PackageInfo packageInfo =
+                                mPackageManager.getPackageInfo(name, 0 /* flags */);
+                        if (packageInfo != null
+                                && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                                != 0) {
+                            return true;
+                        }
+                    } catch (NameNotFoundException e) {
+                        Log.w(TAG, String.format("Could not find package [%s]", name), e);
+                    }
+                }
+            } else {
+                Log.w(TAG, "No known packages with uid " + callingUid);
+            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
-        if (packages != null) {
-            for (String name : packages) {
-                try {
-                    PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 0 /* flags */);
-                    if (packageInfo != null
-                            && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
-                                    != 0) {
-                        return true;
-                    }
-                } catch (NameNotFoundException e) {
-                    Log.w(TAG, String.format("Could not find package [%s]", name), e);
-                }
-            }
-        } else {
-            Log.w(TAG, "No known packages with uid " + callingUid);
-        }
         return false;
     }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 088ddea..2f7d4c1 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -348,7 +348,7 @@
 
         ServiceLookupResult res =
             retrieveServiceLocked(service, resolvedType, callingPackage,
-                    callingPid, callingUid, userId, true, callerFg, false);
+                    callingPid, callingUid, userId, true, callerFg, false, false);
         if (res == null) {
             return null;
         }
@@ -597,7 +597,7 @@
 
         // If this service is active, make sure it is stopped.
         ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, null,
-                Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false);
+                Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false);
         if (r != null) {
             if (r.record != null) {
                 final long origId = Binder.clearCallingIdentity();
@@ -658,7 +658,7 @@
     IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
         ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage,
                 Binder.getCallingPid(), Binder.getCallingUid(),
-                UserHandle.getCallingUserId(), false, false, false);
+                UserHandle.getCallingUserId(), false, false, false, false);
 
         IBinder ret = null;
         if (r != null) {
@@ -1282,12 +1282,19 @@
                     + ") set BIND_ALLOW_WHITELIST_MANAGEMENT when binding service " + service);
         }
 
+        if ((flags & Context.BIND_ALLOW_INSTANT) != 0 && !isCallerSystem) {
+            throw new SecurityException(
+                    "Non-system caller " + caller + " (pid=" + Binder.getCallingPid()
+                            + ") set BIND_ALLOW_INSTANT when binding service " + service);
+        }
+
         final boolean callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
         final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
+        final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
 
         ServiceLookupResult res =
             retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(),
-                    Binder.getCallingUid(), userId, true, callerFg, isBindExternal);
+                    Binder.getCallingUid(), userId, true, callerFg, isBindExternal, allowInstant);
         if (res == null) {
             return 0;
         }
@@ -1657,7 +1664,8 @@
 
     private ServiceLookupResult retrieveServiceLocked(Intent service,
             String resolvedType, String callingPackage, int callingPid, int callingUid, int userId,
-            boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal) {
+            boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
+            boolean allowInstant) {
         ServiceRecord r = null;
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
                 + " type=" + resolvedType + " callingUid=" + callingUid);
@@ -1685,11 +1693,14 @@
         }
         if (r == null) {
             try {
+                int flags = ActivityManagerService.STOCK_PM_FLAGS
+                        | PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+                if (allowInstant) {
+                    flags |= PackageManager.MATCH_INSTANT;
+                }
                 // TODO: come back and remove this assumption to triage all services
                 ResolveInfo rInfo = mAm.getPackageManagerInternalLocked().resolveService(service,
-                        resolvedType, ActivityManagerService.STOCK_PM_FLAGS
-                                | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-                        userId, callingUid);
+                        resolvedType, flags, userId, callingUid);
                 ServiceInfo sInfo =
                     rInfo != null ? rInfo.serviceInfo : null;
                 if (sInfo == null) {
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index af5cf1e..e38148c 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -556,10 +556,10 @@
         return stack == getTopStack();
     }
 
-    boolean isTopFullscreenStack(ActivityStack stack) {
+    boolean isTopNotPinnedStack(ActivityStack stack) {
         for (int i = mStacks.size() - 1; i >= 0; --i) {
             final ActivityStack current = mStacks.get(i);
-            if (current.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+            if (!current.inPinnedWindowingMode()) {
                 return current == stack;
             }
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index b3a596c..0d6d2bd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -77,8 +77,8 @@
     private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
     private static final long DEFAULT_GC_TIMEOUT = 5*1000;
     private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
-    private static final long DEFAULT_FULL_PSS_MIN_INTERVAL = 10*60*1000;
-    private static final long DEFAULT_FULL_PSS_LOWERED_INTERVAL = 2*60*1000;
+    private static final long DEFAULT_FULL_PSS_MIN_INTERVAL = 20*60*1000;
+    private static final long DEFAULT_FULL_PSS_LOWERED_INTERVAL = 5*60*1000;
     private static final long DEFAULT_POWER_CHECK_INTERVAL = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000;
     private static final int DEFAULT_POWER_CHECK_MAX_CPU_1 = 25;
     private static final int DEFAULT_POWER_CHECK_MAX_CPU_2 = 25;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7dfde56..624035d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2521,13 +2521,15 @@
                         }
                     }
                     if (proc != null) {
+                        long startTime = SystemClock.currentThreadTimeMillis();
                         long pss = Debug.getPss(pid, tmp, null);
+                        long endTime = SystemClock.currentThreadTimeMillis();
                         synchronized (ActivityManagerService.this) {
                             if (pss != 0 && proc.thread != null && proc.setProcState == procState
                                     && proc.pid == pid && proc.lastPssTime == lastPssTime) {
                                 num++;
                                 recordPssSampleLocked(proc, procState, pss, tmp[0], tmp[1],
-                                        SystemClock.uptimeMillis());
+                                        endTime-startTime, SystemClock.uptimeMillis());
                             }
                         }
                     }
@@ -2695,6 +2697,13 @@
         }
 
         @Override
+        public void onBootPhase(int phase) {
+            if (phase == PHASE_SYSTEM_SERVICES_READY) {
+                mService.mBatteryStatsService.systemServicesReady();
+            }
+        }
+
+        @Override
         public void onCleanupUser(int userId) {
             mService.mBatteryStatsService.onCleanupUser(userId);
         }
@@ -6539,13 +6548,17 @@
                 }
             }
             infos[i] = new Debug.MemoryInfo();
+            long startTime = SystemClock.currentThreadTimeMillis();
             Debug.getMemoryInfo(pids[i], infos[i]);
+            long endTime = SystemClock.currentThreadTimeMillis();
             if (proc != null) {
                 synchronized (this) {
                     if (proc.thread != null && proc.setAdj == oomAdj) {
                         // Record this for posterity if the process has been stable.
                         proc.baseProcessTracker.addPss(infos[i].getTotalPss(),
-                                infos[i].getTotalUss(), false, proc.pkgList);
+                                infos[i].getTotalUss(), false,
+                                ProcessStats.ADD_PSS_EXTERNAL_SLOW, endTime-startTime,
+                                proc.pkgList);
                     }
                 }
             }
@@ -6567,12 +6580,15 @@
                 }
             }
             long[] tmpUss = new long[1];
+            long startTime = SystemClock.currentThreadTimeMillis();
             pss[i] = Debug.getPss(pids[i], tmpUss, null);
+            long endTime = SystemClock.currentThreadTimeMillis();
             if (proc != null) {
                 synchronized (this) {
                     if (proc.thread != null && proc.setAdj == oomAdj) {
                         // Record this for posterity if the process has been stable.
-                        proc.baseProcessTracker.addPss(pss[i], tmpUss[0], false, proc.pkgList);
+                        proc.baseProcessTracker.addPss(pss[i], tmpUss[0], false,
+                                ProcessStats.ADD_PSS_EXTERNAL, endTime-startTime, proc.pkgList);
                     }
                 }
             }
@@ -10364,26 +10380,6 @@
     }
 
     @Override
-    public void cancelTaskThumbnailTransition(int taskId) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
-                "cancelTaskThumbnailTransition()");
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (this) {
-                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY);
-                if (task == null) {
-                    Slog.w(TAG, "cancelTaskThumbnailTransition: taskId=" + taskId + " not found");
-                    return;
-                }
-                task.cancelThumbnailTransition();
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
     public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
         enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
@@ -12265,6 +12261,7 @@
         mConstants.start(mContext.getContentResolver());
         mCoreSettingsObserver = new CoreSettingsObserver(this);
         mFontScaleSettingObserver = new FontScaleSettingObserver();
+        GlobalSettingsToPropertiesMapper.start(mContext.getContentResolver());
 
         // Now that the settings provider is published we can consider sending
         // in a rescue party.
@@ -13878,68 +13875,100 @@
                 Context.WINDOW_SERVICE)).addView(v, lp);
     }
 
-    public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) {
-        if (sender != null && !(sender instanceof PendingIntentRecord)) {
-            return;
+    @Override
+    public void noteWakeupAlarm(IIntentSender sender, WorkSource workSource, int sourceUid,
+            String sourcePkg, String tag) {
+        if (workSource != null && workSource.isEmpty()) {
+            workSource = null;
         }
-        final PendingIntentRecord rec = (PendingIntentRecord)sender;
-        final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
-        synchronized (stats) {
-            if (mBatteryStatsService.isOnBattery()) {
-                mBatteryStatsService.enforceCallingPermission();
-                int MY_UID = Binder.getCallingUid();
-                final int uid;
-                if (sender == null) {
-                    uid = sourceUid;
-                } else {
-                    uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid;
+
+        if (sourceUid <= 0 && workSource == null) {
+            // Try and derive a UID to attribute things to based on the caller.
+            if (sender != null) {
+                if (!(sender instanceof PendingIntentRecord)) {
+                    return;
                 }
-                BatteryStatsImpl.Uid.Pkg pkg =
-                    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,
-                        tag);
+
+                final PendingIntentRecord rec = (PendingIntentRecord) sender;
+                final int callerUid = Binder.getCallingUid();
+                sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid;
+            } else {
+                // TODO(narayan): Should we throw an exception in this case ? It means that we
+                // haven't been able to derive a UID to attribute things to.
+                return;
             }
         }
+
+        if (DEBUG_POWER) {
+            Slog.w(TAG, "noteWakupAlarm[ sourcePkg=" + sourcePkg + ", sourceUid=" + sourceUid
+                    + ", workSource=" + workSource + ", tag=" + tag + "]");
+        }
+
+        mBatteryStatsService.noteWakupAlarm(sourcePkg, sourceUid, workSource, tag);
     }
 
-    public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) {
-        if (sender != null && !(sender instanceof PendingIntentRecord)) {
-            return;
+    @Override
+    public void noteAlarmStart(IIntentSender sender, WorkSource workSource, int sourceUid,
+            String tag) {
+        if (workSource != null && workSource.isEmpty()) {
+            workSource = null;
         }
-        final PendingIntentRecord rec = (PendingIntentRecord)sender;
-        final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
-        synchronized (stats) {
-            mBatteryStatsService.enforceCallingPermission();
-            int MY_UID = Binder.getCallingUid();
-            final int uid;
-            if (sender == null) {
-                uid = sourceUid;
+
+        if (sourceUid <= 0 && workSource == null) {
+            // Try and derive a UID to attribute things to based on the caller.
+            if (sender != null) {
+                if (!(sender instanceof PendingIntentRecord)) {
+                    return;
+                }
+
+                final PendingIntentRecord rec = (PendingIntentRecord) sender;
+                final int callerUid = Binder.getCallingUid();
+                sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid;
             } else {
-                uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid;
+                // TODO(narayan): Should we throw an exception in this case ? It means that we
+                // haven't been able to derive a UID to attribute things to.
+                return;
             }
-            mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid);
         }
+
+        if (DEBUG_POWER) {
+            Slog.w(TAG, "noteAlarmStart[sourceUid=" + sourceUid + ", workSource=" + workSource +
+                    ", tag=" + tag + "]");
+        }
+
+        mBatteryStatsService.noteAlarmStart(tag, workSource, sourceUid);
     }
 
-    public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) {
-        if (sender != null && !(sender instanceof PendingIntentRecord)) {
-            return;
+    @Override
+    public void noteAlarmFinish(IIntentSender sender, WorkSource workSource, int sourceUid,
+            String tag) {
+        if (workSource != null && workSource.isEmpty()) {
+            workSource = null;
         }
-        final PendingIntentRecord rec = (PendingIntentRecord)sender;
-        final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
-        synchronized (stats) {
-            mBatteryStatsService.enforceCallingPermission();
-            int MY_UID = Binder.getCallingUid();
-            final int uid;
-            if (sender == null) {
-                uid = sourceUid;
+
+        if (sourceUid <= 0 && workSource == null) {
+            // Try and derive a UID to attribute things to based on the caller.
+            if (sender != null) {
+                if (!(sender instanceof PendingIntentRecord)) {
+                    return;
+                }
+
+                final PendingIntentRecord rec = (PendingIntentRecord) sender;
+                final int callerUid = Binder.getCallingUid();
+                sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid;
             } else {
-                uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid;
+                // TODO(narayan): Should we throw an exception in this case ? It means that we
+                // haven't been able to derive a UID to attribute things to.
+                return;
             }
-            mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid);
         }
+
+        if (DEBUG_POWER) {
+            Slog.w(TAG, "noteAlarmFinish[sourceUid=" + sourceUid + ", workSource=" + workSource +
+                    ", tag=" + tag + "]");
+        }
+
+        mBatteryStatsService.noteAlarmFinish(tag, workSource, sourceUid);
     }
 
     public boolean killPids(int[] pids, String pReason, boolean secure) {
@@ -14863,6 +14892,7 @@
                 (process != null && process.info != null) ?
                         (process.info.isInstantApp() ? 1 : 0) : -1,
                 activity != null ? activity.shortComponentName : null,
+                activity != null ? activity.packageName : null,
                 process != null ? (process.isInterestingToUserLocked() ? 1 : 0) : -1);
 
         // Rate-limit how often we're willing to do the heavy lifting below to
@@ -17647,11 +17677,20 @@
                 if (mi == null) {
                     mi = new Debug.MemoryInfo();
                 }
+                final int reportType;
+                final long startTime;
+                final long endTime;
                 if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
+                    reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW;
+                    startTime = SystemClock.currentThreadTimeMillis();
                     Debug.getMemoryInfo(pid, mi);
+                    endTime = SystemClock.currentThreadTimeMillis();
                     hasSwapPss = mi.hasSwappedOutPss;
                 } else {
+                    reportType = ProcessStats.ADD_PSS_EXTERNAL;
+                    startTime = SystemClock.currentThreadTimeMillis();
                     mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
+                    endTime = SystemClock.currentThreadTimeMillis();
                     mi.dalvikPrivateDirty = (int)tmpLong[0];
                 }
                 if (opts.dumpDetails) {
@@ -17694,7 +17733,8 @@
                 synchronized (this) {
                     if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
                         // Record this for posterity if the process has been stable.
-                        r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true, r.pkgList);
+                        r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true,
+                                reportType, endTime-startTime, r.pkgList);
                     }
                 }
 
@@ -18137,11 +18177,20 @@
             if (mi == null) {
                 mi = new Debug.MemoryInfo();
             }
+            final int reportType;
+            final long startTime;
+            final long endTime;
             if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
+                reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW;
+                startTime = SystemClock.currentThreadTimeMillis();
                 Debug.getMemoryInfo(pid, mi);
+                endTime = SystemClock.currentThreadTimeMillis();
                 hasSwapPss = mi.hasSwappedOutPss;
             } else {
+                reportType = ProcessStats.ADD_PSS_EXTERNAL;
+                startTime = SystemClock.currentThreadTimeMillis();
                 mi.dalvikPss = (int) Debug.getPss(pid, tmpLong, null);
+                endTime = SystemClock.currentThreadTimeMillis();
                 mi.dalvikPrivateDirty = (int) tmpLong[0];
             }
             if (opts.dumpDetails) {
@@ -18180,7 +18229,8 @@
             synchronized (this) {
                 if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
                     // Record this for posterity if the process has been stable.
-                    r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true, r.pkgList);
+                    r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true,
+                            reportType, endTime-startTime, r.pkgList);
                 }
             }
 
@@ -19900,16 +19950,14 @@
         userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
                 ALLOW_NON_FULL, "broadcast", callerPackage);
 
-        // Make sure that the user who is receiving this broadcast is running.
-        // If not, we will just skip it. Make an exception for shutdown broadcasts
-        // and upgrade steps.
-
-        if (userId != UserHandle.USER_ALL && !mUserController.isUserRunning(userId, 0)) {
+        // Make sure that the user who is receiving this broadcast or its parent is running.
+        // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
+        if (userId != UserHandle.USER_ALL && !mUserController.isUserOrItsParentRunning(userId)) {
             if ((callingUid != SYSTEM_UID
                     || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
                     && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
                 Slog.w(TAG, "Skipping broadcast of " + intent
-                        + ": user " + userId + " is stopped");
+                        + ": user " + userId + " and its parent (if any) are stopped");
                 return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
             }
         }
@@ -22444,11 +22492,12 @@
      * Record new PSS sample for a process.
      */
     void recordPssSampleLocked(ProcessRecord proc, int procState, long pss, long uss, long swapPss,
-            long now) {
+            long pssDuration, long now) {
         EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss * 1024, uss * 1024,
                 swapPss * 1024);
         proc.lastPssTime = now;
-        proc.baseProcessTracker.addPss(pss, uss, true, proc.pkgList);
+        proc.baseProcessTracker.addPss(pss, uss, true, ProcessStats.ADD_PSS_INTERNAL,
+                pssDuration, proc.pkgList);
         if (DEBUG_PSS) Slog.d(TAG_PSS,
                 "PSS of " + proc.toShortString() + ": " + pss + " lastPss=" + proc.lastPss
                 + " state=" + ProcessList.makeProcStateString(procState));
@@ -22943,8 +22992,11 @@
                 // the data right when a process is transitioning between process
                 // states, which well tend to give noisy data.
                 long start = SystemClock.uptimeMillis();
+                long startTime = SystemClock.currentThreadTimeMillis();
                 long pss = Debug.getPss(app.pid, mTmpLong, null);
-                recordPssSampleLocked(app, app.curProcState, pss, mTmpLong[0], mTmpLong[1], now);
+                long endTime = SystemClock.currentThreadTimeMillis();
+                recordPssSampleLocked(app, app.curProcState, pss, endTime-startTime,
+                        mTmpLong[0], mTmpLong[1], now);
                 mPendingPssProcesses.remove(app);
                 Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState
                         + " to " + app.curProcState + ": "
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index eb022b7..66f0592 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -42,6 +42,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.util.StatsLog;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.SomeArgs;
@@ -431,6 +432,12 @@
         builder.setType(type);
         builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivity.info.name);
         mMetricsLogger.write(builder);
+        StatsLog.write(
+                StatsLog.APP_START_CANCEL_CHANGED,
+                info.launchedActivity.appInfo.uid,
+                info.launchedActivity.packageName,
+                convertAppStartTransitionType(type),
+                info.launchedActivity.info.name);
     }
 
     private void logAppTransitionMultiEvents() {
@@ -450,9 +457,9 @@
                 builder.addTaggedData(APP_TRANSITION_CALLING_PACKAGE_NAME,
                         info.launchedActivity.launchedFromPackage);
             }
-            if (info.launchedActivity.info.launchToken != null) {
-                builder.addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN,
-                        info.launchedActivity.info.launchToken);
+            String launchToken = info.launchedActivity.info.launchToken;
+            if (launchToken != null) {
+                builder.addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN, launchToken);
                 info.launchedActivity.info.launchToken = null;
             }
             builder.addTaggedData(APP_TRANSITION_IS_EPHEMERAL, isInstantApp ? 1 : 0);
@@ -470,9 +477,37 @@
             }
             builder.addTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS, info.windowsDrawnDelayMs);
             mMetricsLogger.write(builder);
+            StatsLog.write(
+                    StatsLog.APP_START_CHANGED,
+                    info.launchedActivity.appInfo.uid,
+                    info.launchedActivity.packageName,
+                    convertAppStartTransitionType(type),
+                    info.launchedActivity.info.name,
+                    info.launchedActivity.launchedFromPackage,
+                    isInstantApp,
+                    mCurrentTransitionDeviceUptime * 1000,
+                    info.reason,
+                    mCurrentTransitionDelayMs,
+                    info.startingWindowDelayMs,
+                    info.bindApplicationDelayMs,
+                    info.windowsDrawnDelayMs,
+                    launchToken);
         }
     }
 
+    private int convertAppStartTransitionType(int tronType) {
+        if (tronType == TYPE_TRANSITION_COLD_LAUNCH) {
+            return StatsLog.APP_START_CHANGED__TYPE__COLD;
+        }
+        if (tronType == TYPE_TRANSITION_WARM_LAUNCH) {
+            return StatsLog.APP_START_CHANGED__TYPE__WARM;
+        }
+        if (tronType == TYPE_TRANSITION_HOT_LAUNCH) {
+            return StatsLog.APP_START_CHANGED__TYPE__HOT;
+        }
+        return StatsLog.APP_START_CHANGED__TYPE__APP_START_TRANSITION_TYPE_UNKNOWN;
+     }
+
     void logAppTransitionReportedDrawn(ActivityRecord r, boolean restoredFromBundle) {
         final StackTransitionInfo info = mLastStackTransitionInfo.get(r.getStackId());
         if (info == null) {
@@ -481,14 +516,24 @@
         final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
         builder.setPackageName(r.packageName);
         builder.addTaggedData(FIELD_CLASS_NAME, r.info.name);
-        builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS,
-                SystemClock.uptimeMillis() - mLastTransitionStartTime);
+        long startupTimeMs = SystemClock.uptimeMillis() - mLastTransitionStartTime;
+        builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS, startupTimeMs);
         builder.setType(restoredFromBundle
                 ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
                 : TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE);
         builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING,
                 info.currentTransitionProcessRunning ? 1 : 0);
         mMetricsLogger.write(builder);
+        StatsLog.write(
+                StatsLog.APP_START_FULLY_DRAWN_CHANGED,
+                info.launchedActivity.appInfo.uid,
+                info.launchedActivity.packageName,
+                restoredFromBundle
+                        ? StatsLog.APP_START_FULLY_DRAWN_CHANGED__TYPE__WITH_BUNDLE
+                        : StatsLog.APP_START_FULLY_DRAWN_CHANGED__TYPE__WITHOUT_BUNDLE,
+                info.launchedActivity.info.name,
+                info.currentTransitionProcessRunning,
+                startupTimeMs);
     }
 
     private int getTransitionType(StackTransitionInfo info) {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 69f6d5e..e1907d3 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -23,6 +23,7 @@
 import static android.app.ActivityOptions.ANIM_CUSTOM;
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
@@ -1476,6 +1477,9 @@
                         }
                     }
                     break;
+                case ANIM_OPEN_CROSS_PROFILE_APPS:
+                    service.mWindowManager.overridePendingAppTransitionStartCrossProfileApps();
+                    break;
                 default:
                     Slog.e(TAG, "applyOptionsLocked: Unknown animationType=" + animationType);
                     break;
@@ -2726,12 +2730,14 @@
     }
 
     /**
-     * @return true if the activity contains windows that have
-     *         {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set or if the activity has set
-     *         {@link #mShowWhenLocked}.
+     * @return true if the activity windowing mode is not
+     *         {@link android.app.WindowConfiguration#WINDOWING_MODE_PINNED} and activity contains
+     *         windows that have {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set or if the activity
+     *         has set {@link #mShowWhenLocked}.
+     *         Multi-windowing mode will be exited if true is returned.
      */
     boolean canShowWhenLocked() {
-        return !inMultiWindowMode() && (mShowWhenLocked
+        return !inPinnedWindowingMode() && (mShowWhenLocked
                 || service.mWindowManager.containsShowWhenLockedWindow(appToken));
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 10c801d..831b31e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -16,7 +16,6 @@
 
 package com.android.server.am;
 
-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_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -144,7 +143,6 @@
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerService.ItemMatcher;
-import com.android.server.am.EventLogTags;
 import com.android.server.wm.ConfigurationContainer;
 import com.android.server.wm.StackWindowController;
 import com.android.server.wm.StackWindowListener;
@@ -1013,6 +1011,14 @@
             return;
         }
 
+        /**
+         * The intent behind moving a primary split screen stack to the back is usually to hide
+         * behind the home stack. Exit split screen in this case.
+         */
+        if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        }
+
         getDisplay().positionChildAtBottom(this);
         mStackSupervisor.setFocusStackUnchecked(reason, getDisplay().getTopStack());
         if (task != null) {
@@ -1811,7 +1817,7 @@
             boolean behindFullscreenActivity = !stackShouldBeVisible;
             boolean resumeNextActivity = mStackSupervisor.isFocusedStack(this)
                     && (isInStackLocked(starting) == null);
-            final boolean isTopFullscreenStack = getDisplay().isTopFullscreenStack(this);
+            final boolean isTopNotPinnedStack = getDisplay().isTopNotPinnedStack(this);
             for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
                 final TaskRecord task = mTaskHistory.get(taskNdx);
                 final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1833,7 +1839,7 @@
 
                     // Now check whether it's really visible depending on Keyguard state.
                     final boolean reallyVisible = checkKeyguardVisibility(r,
-                            visibleIgnoringKeyguard, isTop && isTopFullscreenStack);
+                            visibleIgnoringKeyguard, isTop && isTopNotPinnedStack);
                     if (visibleIgnoringKeyguard) {
                         behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible,
                                 behindFullscreenActivity, r);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 0a42aa9..21085fa 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3116,6 +3116,10 @@
         // Need to make sure the pinned stack exist so we can resize it below...
         stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
 
+        // Calculate the target bounds here before the task is reparented back into pinned windowing
+        // mode (which will reset the saved bounds)
+        final Rect destBounds = stack.getDefaultPictureInPictureBounds(aspectRatio);
+
         try {
             final TaskRecord task = r.getTask();
             // Resize the pinned stack to match the current size of the task the activity we are
@@ -3154,11 +3158,6 @@
             mWindowManager.continueSurfaceLayout();
         }
 
-        // Calculate the default bounds (don't use existing stack bounds as we may have just created
-        // the stack, and schedule the start of the animation into PiP (the bounds animator that
-        // is triggered by this is posted on another thread)
-        final Rect destBounds = stack.getDefaultPictureInPictureBounds(aspectRatio);
-
         stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
                 true /* fromFullscreen */);
 
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 4582430..1fcaeef 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -35,6 +35,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.util.function.pooled.PooledLambda;
 
 import libcore.util.EmptyArray;
 
@@ -117,49 +118,45 @@
     }
 
     @Override
-    public Future<?> scheduleReadProcStateCpuTimes() {
+    public synchronized Future<?> scheduleCpuSyncDueToSettingChange() {
+        return scheduleSyncLocked("setting-change", UPDATE_CPU);
+    }
+
+    @Override
+    public Future<?> scheduleReadProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
         synchronized (mStats) {
-            if (!mStats.mPerProcStateCpuTimesAvailable) {
+            if (!mStats.trackPerProcStateCpuTimes()) {
                 return null;
             }
         }
         synchronized (BatteryExternalStatsWorker.this) {
             if (!mExecutorService.isShutdown()) {
-                return mExecutorService.submit(mReadProcStateCpuTimesTask);
+                return mExecutorService.submit(PooledLambda.obtainRunnable(
+                        BatteryStatsImpl::updateProcStateCpuTimes,
+                        mStats, onBattery, onBatteryScreenOff).recycleOnUse());
             }
         }
         return null;
     }
 
     @Override
-    public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+    public Future<?> scheduleCopyFromAllUidsCpuTimes(
+            boolean onBattery, boolean onBatteryScreenOff) {
         synchronized (mStats) {
-            if (!mStats.mPerProcStateCpuTimesAvailable) {
+            if (!mStats.trackPerProcStateCpuTimes()) {
                 return null;
             }
         }
         synchronized (BatteryExternalStatsWorker.this) {
             if (!mExecutorService.isShutdown()) {
-                return mExecutorService.submit(mCopyFromAllUidsCpuTimesTask);
+                return mExecutorService.submit(PooledLambda.obtainRunnable(
+                        BatteryStatsImpl::copyFromAllUidsCpuTimes,
+                        mStats, onBattery, onBatteryScreenOff).recycleOnUse());
             }
         }
         return null;
     }
 
-    private final Runnable mReadProcStateCpuTimesTask = new Runnable() {
-        @Override
-        public void run() {
-            mStats.updateProcStateCpuTimes();
-        }
-    };
-
-    private final Runnable mCopyFromAllUidsCpuTimesTask = new Runnable() {
-        @Override
-        public void run() {
-            mStats.copyFromAllUidsCpuTimes();
-        }
-    };
-
     public synchronized Future<?> scheduleWrite() {
         if (mExecutorService.isShutdown()) {
             return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index b920b57..c9aa9a2 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -38,6 +38,7 @@
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
 import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
 import android.os.connectivity.CellularBatteryStats;
 import android.os.health.HealthStatsParceler;
 import android.os.health.HealthStatsWriter;
@@ -66,6 +67,7 @@
 import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CodingErrorAction;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
@@ -183,6 +185,10 @@
         ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
     }
 
+    public void systemServicesReady() {
+        mStats.systemServicesReady(mContext);
+    }
+
     private final class LocalService extends BatteryStatsInternal {
         @Override
         public String[] getWifiIfaces() {
@@ -446,17 +452,24 @@
         }
     }
 
-    public void noteAlarmStart(String name, int uid) {
+    public void noteWakupAlarm(String name, int uid, WorkSource workSource, String tag) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.noteAlarmStartLocked(name, uid);
+            mStats.noteWakupAlarmLocked(name, uid, workSource, tag);
         }
     }
 
-    public void noteAlarmFinish(String name, int uid) {
+    public void noteAlarmStart(String name, WorkSource workSource, int uid) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.noteAlarmFinishLocked(name, uid);
+            mStats.noteAlarmStartLocked(name, workSource, uid);
+        }
+    }
+
+    public void noteAlarmFinish(String name, WorkSource workSource, int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteAlarmFinishLocked(name, workSource, uid);
         }
     }
 
@@ -464,15 +477,16 @@
             boolean unimportantForLogging) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.noteStartWakeLocked(uid, pid, name, historyName, type, unimportantForLogging,
-                    SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
+            mStats.noteStartWakeLocked(uid, pid, null, name, historyName, type,
+                    unimportantForLogging, SystemClock.elapsedRealtime(),
+                    SystemClock.uptimeMillis());
         }
     }
 
     public void noteStopWakelock(int uid, int pid, String name, String historyName, int type) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.noteStopWakeLocked(uid, pid, name, historyName, type,
+            mStats.noteStopWakeLocked(uid, pid, null, name, historyName, type,
                     SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
         }
     }
@@ -504,6 +518,7 @@
         }
     }
 
+    @Override
     public void noteLongPartialWakelockStart(String name, String historyName, int uid) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -511,6 +526,16 @@
         }
     }
 
+    @Override
+    public void noteLongPartialWakelockStartFromSource(String name, String historyName,
+            WorkSource workSource) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteLongPartialWakelockStartFromSource(name, historyName, workSource);
+        }
+    }
+
+    @Override
     public void noteLongPartialWakelockFinish(String name, String historyName, int uid) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -518,6 +543,15 @@
         }
     }
 
+    @Override
+    public void noteLongPartialWakelockFinishFromSource(String name, String historyName,
+            WorkSource workSource) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteLongPartialWakelockFinishFromSource(name, historyName, workSource);
+        }
+    }
+
     public void noteStartSensor(int uid, int sensor) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -1155,6 +1189,7 @@
         pw.println("  --write: force write current collected stats to disk.");
         pw.println("  --new-daily: immediately create and write new daily stats record.");
         pw.println("  --read-daily: read-load last written daily stats.");
+        pw.println("  --settings: dump the settings key/values related to batterystats");
         pw.println("  <package.name>: optional name of package to filter output by.");
         pw.println("  -h: print this help text.");
         pw.println("Battery stats (batterystats) commands:");
@@ -1167,6 +1202,12 @@
         pw.println("      pretend-screen-off: pretend the screen is off, even if screen state changes");
     }
 
+    private void dumpSettings(PrintWriter pw) {
+        synchronized (mStats) {
+            mStats.dumpConstantsLocked(pw);
+        }
+    }
+
     private int doEnableOrDisable(PrintWriter pw, int i, String[] args, boolean enable) {
         i++;
         if (i >= args.length) {
@@ -1277,6 +1318,9 @@
                 } else if ("-h".equals(arg)) {
                     dumpHelp(pw);
                     return;
+                } else if ("--settings".equals(arg)) {
+                    dumpSettings(pw);
+                    return;
                 } else if ("-a".equals(arg)) {
                     flags |= BatteryStats.DUMP_VERBOSE;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
diff --git a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
new file mode 100644
index 0000000..5632fc0
--- /dev/null
+++ b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Maps global system settings to system properties.
+ * <p>The properties are dynamically updated when settings change.
+ */
+class GlobalSettingsToPropertiesMapper {
+
+    private static final String TAG = "GlobalSettingsToPropertiesMapper";
+
+    private static final String[][] sGlobalSettingsMapping = new String[][] {
+    //  List mapping entries in the following format:
+    //            {Settings.Global.SETTING_NAME, "system_property_name"},
+    };
+
+
+    private final ContentResolver mContentResolver;
+    private final String[][] mGlobalSettingsMapping;
+
+    @VisibleForTesting
+    GlobalSettingsToPropertiesMapper(ContentResolver contentResolver,
+            String[][] globalSettingsMapping) {
+        mContentResolver = contentResolver;
+        mGlobalSettingsMapping = globalSettingsMapping;
+    }
+
+    void updatePropertiesFromGlobalSettings() {
+        for (String[] entry : mGlobalSettingsMapping) {
+            final String settingName = entry[0];
+            final String propName = entry[1];
+            Uri settingUri = Settings.Global.getUriFor(settingName);
+            Preconditions.checkNotNull(settingUri, "Setting " + settingName + " not found");
+            ContentObserver co = new ContentObserver(null) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    updatePropertyFromSetting(settingName, propName);
+                }
+            };
+            updatePropertyFromSetting(settingName, propName);
+            mContentResolver.registerContentObserver(settingUri, false, co);
+        }
+    }
+
+    public static void start(ContentResolver contentResolver) {
+        new GlobalSettingsToPropertiesMapper(contentResolver, sGlobalSettingsMapping)
+                .updatePropertiesFromGlobalSettings();
+    }
+
+    private String getGlobalSetting(String name) {
+        return Settings.Global.getString(mContentResolver, name);
+    }
+
+    private void setProperty(String key, String value) {
+        // Check if need to clear the property
+        if (value == null) {
+            // It's impossible to remove system property, therefore we check previous value to
+            // avoid setting an empty string if the property wasn't set.
+            if (TextUtils.isEmpty(systemPropertiesGet(key))) {
+                return;
+            }
+            value = "";
+        }
+        try {
+            systemPropertiesSet(key, value);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Unable to set property " + key + " value '" + value + "'", e);
+        }
+    }
+
+    @VisibleForTesting
+    protected String systemPropertiesGet(String key) {
+        return SystemProperties.get(key);
+    }
+
+    @VisibleForTesting
+    protected void systemPropertiesSet(String key, String value) {
+        SystemProperties.set(key, value);
+    }
+
+    @VisibleForTesting
+    void updatePropertyFromSetting(String settingName, String propName) {
+        String settingValue = getGlobalSetting(settingName);
+        setProperty(propName, settingValue);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ab5d64c..b39e96d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -432,13 +432,13 @@
     public static final int PSS_MIN_TIME_FROM_STATE_CHANGE = 15*1000;
 
     // The maximum amount of time we want to go between PSS collections.
-    public static final int PSS_MAX_INTERVAL = 30*60*1000;
+    public static final int PSS_MAX_INTERVAL = 40*60*1000;
 
     // The minimum amount of time between successive PSS requests for *all* processes.
-    public static final int PSS_ALL_INTERVAL = 10*60*1000;
+    public static final int PSS_ALL_INTERVAL = 20*60*1000;
 
-    // The minimum amount of time between successive PSS requests for a process.
-    private static final int PSS_SHORT_INTERVAL = 2*60*1000;
+    // The amount of time until PSS when a persistent process first appears.
+    private static final int PSS_FIRST_PERSISTENT_INTERVAL = 30*1000;
 
     // The amount of time until PSS when a process first becomes top.
     private static final int PSS_FIRST_TOP_INTERVAL = 10*1000;
@@ -449,6 +449,9 @@
     // The amount of time until PSS when a process first becomes cached.
     private static final int PSS_FIRST_CACHED_INTERVAL = 30*1000;
 
+    // The amount of time until PSS when the top process stays in the same state.
+    private static final int PSS_SAME_TOP_INTERVAL = 5*60*1000;
+
     // The amount of time until PSS when an important process stays in the same state.
     private static final int PSS_SAME_IMPORTANT_INTERVAL = 15*60*1000;
 
@@ -458,6 +461,18 @@
     // The amount of time until PSS when a cached process stays in the same state.
     private static final int PSS_SAME_CACHED_INTERVAL = 30*60*1000;
 
+    // The amount of time until PSS when a persistent process first appears.
+    private static final int PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL = 1*60*1000;
+
+    // The amount of time until PSS when a process first becomes top.
+    private static final int PSS_FIRST_ASLEEP_TOP_INTERVAL = 20*1000;
+
+    // The amount of time until PSS when a process first goes into the background.
+    private static final int PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL = 30*1000;
+
+    // The amount of time until PSS when a process first becomes cached.
+    private static final int PSS_FIRST_ASLEEP_CACHED_INTERVAL = 1*60*1000;
+
     // The minimum time interval after a state change it is safe to collect PSS.
     public static final int PSS_TEST_MIN_TIME_FROM_STATE_CHANGE = 10*1000;
 
@@ -502,8 +517,8 @@
     };
 
     private static final long[] sFirstAwakePssTimes = new long[] {
-        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_PERSISTENT
-        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_PERSISTENT_UI
+        PSS_FIRST_PERSISTENT_INTERVAL,  // ActivityManager.PROCESS_STATE_PERSISTENT
+        PSS_FIRST_PERSISTENT_INTERVAL,  // ActivityManager.PROCESS_STATE_PERSISTENT_UI
         PSS_FIRST_TOP_INTERVAL,         // ActivityManager.PROCESS_STATE_TOP
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
@@ -526,7 +541,51 @@
     private static final long[] sSameAwakePssTimes = new long[] {
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_PERSISTENT
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_PERSISTENT_UI
-        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_TOP
+        PSS_SAME_TOP_INTERVAL,          // ActivityManager.PROCESS_STATE_TOP
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_BACKUP
+        PSS_SAME_SERVICE_INTERVAL,      // ActivityManager.PROCESS_STATE_SERVICE
+        PSS_SAME_SERVICE_INTERVAL,      // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_HOME
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_RECENT
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+    };
+
+    private static final long[] sFirstAsleepPssTimes = new long[] {
+        PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL,   // ActivityManager.PROCESS_STATE_PERSISTENT
+        PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL,   // ActivityManager.PROCESS_STATE_PERSISTENT_UI
+        PSS_FIRST_ASLEEP_TOP_INTERVAL,          // ActivityManager.PROCESS_STATE_TOP
+        PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL,   // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+        PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL,   // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+        PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL,   // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+        PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL,   // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+        PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL,   // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
+        PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL,   // ActivityManager.PROCESS_STATE_BACKUP
+        PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL,   // ActivityManager.PROCESS_STATE_SERVICE
+        PSS_FIRST_ASLEEP_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_FIRST_ASLEEP_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_FIRST_ASLEEP_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PSS_FIRST_ASLEEP_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_HOME
+        PSS_FIRST_ASLEEP_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
+        PSS_FIRST_ASLEEP_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+        PSS_FIRST_ASLEEP_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_FIRST_ASLEEP_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_RECENT
+        PSS_FIRST_ASLEEP_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+    };
+
+    private static final long[] sSameAsleepPssTimes = new long[] {
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_PERSISTENT
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_PERSISTENT_UI
+        PSS_SAME_TOP_INTERVAL,          // ActivityManager.PROCESS_STATE_TOP
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
@@ -604,8 +663,8 @@
                         ? sTestFirstPssTimes
                         : sTestSamePssTimes)
                 : (first
-                        ? sFirstAwakePssTimes
-                        : sSameAwakePssTimes);
+                        ? (sleeping ? sFirstAsleepPssTimes : sFirstAwakePssTimes)
+                        : (sleeping ? sSameAsleepPssTimes : sSameAwakePssTimes));
         return now + table[procState];
     }
 
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 91b3315..4aef95d 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -772,10 +772,6 @@
         mWindowContainerController.cancelWindowTransition();
     }
 
-    void cancelThumbnailTransition() {
-        mWindowContainerController.cancelThumbnailTransition();
-    }
-
     /**
      * DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
      */
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 14260c5..34621e0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1737,6 +1737,19 @@
         }
     }
 
+    boolean isUserOrItsParentRunning(int userId) {
+        synchronized (mLock) {
+            if (isUserRunning(userId, 0)) {
+                return true;
+            }
+            final int parentUserId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+            if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
+                return false;
+            }
+            return isUserRunning(parentUserId, 0);
+        }
+    }
+
     boolean isCurrentProfile(int userId) {
         synchronized (mLock) {
             return ArrayUtils.contains(mCurrentProfileIds, userId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 2d7a6ad..799f2a9 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2203,7 +2203,7 @@
         return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
     }
 
-    /** @see AudioManager#getStreamMinVolume(int) */
+    /** @see AudioManager#getStreamMinVolumeInt(int) */
     public int getStreamMinVolume(int streamType) {
         ensureValidStreamType(streamType);
         return (mStreamStates[streamType].getMinIndex() + 5) / 10;
@@ -2251,12 +2251,15 @@
         if (DEBUG_VOL) {
             Log.d(TAG, String.format("Mic mute %s, user=%d", on, userId));
         }
-        // If mute is for current user actually mute, else just persist the setting
-        // which will be loaded on user switch.
+        // only mute for the current user
         if (getCurrentUserId() == userId) {
+            final boolean currentMute = AudioSystem.isMicrophoneMuted();
             AudioSystem.muteMicrophone(on);
+            if (on != currentMute) {
+                mContext.sendBroadcast(new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED)
+                        .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY));
+            }
         }
-        // Post a persist microphone msg.
     }
 
     @Override
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index c298fe70..48f0d5a 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -25,6 +25,7 @@
 import android.os.IBinder;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler;
 
 import java.io.PrintWriter;
@@ -300,16 +301,19 @@
     }
 
     /**
-     * Called synchronized on MediaFocusControl.mAudioFocusLock
+     * Handle the loss of focus resulting from a given focus gain.
+     * @param focusGain the focus gain from which the loss of focus is resulting
+     * @param frWinner the new focus owner
+     * @return true if the focus loss is definitive, false otherwise.
      */
-    void handleExternalFocusGain(int focusGain, final FocusRequester fr) {
-        int focusLoss = focusLossForGainRequest(focusGain);
-        handleFocusLoss(focusLoss, fr);
+    @GuardedBy("MediaFocusControl.mAudioFocusLock")
+    boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner) {
+        final int focusLoss = focusLossForGainRequest(focusGain);
+        handleFocusLoss(focusLoss, frWinner);
+        return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);
     }
 
-    /**
-     * Called synchronized on MediaFocusControl.mAudioFocusLock
-     */
+    @GuardedBy("MediaFocusControl.mAudioFocusLock")
     void handleFocusGain(int focusGain) {
         try {
             mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
@@ -331,19 +335,15 @@
         }
     }
 
-    /**
-     * Called synchronized on MediaFocusControl.mAudioFocusLock
-     */
+    @GuardedBy("MediaFocusControl.mAudioFocusLock")
     void handleFocusGainFromRequest(int focusRequestResult) {
         if (focusRequestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
             mFocusController.unduckPlayers(this);
         }
     }
 
-    /**
-     * Called synchronized on MediaFocusControl.mAudioFocusLock
-     */
-    void handleFocusLoss(int focusLoss, @Nullable final FocusRequester fr) {
+    @GuardedBy("MediaFocusControl.mAudioFocusLock")
+    void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner) {
         try {
             if (focusLoss != mFocusLossReceived) {
                 mFocusLossReceived = focusLoss;
@@ -371,9 +371,9 @@
                 boolean handled = false;
                 if (focusLoss == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
                         && MediaFocusControl.ENFORCE_DUCKING
-                        && fr != null) {
+                        && frWinner != null) {
                     // candidate for enforcement by the framework
-                    if (fr.mCallingUid != this.mCallingUid) {
+                    if (frWinner.mCallingUid != this.mCallingUid) {
                         if ((mGrantFlags
                                 & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) {
                             // the focus loser declared it would pause instead of duck, let it
@@ -386,7 +386,7 @@
                             handled = false;
                             Log.v(TAG, "not ducking uid " + this.mCallingUid + " - old SDK");
                         } else {
-                            handled = mFocusController.duckPlayers(fr, this);
+                            handled = mFocusController.duckPlayers(frWinner, this);
                         }
                     } // else: the focus change is within the same app, so let the dispatching
                       //       happen as if the framework was not involved.
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index c5f563c7..de58b59 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -32,11 +32,15 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.Stack;
@@ -146,9 +150,7 @@
         }
     }
 
-    /**
-     * Called synchronized on mAudioFocusLock
-     */
+    @GuardedBy("mAudioFocusLock")
     private void notifyTopOfAudioFocusStack() {
         // notify the top of the stack it gained focus
         if (!mFocusStack.empty()) {
@@ -160,14 +162,24 @@
 
     /**
      * Focus is requested, propagate the associated loss throughout the stack.
+     * Will also remove entries in the stack that have just received a definitive loss of focus.
      * @param focusGain the new focus gain that will later be added at the top of the stack
      */
+    @GuardedBy("mAudioFocusLock")
     private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr) {
+        final List<String> clientsToRemove = new LinkedList<String>();
         // going through the audio focus stack to signal new focus, traversing order doesn't
         // matter as all entries respond to the same external focus gain
-        Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
-        while(stackIterator.hasNext()) {
-            stackIterator.next().handleExternalFocusGain(focusGain, fr);
+        for (FocusRequester focusLoser : mFocusStack) {
+            final boolean isDefinitiveLoss =
+                    focusLoser.handleFocusLossFromGain(focusGain, fr);
+            if (isDefinitiveLoss) {
+                clientsToRemove.add(focusLoser.getClientId());
+            }
+        }
+        for (String clientToRemove : clientsToRemove) {
+            removeFocusStackEntry(clientToRemove, false /*signal*/,
+                    true /*notifyFocusFollowers*/);
         }
     }
 
@@ -198,13 +210,12 @@
     }
 
     /**
-     * Helper function:
-     * Called synchronized on mAudioFocusLock
      * Remove a focus listener from the focus stack.
      * @param clientToRemove the focus listener
      * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding
      *   focus, notify the next item in the stack it gained focus.
      */
+    @GuardedBy("mAudioFocusLock")
     private void removeFocusStackEntry(String clientToRemove, boolean signal,
             boolean notifyFocusFollowers) {
         // is the current top of the focus stack abandoning focus? (because of request, not death)
@@ -242,10 +253,9 @@
     }
 
     /**
-     * Helper function:
-     * Called synchronized on mAudioFocusLock
      * Remove focus listeners from the focus stack for a particular client when it has died.
      */
+    @GuardedBy("mAudioFocusLock")
     private void removeFocusStackEntryOnDeath(IBinder cb) {
         // is the owner of the audio focus part of the client to remove?
         boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
@@ -271,10 +281,10 @@
 
     /**
      * Helper function for external focus policy:
-     * Called synchronized on mAudioFocusLock
      * Remove focus listeners from the list of potential focus owners for a particular client when
      * it has died.
      */
+    @GuardedBy("mAudioFocusLock")
     private void removeFocusEntryForExtPolicy(IBinder cb) {
         if (mFocusOwnersForFocusPolicy.isEmpty()) {
             return;
@@ -324,6 +334,7 @@
      * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
      *     {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
      */
+    @GuardedBy("mAudioFocusLock")
     private int pushBelowLockedFocusOwners(FocusRequester nfr) {
         int lastLockedFocusOwnerIndex = mFocusStack.size();
         for (int index = mFocusStack.size()-1; index >= 0; index--) {
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 3064144..7d3b670 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -25,6 +25,7 @@
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
 import android.os.ParcelableException;
+import android.util.Slog;
 
 import com.android.server.SystemService;
 
@@ -33,6 +34,8 @@
 import java.util.OptionalInt;
 
 public class BroadcastRadioService extends SystemService {
+    private static final String TAG = "BcRadioSrv";
+
     private final ServiceImpl mServiceImpl = new ServiceImpl();
 
     private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1 =
@@ -84,13 +87,14 @@
         @Override
         public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
                 boolean withAudio, ITunerCallback callback) {
+            Slog.i(TAG, "openTuner(" + moduleId + ", _, " + withAudio + ", _)");
             enforcePolicyAccess();
             if (callback == null) {
                 throw new IllegalArgumentException("Callback must not be empty");
             }
             synchronized (mLock) {
                 if (mHal2.hasModule(moduleId)) {
-                    throw new RuntimeException("Not implemented");
+                    return mHal2.openSession(moduleId, bandConfig, withAudio, callback);
                 } else {
                     return mHal1.openTuner(moduleId, bandConfig, withAudio, callback);
                 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index cce534d..e5090ed 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -263,19 +263,31 @@
     }
 
     @Override
-    public boolean isAnalogForced() {
-        synchronized (mLock) {
-            checkNotClosedLocked();
-            return nativeIsAnalogForced(mNativeContext);
-        }
+    public boolean isConfigFlagSupported(int flag) {
+        return flag == RadioManager.CONFIG_FORCE_ANALOG;
     }
 
     @Override
-    public void setAnalogForced(boolean isForced) {
-        synchronized (mLock) {
-            checkNotClosedLocked();
-            nativeSetAnalogForced(mNativeContext, isForced);
+    public boolean isConfigFlagSet(int flag) {
+        if (flag == RadioManager.CONFIG_FORCE_ANALOG) {
+            synchronized (mLock) {
+                checkNotClosedLocked();
+                return nativeIsAnalogForced(mNativeContext);
+            }
         }
+        throw new UnsupportedOperationException("Not supported by HAL 1.x");
+    }
+
+    @Override
+    public void setConfigFlag(int flag, boolean value) {
+        if (flag == RadioManager.CONFIG_FORCE_ANALOG) {
+            synchronized (mLock) {
+                checkNotClosedLocked();
+                nativeSetAnalogForced(mNativeContext, value);
+                return;
+            }
+        }
+        throw new UnsupportedOperationException("Not supported by HAL 1.x");
     }
 
     @Override
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 7629477..9158ff0 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -17,6 +17,9 @@
 package com.android.server.broadcastradio.hal2;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
 import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
 import android.hidl.manager.V1_0.IServiceManager;
@@ -28,6 +31,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 public class BroadcastRadioService {
@@ -76,4 +80,22 @@
     public boolean hasModule(int id) {
         return mModules.containsKey(id);
     }
+
+    public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
+        boolean withAudio, @NonNull ITunerCallback callback) {
+        Objects.requireNonNull(callback);
+
+        if (!withAudio) {
+            throw new IllegalArgumentException("Non-audio sessions not supported with HAL 2.x");
+        }
+
+        RadioModule module = mModules.get(moduleId);
+        if (module == null) {
+            throw new IllegalArgumentException("Invalid module ID");
+        }
+
+        TunerSession session = module.openSession(callback);
+        session.setConfiguration(legacyConfig);
+        return session;
+    }
 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index c3394e9..2c129bb 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -18,12 +18,17 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.broadcastradio.V2_0.AmFmBandRange;
+import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
 import android.hardware.broadcastradio.V2_0.Properties;
+import android.hardware.broadcastradio.V2_0.Result;
 import android.hardware.broadcastradio.V2_0.VendorKeyValue;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
+import android.os.ParcelableException;
 import android.util.Slog;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -35,6 +40,28 @@
 class Convert {
     private static final String TAG = "BcRadio2Srv.convert";
 
+    static void throwOnError(String action, int result) {
+        switch (result) {
+            case Result.OK:
+                return;
+            case Result.UNKNOWN_ERROR:
+                throw new ParcelableException(new RuntimeException(action + ": UNKNOWN_ERROR"));
+            case Result.INTERNAL_ERROR:
+                throw new ParcelableException(new RuntimeException(action + ": INTERNAL_ERROR"));
+            case Result.INVALID_ARGUMENTS:
+                throw new IllegalArgumentException(action + ": INVALID_ARGUMENTS");
+            case Result.INVALID_STATE:
+                throw new IllegalStateException(action + ": INVALID_STATE");
+            case Result.NOT_SUPPORTED:
+                throw new UnsupportedOperationException(action + ": NOT_SUPPORTED");
+            case Result.TIMEOUT:
+                throw new ParcelableException(new RuntimeException(action + ": TIMEOUT"));
+            default:
+                throw new ParcelableException(new RuntimeException(
+                        action + ": unknown error (" + result + ")"));
+        }
+    }
+
     private static @NonNull Map<String, String>
     vendorInfoFromHal(@Nullable List<VendorKeyValue> info) {
         if (info == null) return Collections.emptyMap();
@@ -94,12 +121,47 @@
         return pTypes.stream().mapToInt(Integer::intValue).toArray();
     }
 
-    static @NonNull RadioManager.ModuleProperties
-    propertiesFromHal(int id, @NonNull String serviceName, Properties prop) {
-        Objects.requireNonNull(prop);
+    private static @NonNull RadioManager.BandDescriptor[]
+    amfmConfigToBands(@Nullable AmFmRegionConfig config) {
+        if (config == null) return new RadioManager.BandDescriptor[0];
 
-        // TODO(b/69958423): implement region info
-        RadioManager.BandDescriptor[] bands = new RadioManager.BandDescriptor[0];
+        int len = config.ranges.size();
+        List<RadioManager.BandDescriptor> bands = new ArrayList<>(len);
+
+        // Just a dummy value.
+        int region = RadioManager.REGION_ITU_1;
+
+        for (AmFmBandRange range : config.ranges) {
+            FrequencyBand bandType = Utils.getBand(range.lowerBound);
+            if (bandType == FrequencyBand.UNKNOWN) {
+                Slog.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz");
+                continue;
+            }
+            if (bandType == FrequencyBand.FM) {
+                bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM,
+                    range.lowerBound, range.upperBound, range.spacing,
+
+                    // TODO(b/69958777): stereo, rds, ta, af, ea
+                    true, true, true, true, true
+                ));
+            } else {  // AM
+                bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM,
+                    range.lowerBound, range.upperBound, range.spacing,
+
+                    // TODO(b/69958777): stereo
+                    true
+                ));
+            }
+        }
+
+        return bands.toArray(new RadioManager.BandDescriptor[bands.size()]);
+    }
+
+    static @NonNull RadioManager.ModuleProperties
+    propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop,
+            @Nullable AmFmRegionConfig amfmConfig) {
+        Objects.requireNonNull(serviceName);
+        Objects.requireNonNull(prop);
 
         int[] supportedIdentifierTypes = prop.supportedIdentifierTypes.stream().
                 mapToInt(Integer::intValue).toArray();
@@ -123,8 +185,8 @@
                 1,      // numAudioSources
                 false,  // isCaptureSupported
 
-                bands,
-                true,  // isBgScanSupported is deprecated
+                amfmConfigToBands(amfmConfig),
+                false,  // isBgScanSupported is deprecated
                 supportedProgramTypes,
                 supportedIdentifierTypes,
                 vendorInfoFromHal(prop.vendorInfo));
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
new file mode 100644
index 0000000..a9d8054
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.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 com.android.server.broadcastradio.hal2;
+
+/**
+ * A wrapper class for mutable objects to be used in non-mutable contexts
+ * (i.e. final variables catched in lambda closures).
+ *
+ * @param <E> type of boxed value.
+ */
+final class Mutable<E> {
+    /**
+     * A mutable value.
+     */
+    public E value;
+
+    /**
+     * Initialize value with null pointer.
+     */
+    public Mutable() {
+        value = null;
+    }
+
+    /**
+     * Initialize value with specific value.
+     *
+     * @param value initial value.
+     */
+    public Mutable(E value) {
+        this.value = value;
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 34c1b0c..45b2190 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -18,9 +18,15 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.radio.ITuner;
 import android.hardware.radio.RadioManager;
+import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
 import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.broadcastradio.V2_0.ITunerSession;
+import android.hardware.broadcastradio.V2_0.Result;
+import android.os.ParcelableException;
 import android.os.RemoteException;
+import android.util.MutableInt;
 import android.util.Slog;
 
 import java.util.Objects;
@@ -42,8 +48,13 @@
             IBroadcastRadio service = IBroadcastRadio.getService();
             if (service == null) return null;
 
+            Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>();
+            service.getAmFmRegionConfig(false, (int result, AmFmRegionConfig config) -> {
+                if (result == Result.OK) amfmConfig.value = config;
+            });
+
             RadioManager.ModuleProperties prop =
-                    Convert.propertiesFromHal(idx, fqName, service.getProperties());
+                    Convert.propertiesFromHal(idx, fqName, service.getProperties(), amfmConfig.value);
 
             return new RadioModule(service, prop);
         } catch (RemoteException ex) {
@@ -51,4 +62,25 @@
             return null;
         }
     }
+
+    public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) {
+        TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb));
+        Mutable<ITunerSession> hwSession = new Mutable<>();
+        MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
+
+        try {
+            mService.openSession(cb, (int result, ITunerSession session) -> {
+                hwSession.value = session;
+                halResult.value = result;
+            });
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "failed to open session", ex);
+            throw new ParcelableException(ex);
+        }
+
+        Convert.throwOnError("openSession", halResult.value);
+        Objects.requireNonNull(hwSession.value);
+
+        return new TunerSession(hwSession.value, cb);
+    }
 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
new file mode 100644
index 0000000..c9084ee
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
@@ -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.
+ */
+
+package com.android.server.broadcastradio.hal2;
+
+import android.annotation.NonNull;
+import android.hardware.broadcastradio.V2_0.ITunerCallback;
+import android.hardware.broadcastradio.V2_0.ProgramInfo;
+import android.hardware.broadcastradio.V2_0.ProgramListChunk;
+import android.hardware.broadcastradio.V2_0.ProgramSelector;
+import android.hardware.broadcastradio.V2_0.VendorKeyValue;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+class TunerCallback extends ITunerCallback.Stub {
+    private static final String TAG = "BcRadio2Srv.cb";
+
+    final android.hardware.radio.ITunerCallback mClientCb;
+
+    interface RunnableThrowingRemoteException {
+        void run() throws RemoteException;
+    }
+
+    TunerCallback(@NonNull android.hardware.radio.ITunerCallback clientCallback) {
+        mClientCb = Objects.requireNonNull(clientCallback);
+    }
+
+    static void dispatch(RunnableThrowingRemoteException func) {
+        try {
+            func.run();
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "callback call failed", ex);
+        }
+    }
+
+    @Override
+    public void onTuneFailed(int result, ProgramSelector selector) {}
+
+    @Override
+    public void onCurrentProgramInfoChanged(ProgramInfo info) {}
+
+    @Override
+    public void onProgramListUpdated(ProgramListChunk chunk) {}
+
+    @Override
+    public void onAntennaStateChange(boolean connected) {}
+
+    @Override
+    public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {}
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
new file mode 100644
index 0000000..8ed646a
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -0,0 +1,267 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.broadcastradio.hal2;
+
+import android.annotation.NonNull;
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.V2_0.ConfigFlag;
+import android.hardware.broadcastradio.V2_0.ITunerSession;
+import android.hardware.broadcastradio.V2_0.Result;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.media.AudioSystem;
+import android.os.RemoteException;
+import android.util.MutableBoolean;
+import android.util.MutableInt;
+import android.util.Slog;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+class TunerSession extends ITuner.Stub {
+    private static final String TAG = "BcRadio2Srv.session";
+    private static final String kAudioDeviceName = "Radio tuner source";
+
+    private final Object mLock = new Object();
+
+    private final ITunerSession mHwSession;
+    private final TunerCallback mCallback;
+    private boolean mIsClosed = false;
+    private boolean mIsAudioConnected = false;
+    private boolean mIsMuted = false;
+
+    // necessary only for older APIs compatibility
+    private RadioManager.BandConfig mDummyConfig = null;
+
+    TunerSession(@NonNull ITunerSession hwSession, @NonNull TunerCallback callback) {
+        mHwSession = Objects.requireNonNull(hwSession);
+        mCallback = Objects.requireNonNull(callback);
+        notifyAudioServiceLocked(true);
+    }
+
+    @Override
+    public void close() {
+        synchronized (mLock) {
+            if (mIsClosed) return;
+            mIsClosed = true;
+            notifyAudioServiceLocked(false);
+        }
+    }
+
+    @Override
+    public boolean isClosed() {
+        return mIsClosed;
+    }
+
+    private void checkNotClosedLocked() {
+        if (mIsClosed) {
+            throw new IllegalStateException("Tuner is closed, no further operations are allowed");
+        }
+    }
+
+    private void notifyAudioServiceLocked(boolean connected) {
+        if (mIsAudioConnected == connected) return;
+
+        Slog.d(TAG, "Notifying AudioService about new state: " + connected);
+        int ret = AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_FM_TUNER,
+            connected ? AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE,
+            null, kAudioDeviceName);
+
+        if (ret == AudioSystem.AUDIO_STATUS_OK) {
+            mIsAudioConnected = connected;
+        } else {
+            Slog.e(TAG, "Failed to notify AudioService about new state: " + connected);
+        }
+    }
+
+    @Override
+    public void setConfiguration(RadioManager.BandConfig config) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            mDummyConfig = Objects.requireNonNull(config);
+            Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.x");
+            TunerCallback.dispatch(() -> mCallback.mClientCb.onConfigurationChanged(config));
+        }
+    }
+
+    @Override
+    public RadioManager.BandConfig getConfiguration() {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            return mDummyConfig;
+        }
+    }
+
+    @Override
+    public void setMuted(boolean mute) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            if (mIsMuted == mute) return;
+            mIsMuted = mute;
+            notifyAudioServiceLocked(!mute);
+        }
+    }
+
+    @Override
+    public boolean isMuted() {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            return mIsMuted;
+        }
+    }
+
+    @Override
+    public void step(boolean directionDown, boolean skipSubChannel) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+        }
+    }
+
+    @Override
+    public void scan(boolean directionDown, boolean skipSubChannel) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+        }
+    }
+
+    @Override
+    public void tune(ProgramSelector selector) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+        }
+    }
+
+    @Override
+    public void cancel() {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+        }
+    }
+
+    @Override
+    public void cancelAnnouncement() {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+        }
+    }
+
+    @Override
+    public RadioManager.ProgramInfo getProgramInformation() {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            return null;
+        }
+    }
+
+    @Override
+    public Bitmap getImage(int id) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            return null;
+        }
+    }
+
+    @Override
+    public boolean startBackgroundScan() {
+        Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.x");
+        return false;
+    }
+
+    @Override
+    public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            return null;
+        }
+    }
+
+    @Override
+    public boolean isConfigFlagSupported(int flag) {
+        try {
+            isConfigFlagSet(flag);
+            return true;
+        } catch (IllegalStateException ex) {
+            return true;
+        } catch (UnsupportedOperationException ex) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isConfigFlagSet(int flag) {
+        Slog.v(TAG, "isConfigFlagSet " + ConfigFlag.toString(flag));
+        synchronized (mLock) {
+            checkNotClosedLocked();
+
+            MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
+            MutableBoolean flagState = new MutableBoolean(false);
+            try {
+                mHwSession.getConfigFlag(flag, (int result, boolean value) -> {
+                    halResult.value = result;
+                    flagState.value = value;
+                });
+            } catch (RemoteException ex) {
+                throw new RuntimeException("Failed to check flag " + ConfigFlag.toString(flag), ex);
+            }
+            Convert.throwOnError("isConfigFlagSet", halResult.value);
+
+            return flagState.value;
+        }
+    }
+
+    @Override
+    public void setConfigFlag(int flag, boolean value) {
+        Slog.v(TAG, "setConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
+        synchronized (mLock) {
+            checkNotClosedLocked();
+
+            int halResult;
+            try {
+                halResult = mHwSession.setConfigFlag(flag, value);
+            } catch (RemoteException ex) {
+                throw new RuntimeException("Failed to set flag " + ConfigFlag.toString(flag), ex);
+            }
+            Convert.throwOnError("setConfigFlag", halResult);
+        }
+    }
+
+    @Override
+    public Map setParameters(Map parameters) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            return null;
+        }
+    }
+
+    @Override
+    public Map getParameters(List<String> keys) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            return null;
+        }
+    }
+
+    @Override
+    public boolean isAntennaConnected() {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            return true;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
new file mode 100644
index 0000000..3520f37
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.broadcastradio.hal2;
+
+enum FrequencyBand {
+    UNKNOWN,
+    FM,
+    AM_LW,
+    AM_MW,
+    AM_SW,
+};
+
+class Utils {
+    private static final String TAG = "BcRadio2Srv.utils";
+
+    static FrequencyBand getBand(int freq) {
+        // keep in sync with hardware/interfaces/broadcastradio/common/utils2x/Utils.cpp
+        if (freq < 30) return FrequencyBand.UNKNOWN;
+        if (freq < 500) return FrequencyBand.AM_LW;
+        if (freq < 1705) return FrequencyBand.AM_MW;
+        if (freq < 30000) return FrequencyBand.AM_SW;
+        if (freq < 60000) return FrequencyBand.UNKNOWN;
+        if (freq < 110000) return FrequencyBand.FM;
+        return FrequencyBand.UNKNOWN;
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 7715727..c7a4315 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -305,6 +305,7 @@
         } else {
             for (Network underlying : underlyingNetworks) {
                 final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying);
+                if (underlyingCaps == null) continue;
                 for (int underlyingType : underlyingCaps.getTransportTypes()) {
                     transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType);
                 }
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index 29b322e..d957ca0 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -25,6 +25,9 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
 
 public class SyncJobService extends JobService {
     private static final String TAG = "SyncManager";
@@ -32,7 +35,14 @@
     public static final String EXTRA_MESSENGER = "messenger";
 
     private Messenger mMessenger;
-    private SparseArray<JobParameters> jobParamsMap = new SparseArray<JobParameters>();
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final SparseArray<JobParameters> mJobParamsMap = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mStartedSyncs = new SparseBooleanArray();
 
     private final SyncLogger mLogger = SyncLogger.getInstance();
 
@@ -69,8 +79,10 @@
         mLogger.purgeOldLogs();
 
         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-        synchronized (jobParamsMap) {
-            jobParamsMap.put(params.getJobId(), params);
+        synchronized (mLock) {
+            final int jobId = params.getJobId();
+            mJobParamsMap.put(jobId, params);
+            mStartedSyncs.delete(jobId);
         }
         Message m = Message.obtain();
         m.what = SyncManager.SyncHandler.MESSAGE_START_SYNC;
@@ -97,8 +109,18 @@
                     + params.getStopReason());
         }
         mLogger.log("onStopJob() ", mLogger.jobParametersToString(params));
-        synchronized (jobParamsMap) {
-            jobParamsMap.remove(params.getJobId());
+        synchronized (mLock) {
+            final int jobId = params.getJobId();
+            mJobParamsMap.remove(jobId);
+
+            if (!mStartedSyncs.get(jobId)) {
+                final String message = "Job " + jobId + " didn't start: params=" +
+                        jobParametersToString(params);
+                mLogger.log(message);
+                Slog.wtf(TAG, message);
+            }
+
+            mStartedSyncs.delete(jobId);
         }
         Message m = Message.obtain();
         m.what = SyncManager.SyncHandler.MESSAGE_STOP_SYNC;
@@ -117,8 +139,8 @@
     }
 
     public void callJobFinished(int jobId, boolean needsReschedule, String why) {
-        synchronized (jobParamsMap) {
-            JobParameters params = jobParamsMap.get(jobId);
+        synchronized (mLock) {
+            JobParameters params = mJobParamsMap.get(jobId);
             mLogger.log("callJobFinished()",
                     " jobid=", jobId,
                     " needsReschedule=", needsReschedule,
@@ -126,10 +148,25 @@
                     " why=", why);
             if (params != null) {
                 jobFinished(params, needsReschedule);
-                jobParamsMap.remove(jobId);
+                mJobParamsMap.remove(jobId);
             } else {
                 Slog.e(TAG, "Job params not found for " + String.valueOf(jobId));
             }
         }
     }
+
+    public void markSyncStarted(int jobId) {
+        synchronized (mLock) {
+            mStartedSyncs.put(jobId, true);
+        }
+    }
+
+    public static String jobParametersToString(JobParameters params) {
+        if (params == null) {
+            return "job:null";
+        } else {
+            return "job:#" + params.getJobId() + ":"
+                    + SyncOperation.maybeCreateFromJobExtras(params.getExtras());
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/content/SyncLogger.java b/services/core/java/com/android/server/content/SyncLogger.java
index 8503768..75c0181 100644
--- a/services/core/java/com/android/server/content/SyncLogger.java
+++ b/services/core/java/com/android/server/content/SyncLogger.java
@@ -233,12 +233,7 @@
 
         @Override
         public String jobParametersToString(JobParameters params) {
-            if (params == null) {
-                return "job:null";
-            } else {
-                return "job:#" + params.getJobId() + ":"
-                        + SyncOperation.maybeCreateFromJobExtras(params.getExtras());
-            }
+            return SyncJobService.jobParametersToString(params);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 965159b..ad2cf6c 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2898,6 +2898,8 @@
             final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
             if (isLoggable) Slog.v(TAG, op.toString());
 
+            mSyncJobService.markSyncStarted(op.jobId);
+
             if (mStorageIsLow) {
                 deferSyncH(op, SYNC_DELAY_ON_LOW_STORAGE, "storage low");
                 return;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 3b9d40f..ac0e1b5 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -17,6 +17,8 @@
 package com.android.server.display;
 
 import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.hardware.display.BrightnessConfiguration;
 import android.os.PowerManager;
 import android.util.MathUtils;
@@ -41,11 +43,30 @@
     private static final boolean DEBUG = false;
 
     @Nullable
-    public static BrightnessMappingStrategy create(
-            float[] luxLevels, int[] brightnessLevelsBacklight, float[] brightnessLevelsNits,
-            float[] nitsRange, int[] backlightRange) {
+    public static BrightnessMappingStrategy create(Resources resources) {
+        float[] luxLevels = getLuxLevels(resources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLevels));
+        int[] brightnessLevelsBacklight = resources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
+        float[] brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
+
+        float[] nitsRange = getFloatArray(resources.obtainTypedArray(
+                com.android.internal.R.array.config_screenBrightnessNits));
+        int[] backlightRange = resources.getIntArray(
+                com.android.internal.R.array.config_screenBrightnessBacklight);
+
         if (isValidMapping(nitsRange, backlightRange)
                 && isValidMapping(luxLevels, brightnessLevelsNits)) {
+            int minimumBacklight = resources.getInteger(
+                    com.android.internal.R.integer.config_screenBrightnessSettingMinimum);
+            int maximumBacklight = resources.getInteger(
+                    com.android.internal.R.integer.config_screenBrightnessSettingMaximum);
+            if (backlightRange[0] > minimumBacklight
+                    || backlightRange[backlightRange.length - 1] < maximumBacklight) {
+                Slog.w(TAG, "Screen brightness mapping does not cover whole range of available"
+                        + " backlight values, autobrightness functionality may be impaired.");
+            }
             BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder();
             builder.setCurve(luxLevels, brightnessLevelsNits);
             return new PhysicalMappingStrategy(builder.build(), nitsRange, backlightRange);
@@ -56,6 +77,25 @@
         }
     }
 
+    private static float[] getLuxLevels(int[] lux) {
+        // The first control point is implicit and always at 0 lux.
+        float[] levels = new float[lux.length + 1];
+        for (int i = 0; i < lux.length; i++) {
+            levels[i + 1] = (float) lux[i];
+        }
+        return levels;
+    }
+
+    private static float[] getFloatArray(TypedArray array) {
+        final int N = array.length();
+        float[] vals = new float[N];
+        for (int i = 0; i < N; i++) {
+            vals[i] = array.getFloat(i, -1.0f);
+        }
+        array.recycle();
+        return vals;
+    }
+
     private static boolean isValidMapping(float[] x, float[] y) {
         if (x == null || y == null || x.length == 0 || y.length == 0) {
             return false;
@@ -124,10 +164,17 @@
      * brightness and 0 is the display at minimum brightness.
      *
      * @param lux The current ambient brightness in lux.
-     * @return The desired brightness of the display compressed to the range [0, 1.0].
+     * @return The desired brightness of the display normalized to the range [0, 1.0].
      */
     public abstract float getBrightness(float lux);
 
+    /**
+     * Gets the display's brightness in nits for the given backlight value.
+     *
+     * Returns -1.0f if there's no available mapping for the backlight to nits.
+     */
+    public abstract float getNits(int backlight);
+
     public abstract void dump(PrintWriter pw);
 
     private static float normalizeAbsoluteBrightness(int brightness) {
@@ -175,8 +222,6 @@
 
         @Override
         public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
-            Slog.e(TAG,
-                    "setBrightnessConfiguration called on device without display information.");
             return false;
         }
 
@@ -186,6 +231,11 @@
         }
 
         @Override
+        public float getNits(int backlight) {
+            return -1.0f;
+        }
+
+        @Override
         public void dump(PrintWriter pw) {
             pw.println("SimpleMappingStrategy");
             pw.println("  mSpline=" + mSpline);
@@ -209,7 +259,11 @@
 
         // A spline mapping from nits to the corresponding backlight value, normalized to the range
         // [0, 1.0].
-        private final Spline mBacklightSpline;
+        private final Spline mNitsToBacklightSpline;
+
+        // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to
+        // a brightness in nits.
+        private final Spline mBacklightToNitsSpline;
 
         // The default brightness configuration.
         private final BrightnessConfiguration mDefaultConfig;
@@ -227,19 +281,18 @@
 
             // Setup the backlight spline
             final int N = nits.length;
-            float[] x = new float[N];
-            float[] y = new float[N];
+            float[] normalizedBacklight = new float[N];
             for (int i = 0; i < N; i++) {
-                x[i] = nits[i];
-                y[i] = normalizeAbsoluteBrightness(backlight[i]);
+                normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]);
             }
 
-            mBacklightSpline = Spline.createSpline(x, y);
+            mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight);
+            mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits);
             if (DEBUG) {
-                Slog.d(TAG, "Backlight spline: " + mBacklightSpline);
+                Slog.d(TAG, "Backlight spline: " + mNitsToBacklightSpline);
                 for (float v = 1f; v < nits[nits.length - 1] * 1.25f; v *= 1.25f) {
                     Slog.d(TAG, String.format(
-                                "  %7.1f: %7.1f", v, mBacklightSpline.interpolate(v)));
+                                "  %7.1f: %7.1f", v, mNitsToBacklightSpline.interpolate(v)));
                 }
             }
 
@@ -275,7 +328,12 @@
 
         @Override
         public float getBrightness(float lux) {
-            return mBacklightSpline.interpolate(mBrightnessSpline.interpolate(lux));
+            return mNitsToBacklightSpline.interpolate(mBrightnessSpline.interpolate(lux));
+        }
+
+        @Override
+        public float getNits(int backlight) {
+            return mBacklightToNitsSpline.interpolate(normalizeAbsoluteBrightness(backlight));
         }
 
         @Override
@@ -283,7 +341,7 @@
             pw.println("PhysicalMappingStrategy");
             pw.println("  mConfig=" + mConfig);
             pw.println("  mBrightnessSpline=" + mBrightnessSpline);
-            pw.println("  mBacklightSpline=" + mBacklightSpline);
+            pw.println("  mNitsToBacklightSpline=" + mNitsToBacklightSpline);
         }
     }
 }
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 42247f9..cbb1c01 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -24,7 +24,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ParceledListSlice;
-import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
@@ -34,6 +33,8 @@
 import android.os.BatteryManager;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -88,7 +89,7 @@
 
     private static final String TAG_EVENTS = "events";
     private static final String TAG_EVENT = "event";
-    private static final String ATTR_BRIGHTNESS = "brightness";
+    private static final String ATTR_NITS = "nits";
     private static final String ATTR_TIMESTAMP = "timestamp";
     private static final String ATTR_PACKAGE_NAME = "packageName";
     private static final String ATTR_USER = "user";
@@ -97,7 +98,10 @@
     private static final String ATTR_BATTERY_LEVEL = "batteryLevel";
     private static final String ATTR_NIGHT_MODE = "nightMode";
     private static final String ATTR_COLOR_TEMPERATURE = "colorTemperature";
-    private static final String ATTR_LAST_BRIGHTNESS = "lastBrightness";
+    private static final String ATTR_LAST_NITS = "lastNits";
+
+    private static final int MSG_BACKGROUND_START = 0;
+    private static final int MSG_BRIGHTNESS_CHANGED = 1;
 
     // Lock held while accessing mEvents, is held while writing events to flash.
     private final Object mEventsLock = new Object();
@@ -113,9 +117,7 @@
     private final Context mContext;
     private final ContentResolver mContentResolver;
     private Handler mBgHandler;
-    // mSettingsObserver, mBroadcastReceiver and mSensorListener should only be used on
-    // the mBgHandler thread.
-    private SettingsObserver mSettingsObserver;
+    // mBroadcastReceiver and mSensorListener should only be used on the mBgHandler thread.
     private BroadcastReceiver mBroadcastReceiver;
     private SensorListener mSensorListener;
 
@@ -126,9 +128,9 @@
     @GuardedBy("mDataCollectionLock")
     private float mLastBatteryLevel = Float.NaN;
     @GuardedBy("mDataCollectionLock")
-    private int mIgnoreBrightness = -1;
+    private float mLastBrightness = -1;
     @GuardedBy("mDataCollectionLock")
-    private int mLastBrightness = -1;
+    private boolean mStarted;
 
     private final Injector mInjector;
 
@@ -144,33 +146,31 @@
         }
     }
 
-    /** Start listening for brightness slider events */
-    public void start() {
+    /**
+     * Start listening for brightness slider events
+     *
+     * @param brightness the initial screen brightness
+     */
+    public void start(float initialBrightness) {
         if (DEBUG) {
             Slog.d(TAG, "Start");
         }
-        mBgHandler = mInjector.getBackgroundHandler();
+        mBgHandler = new TrackerHandler(mInjector.getBackgroundHandler().getLooper());
         mUserManager = mContext.getSystemService(UserManager.class);
 
-        mBgHandler.post(() -> backgroundStart());
+        mBgHandler.obtainMessage(MSG_BACKGROUND_START, (Float) initialBrightness).sendToTarget();
     }
 
-    private void backgroundStart() {
+    private void backgroundStart(float initialBrightness) {
         readEvents();
 
-        mLastBrightness = mInjector.getSystemIntForUser(mContentResolver,
-                Settings.System.SCREEN_BRIGHTNESS, -1,
-                UserHandle.USER_CURRENT);
-
         mSensorListener = new SensorListener();
 
+
         if (mInjector.isInteractive(mContext)) {
             mInjector.registerSensorListener(mContext, mSensorListener, mBgHandler);
         }
 
-        mSettingsObserver = new SettingsObserver(mBgHandler);
-        mInjector.registerBrightnessObserver(mContentResolver, mSettingsObserver);
-
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_SHUTDOWN);
         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
@@ -180,6 +180,10 @@
         mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
 
         mInjector.scheduleIdleJob(mContext);
+        synchronized (mDataCollectionLock) {
+            mLastBrightness = initialBrightness;
+            mStarted = true;
+        }
     }
 
     /** Stop listening for events */
@@ -188,10 +192,14 @@
         if (DEBUG) {
             Slog.d(TAG, "Stop");
         }
+        mBgHandler.removeMessages(MSG_BACKGROUND_START);
         mInjector.unregisterSensorListener(mContext, mSensorListener);
         mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
-        mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
         mInjector.cancelIdleJob(mContext);
+
+        synchronized (mDataCollectionLock) {
+            mStarted = false;
+        }
     }
 
     /**
@@ -220,40 +228,45 @@
         return new ParceledListSlice<>(out);
     }
 
-    /** Sets brightness without logging the brightness change event */
-    public void setBrightness(int brightness, int userId) {
-        synchronized (mDataCollectionLock) {
-            mIgnoreBrightness = brightness;
-        }
-        mInjector.putSystemIntForUser(mContentResolver, Settings.System.SCREEN_BRIGHTNESS,
-                brightness, userId);
-    }
-
     public void persistEvents() {
         scheduleWriteEvents();
     }
 
-    private void handleBrightnessChanged() {
+    /**
+     * Notify the BrightnessTracker that the user has changed the brightness of the display.
+     */
+    public void notifyBrightnessChanged(float brightness, boolean userInitiated) {
         if (DEBUG) {
-            Slog.d(TAG, "Brightness change");
+            Slog.d(TAG, String.format("notifyBrightnessChanged(brightness=%f, userInitiated=%b)",
+                        brightness, userInitiated));
         }
-        final BrightnessChangeEvent event = new BrightnessChangeEvent();
-        event.timeStamp = mInjector.currentTimeMillis();
+        Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED,
+                userInitiated ? 1 : 0, 0 /*unused*/, (Float) brightness);
+        m.sendToTarget();
+    }
 
-        int brightness = mInjector.getSystemIntForUser(mContentResolver,
-                Settings.System.SCREEN_BRIGHTNESS, -1,
-                UserHandle.USER_CURRENT);
-
+    private void handleBrightnessChanged(float brightness, boolean userInitiated) {
+        final BrightnessChangeEvent event;
         synchronized (mDataCollectionLock) {
-            int previousBrightness = mLastBrightness;
-            mLastBrightness = brightness;
-
-            if (brightness == -1 || brightness == mIgnoreBrightness) {
-                // Notified of brightness change but no setting or self change so ignore.
-                mIgnoreBrightness = -1;
+            if (!mStarted) {
+                // Not currently gathering brightness change information
                 return;
             }
 
+            float previousBrightness = mLastBrightness;
+            mLastBrightness = brightness;
+
+            if (!userInitiated) {
+                // We want to record what current brightness is so that we know what the user
+                // changed it from, but if it wasn't user initiated then we don't want to record it
+                // as a BrightnessChangeEvent.
+                return;
+            }
+
+
+            event = new BrightnessChangeEvent();
+            event.timeStamp = mInjector.currentTimeMillis();
+
             final int readingCount = mLastSensorReadings.size();
             if (readingCount == 0) {
                 // No sensor data so ignore this.
@@ -386,7 +399,7 @@
             if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) {
                 mEvents.append(toWrite[i]);
                 out.startTag(null, TAG_EVENT);
-                out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness));
+                out.attribute(null, ATTR_NITS, Float.toString(toWrite[i].brightness));
                 out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
                 out.attribute(null, ATTR_PACKAGE_NAME, toWrite[i].packageName);
                 out.attribute(null, ATTR_USER, Integer.toString(userSerialNo));
@@ -394,8 +407,8 @@
                 out.attribute(null, ATTR_NIGHT_MODE, Boolean.toString(toWrite[i].nightMode));
                 out.attribute(null, ATTR_COLOR_TEMPERATURE, Integer.toString(
                         toWrite[i].colorTemperature));
-                out.attribute(null, ATTR_LAST_BRIGHTNESS,
-                        Integer.toString(toWrite[i].lastBrightness));
+                out.attribute(null, ATTR_LAST_NITS,
+                        Float.toString(toWrite[i].lastBrightness));
                 StringBuilder luxValues = new StringBuilder();
                 StringBuilder luxTimestamps = new StringBuilder();
                 for (int j = 0; j < toWrite[i].luxValues.length; ++j) {
@@ -446,8 +459,8 @@
                 if (TAG_EVENT.equals(tag)) {
                     BrightnessChangeEvent event = new BrightnessChangeEvent();
 
-                    String brightness = parser.getAttributeValue(null, ATTR_BRIGHTNESS);
-                    event.brightness = Integer.parseInt(brightness);
+                    String brightness = parser.getAttributeValue(null, ATTR_NITS);
+                    event.brightness = Float.parseFloat(brightness);
                     String timestamp = parser.getAttributeValue(null, ATTR_TIMESTAMP);
                     event.timeStamp = Long.parseLong(timestamp);
                     event.packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
@@ -460,8 +473,8 @@
                     String colorTemperature =
                             parser.getAttributeValue(null, ATTR_COLOR_TEMPERATURE);
                     event.colorTemperature = Integer.parseInt(colorTemperature);
-                    String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_BRIGHTNESS);
-                    event.lastBrightness = Integer.parseInt(lastBrightness);
+                    String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_NITS);
+                    event.lastBrightness = Float.parseFloat(lastBrightness);
 
                     String luxValue = parser.getAttributeValue(null, ATTR_LUX);
                     String luxTimestamp = parser.getAttributeValue(null, ATTR_LUX_TIMESTAMPS);
@@ -582,22 +595,6 @@
         }
     }
 
-    private final class SettingsObserver extends ContentObserver {
-        public SettingsObserver(Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (DEBUG) {
-                Slog.v(TAG, "settings change " + uri);
-            }
-            // Self change is based on observer passed to notifyObserver, SettingsProvider
-            // passes null so no changes are self changes.
-            handleBrightnessChanged();
-        }
-    }
-
     private final class Receiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -623,6 +620,24 @@
         }
     }
 
+    private final class TrackerHandler extends Handler {
+        public TrackerHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_BACKGROUND_START:
+                    backgroundStart((float)msg.obj /*initial brightness*/);
+                    break;
+                case MSG_BRIGHTNESS_CHANGED:
+                    float newBrightness = (float) msg.obj;
+                    boolean userInitiatedChange = (msg.arg1 == 1);
+                    handleBrightnessChanged(newBrightness, userInitiatedChange);
+                    break;
+            }
+        }
+    }
+
     @VisibleForTesting
     static class Injector {
         public void registerSensorListener(Context context,
@@ -638,18 +653,6 @@
             sensorManager.unregisterListener(sensorListener);
         }
 
-        public void registerBrightnessObserver(ContentResolver resolver,
-                ContentObserver settingsObserver) {
-            resolver.registerContentObserver(Settings.System.getUriFor(
-                    Settings.System.SCREEN_BRIGHTNESS),
-                    false, settingsObserver, UserHandle.USER_ALL);
-        }
-
-        public void unregisterBrightnessObserver(Context context,
-                ContentObserver settingsObserver) {
-            context.getContentResolver().unregisterContentObserver(settingsObserver);
-        }
-
         public void registerReceiver(Context context,
                 BroadcastReceiver receiver, IntentFilter filter) {
             context.registerReceiver(receiver, filter);
@@ -664,16 +667,6 @@
             return BackgroundThread.getHandler();
         }
 
-        public int getSystemIntForUser(ContentResolver resolver, String setting, int defaultValue,
-                int userId) {
-            return Settings.System.getIntForUser(resolver, setting, defaultValue, userId);
-        }
-
-        public void putSystemIntForUser(ContentResolver resolver, String setting, int value,
-                int userId) {
-            Settings.System.putIntForUser(resolver, setting, value, userId);
-        }
-
         public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
                 int userId) {
             return Settings.Secure.getIntForUser(resolver, setting, defaultValue, userId);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9b97934..02e4fe0 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -270,8 +270,6 @@
 
     private final Injector mInjector;
 
-    private final BrightnessTracker mBrightnessTracker;
-
     public DisplayManagerService(Context context) {
         this(context, new Injector());
     }
@@ -290,7 +288,6 @@
 
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
-        mBrightnessTracker = new BrightnessTracker(context, null);
         mCurrentUserId = UserHandle.USER_SYSTEM;
     }
 
@@ -1339,9 +1336,6 @@
 
             pw.println();
             mPersistentDataStore.dump(pw);
-
-            pw.println();
-            mBrightnessTracker.dump(pw);
         }
     }
 
@@ -1418,10 +1412,6 @@
                     break;
                 }
 
-                case MSG_REGISTER_BRIGHTNESS_TRACKER:
-                    mBrightnessTracker.start();
-                    break;
-
                 case MSG_LOAD_BRIGHTNESS_CONFIGURATION:
                     loadBrightnessConfiguration();
                     break;
@@ -1833,22 +1823,9 @@
             final int userId = UserHandle.getUserId(callingUid);
             final long token = Binder.clearCallingIdentity();
             try {
-                return mBrightnessTracker.getEvents(userId, hasUsageStats);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override // Binder call
-        public void setBrightness(int brightness) {
-            // STOPSHIP - remove when adaptive brightness controller accepts curves.
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.BRIGHTNESS_SLIDER_USAGE,
-                    "Permission to set brightness.");
-            int userId = UserHandle.getUserId(Binder.getCallingUid());
-            final long token = Binder.clearCallingIdentity();
-            try {
-                mBrightnessTracker.setBrightness(brightness, userId);
+                synchronized (mSyncRoot) {
+                    return mDisplayPowerController.getBrightnessEvents(userId, hasUsageStats);
+                }
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -2028,7 +2005,9 @@
 
         @Override
         public void persistBrightnessSliderEvents() {
-            mBrightnessTracker.persistEvents();
+            synchronized (mSyncRoot) {
+                mDisplayPowerController.persistBrightnessSliderEvents();
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index a2d9548..d2b8e5c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -24,13 +24,16 @@
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.Context;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.display.BrightnessChangeEvent;
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
@@ -96,7 +99,6 @@
     private static final int MSG_SCREEN_ON_UNBLOCKED = 3;
     private static final int MSG_SCREEN_OFF_UNBLOCKED = 4;
     private static final int MSG_CONFIGURE_BRIGHTNESS = 5;
-    private static final int MSG_USER_SWITCH = 6;
 
     private static final int PROXIMITY_UNKNOWN = -1;
     private static final int PROXIMITY_NEGATIVE = 0;
@@ -151,9 +153,6 @@
     // The dim screen brightness.
     private final int mScreenBrightnessDimConfig;
 
-    // The minimum screen brightness to use in a very dark room.
-    private final int mScreenBrightnessDarkConfig;
-
     // The minimum allowed brightness.
     private final int mScreenBrightnessRangeMinimum;
 
@@ -289,19 +288,30 @@
     // The controller for the automatic brightness level.
     private AutomaticBrightnessController mAutomaticBrightnessController;
 
-    // The default brightness configuration. Used for whenever we don't have a valid brightness
-    // configuration set. This is typically seen with users that don't have a brightness
-    // configuration that's different from the default.
-    private BrightnessConfiguration mDefaultBrightnessConfiguration;
+    // The mapper between ambient lux, display backlight values, and display brightness.
+    @Nullable
+    private BrightnessMappingStrategy mBrightnessMapper;
 
     // The current brightness configuration.
+    @Nullable
     private BrightnessConfiguration mBrightnessConfiguration;
 
+    // The last brightness that was set by the user and not temporary. Set to -1 when a brightness
+    // has yet to be recorded.
+    private int mLastBrightness;
+
+    // The last auto brightness adjustment that was set by the user and not temporary. Set to
+    // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
+    private float mLastAutoBrightnessAdjustment;
+
     // Animators.
     private ObjectAnimator mColorFadeOnAnimator;
     private ObjectAnimator mColorFadeOffAnimator;
     private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
 
+    // Tracker for brightness changes
+    private final BrightnessTracker mBrightnessTracker;
+
     /**
      * Creates the display power controller.
      */
@@ -309,6 +319,7 @@
             DisplayPowerCallbacks callbacks, Handler handler,
             SensorManager sensorManager, DisplayBlanker blanker) {
         mHandler = new DisplayControllerHandler(handler.getLooper());
+        mBrightnessTracker = new BrightnessTracker(context, null);
         mCallbacks = callbacks;
 
         mBatteryStats = BatteryStatsService.getService();
@@ -327,23 +338,8 @@
         mScreenBrightnessDimConfig = clampAbsoluteBrightness(resources.getInteger(
                 com.android.internal.R.integer.config_screenBrightnessDim));
 
-        mScreenBrightnessDarkConfig = clampAbsoluteBrightness(resources.getInteger(
-                com.android.internal.R.integer.config_screenBrightnessDark));
-        if (mScreenBrightnessDarkConfig > mScreenBrightnessDimConfig) {
-            Slog.w(TAG, "Expected config_screenBrightnessDark ("
-                    + mScreenBrightnessDarkConfig + ") to be less than or equal to "
-                    + "config_screenBrightnessDim (" + mScreenBrightnessDimConfig + ").");
-        }
-        if (mScreenBrightnessDarkConfig > screenBrightnessSettingMinimum) {
-            Slog.w(TAG, "Expected config_screenBrightnessDark ("
-                    + mScreenBrightnessDarkConfig + ") to be less than or equal to "
-                    + "config_screenBrightnessSettingMinimum ("
-                    + screenBrightnessSettingMinimum + ").");
-        }
-
-        int screenBrightnessRangeMinimum = Math.min(Math.min(
-                screenBrightnessSettingMinimum, mScreenBrightnessDimConfig),
-                mScreenBrightnessDarkConfig);
+        mScreenBrightnessRangeMinimum =
+                Math.min(screenBrightnessSettingMinimum, mScreenBrightnessDimConfig);
 
         mScreenBrightnessRangeMaximum = clampAbsoluteBrightness(resources.getInteger(
                     com.android.internal.R.integer.config_screenBrightnessSettingMaximum));
@@ -362,18 +358,6 @@
                 com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
 
         if (mUseSoftwareAutoBrightnessConfig) {
-            float[] luxLevels = getLuxLevels(resources.getIntArray(
-                    com.android.internal.R.array.config_autoBrightnessLevels));
-            int[] backlightValues = resources.getIntArray(
-                    com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
-            float[] brightnessValuesNits = getFloatArray(resources.obtainTypedArray(
-                    com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
-
-            final float screenMinimumNits = resources.getFloat(
-                    com.android.internal.R.dimen.config_screenBrightnessMinimumNits);
-            final float screenMaximumNits = resources.getFloat(
-                    com.android.internal.R.dimen.config_screenBrightnessMaximumNits);
-
             final float dozeScaleFactor = resources.getFraction(
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
@@ -413,31 +397,13 @@
                         + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ").");
             }
 
-            if (backlightValues != null && backlightValues.length > 0) {
-                final int bottom = backlightValues[0];
-                if (mScreenBrightnessDarkConfig > bottom) {
-                    Slog.w(TAG, "config_screenBrightnessDark (" + mScreenBrightnessDarkConfig
-                            + ") should be less than or equal to the first value of "
-                            + "config_autoBrightnessLcdBacklightValues ("
-                            + bottom + ").");
-                }
-                if (bottom < screenBrightnessRangeMinimum) {
-                    screenBrightnessRangeMinimum = bottom;
-                }
-            }
-
-            float[] nitsRange = { screenMinimumNits, screenMaximumNits };
-            int[] backlightRange = { screenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum };
-
-            BrightnessMappingStrategy mapper = BrightnessMappingStrategy.create(
-                    luxLevels, backlightValues, brightnessValuesNits,
-                    nitsRange, backlightRange);
-            if (mapper != null) {
+            mBrightnessMapper = BrightnessMappingStrategy.create(resources);
+            if (mBrightnessMapper != null) {
                 mAutomaticBrightnessController = new AutomaticBrightnessController(this,
-                        handler.getLooper(), sensorManager, mapper, lightSensorWarmUpTimeConfig,
-                        screenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum,
-                        dozeScaleFactor, lightSensorRate, initialLightSensorRate,
-                        brighteningLightDebounce, darkeningLightDebounce,
+                        handler.getLooper(), sensorManager, mBrightnessMapper,
+                        lightSensorWarmUpTimeConfig, mScreenBrightnessRangeMinimum,
+                        mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate,
+                        initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
                         autoBrightnessResetAmbientLuxAfterWarmUp, ambientLightHorizon,
                         autoBrightnessAdjustmentMaxGamma, dynamicHysteresis);
             } else {
@@ -445,9 +411,6 @@
             }
         }
 
-        mScreenBrightnessRangeMinimum = screenBrightnessRangeMinimum;
-
-
         mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
         mColorFadeFadesConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_animateScreenLights);
@@ -466,25 +429,8 @@
             }
         }
 
-    }
-
-    private static float[] getLuxLevels(int[] lux) {
-        // The first control point is implicit and always at 0 lux.
-        float[] levels = new float[lux.length + 1];
-        for (int i = 0; i < lux.length; i++) {
-            levels[i + 1] = (float) lux[i];
-        }
-        return levels;
-    }
-
-    private static float[] getFloatArray(TypedArray array) {
-        final int N = array.length();
-        float[] vals = new float[N];
-        for (int i = 0; i < N; i++) {
-            vals[i] = array.getFloat(i, -1.0f);
-        }
-        array.recycle();
-        return vals;
+        mLastBrightness = -1;
+        mLastAutoBrightnessAdjustment = Float.NaN;
     }
 
     /**
@@ -495,6 +441,23 @@
     }
 
     /**
+     * Get the {@link BrightnessChangeEvent}s for the specified user.
+     * @param userId userId to fetch data for
+     * @param includePackage if false will null out the package name in events
+     */
+    public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(
+            @UserIdInt int userId, boolean includePackage) {
+        return mBrightnessTracker.getEvents(userId, includePackage);
+    }
+
+    /**
+     * Persist the brightness slider events to disk.
+     */
+    public void persistBrightnessSliderEvents() {
+        mBrightnessTracker.persistEvents();
+    }
+
+    /**
      * Requests a new power state.
      * The controller makes a copy of the provided object and then
      * begins adjusting the power state to match what was requested.
@@ -588,6 +551,12 @@
         } catch (RemoteException ex) {
             // same process
         }
+
+        // Initialize all of the brightness tracking state
+        final float brightness = getNits(mPowerState.getScreenBrightness());
+        if (brightness >= 0.0f) {
+            mBrightnessTracker.start(brightness);
+        }
     }
 
     private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -722,16 +691,32 @@
             brightness = PowerManager.BRIGHTNESS_OFF;
         }
 
-        // Configure auto-brightness.
-        boolean autoBrightnessEnabled = false;
-        if (mAutomaticBrightnessController != null) {
-            final boolean autoBrightnessEnabledInDoze =
-                    mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
-            autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+
+        final boolean autoBrightnessEnabledInDoze =
+                mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
+        final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
                     && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
-                    && brightness < 0;
-            final boolean userInitiatedChange = autoBrightnessAdjustmentChanged
-                    && mPowerRequest.brightnessSetByUser;
+                    && brightness < 0
+                    && mAutomaticBrightnessController != null;
+        final boolean brightnessAdjustmentChanged =
+                !Float.isNaN(mLastAutoBrightnessAdjustment)
+                && mPowerRequest.screenAutoBrightnessAdjustment != mLastAutoBrightnessAdjustment;
+        final boolean brightnessChanged = mLastBrightness >= 0
+                && mPowerRequest.screenBrightness != mLastBrightness;
+
+        // Update the last set brightness values.
+        final boolean userInitiatedChange;
+        if (mPowerRequest.brightnessSetByUser && !mPowerRequest.brightnessIsTemporary) {
+            userInitiatedChange = autoBrightnessEnabled && brightnessAdjustmentChanged
+                    || !autoBrightnessEnabled && brightnessChanged;
+            mLastBrightness = mPowerRequest.screenBrightness;
+            mLastAutoBrightnessAdjustment = mPowerRequest.screenAutoBrightnessAdjustment;
+        } else {
+            userInitiatedChange = false;
+        }
+
+        // Configure auto-brightness.
+        if (mAutomaticBrightnessController != null) {
             mAutomaticBrightnessController.configure(autoBrightnessEnabled,
                     mBrightnessConfiguration, mPowerRequest.screenAutoBrightnessAdjustment,
                     state != Display.STATE_ON, userInitiatedChange);
@@ -854,6 +839,13 @@
                 animateScreenBrightness(brightness,
                         slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast);
             }
+
+            final float brightnessInNits = getNits(brightness);
+            if (!mPowerRequest.brightnessIsTemporary && brightnessInNits >= 0.0f) {
+                // We only want to track changes made by the user and on devices that can actually
+                // map the display backlight values into a physical brightness unit.
+                mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiatedChange);
+            }
         }
 
         // Determine whether the display is ready for use in the newly requested state.
@@ -1312,6 +1304,14 @@
         mHandler.post(mOnStateChangedRunnable);
     }
 
+    private float getNits(int backlight) {
+        if (mBrightnessMapper != null) {
+            return mBrightnessMapper.getNits(backlight);
+        } else {
+            return -1.0f;
+        }
+    }
+
     private final Runnable mOnStateChangedRunnable = new Runnable() {
         @Override
         public void run() {
@@ -1362,7 +1362,6 @@
         pw.println("Display Power Controller Configuration:");
         pw.println("  mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
         pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
-        pw.println("  mScreenBrightnessDarkConfig=" + mScreenBrightnessDarkConfig);
         pw.println("  mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
         pw.println("  mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
         pw.println("  mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
@@ -1383,7 +1382,6 @@
         pw.println("Display Power Controller Thread State:");
         pw.println("  mPowerRequest=" + mPowerRequest);
         pw.println("  mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
-
         pw.println("  mProximitySensor=" + mProximitySensor);
         pw.println("  mProximitySensorEnabled=" + mProximitySensorEnabled);
         pw.println("  mProximityThreshold=" + mProximityThreshold);
@@ -1392,6 +1390,8 @@
         pw.println("  mPendingProximityDebounceTime="
                 + TimeUtils.formatUptime(mPendingProximityDebounceTime));
         pw.println("  mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
+        pw.println("  mLastBrightness=" + mLastBrightness);
+        pw.println("  mLastAutoBrightnessAdjustment=" + mLastAutoBrightnessAdjustment);
         pw.println("  mAppliedAutoBrightness=" + mAppliedAutoBrightness);
         pw.println("  mAppliedDimming=" + mAppliedDimming);
         pw.println("  mAppliedLowPower=" + mAppliedLowPower);
@@ -1420,6 +1420,10 @@
             mAutomaticBrightnessController.dump(pw);
         }
 
+        if (mBrightnessTracker != null) {
+            pw.println();
+            mBrightnessTracker.dump(pw);
+        }
     }
 
     private static String proximityToString(int state) {
@@ -1481,8 +1485,7 @@
                     }
                     break;
                 case MSG_CONFIGURE_BRIGHTNESS:
-                    BrightnessConfiguration c = (BrightnessConfiguration) msg.obj;
-                    mBrightnessConfiguration = c != null ? c : mDefaultBrightnessConfiguration;
+                    mBrightnessConfiguration = (BrightnessConfiguration)msg.obj;
                     updatePowerState();
                     break;
             }
diff --git a/services/core/java/com/android/server/job/GrantedUriPermissions.java b/services/core/java/com/android/server/job/GrantedUriPermissions.java
index c23b109..8fecb8f 100644
--- a/services/core/java/com/android/server/job/GrantedUriPermissions.java
+++ b/services/core/java/com/android/server/job/GrantedUriPermissions.java
@@ -25,6 +25,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -153,4 +154,21 @@
             pw.println(mUris.get(i));
         }
     }
+
+    public void dump(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        proto.write(GrantedUriPermissionsDumpProto.FLAGS, mGrantFlags);
+        proto.write(GrantedUriPermissionsDumpProto.SOURCE_USER_ID, mSourceUserId);
+        proto.write(GrantedUriPermissionsDumpProto.TAG, mTag);
+        proto.write(GrantedUriPermissionsDumpProto.PERMISSION_OWNER, mPermissionOwner.toString());
+        for (int i = 0; i < mUris.size(); i++) {
+            Uri u = mUris.get(i);
+            if (u != null) {
+                proto.write(GrantedUriPermissionsDumpProto.URIS, u.toString());
+            }
+        }
+
+        proto.end(token);
+    }
 }
diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java
index 296743b..8b8faa3 100644
--- a/services/core/java/com/android/server/job/JobPackageTracker.java
+++ b/services/core/java/com/android/server/job/JobPackageTracker.java
@@ -28,6 +28,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.RingBufferIndices;
 import com.android.server.job.controllers.JobStatus;
@@ -308,13 +309,13 @@
             }
         }
 
-        void dump(PrintWriter pw, String header, String prefix, long now, long nowEllapsed,
+        void dump(PrintWriter pw, String header, String prefix, long now, long nowElapsed,
                 int filterUid) {
             final long period = getTotalTime(now);
             pw.print(prefix); pw.print(header); pw.print(" at ");
             pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString());
             pw.print(" (");
-            TimeUtils.formatDuration(mStartElapsedTime, nowEllapsed, pw);
+            TimeUtils.formatDuration(mStartElapsedTime, nowElapsed, pw);
             pw.print(") over ");
             TimeUtils.formatDuration(period, pw);
             pw.println(":");
@@ -365,6 +366,73 @@
             pw.print(mMaxTotalActive); pw.print(" total, ");
             pw.print(mMaxFgActive); pw.println(" foreground");
         }
+
+        private void printPackageEntryState(ProtoOutputStream proto, long fieldId,
+                long duration, int count) {
+            final long token = proto.start(fieldId);
+            proto.write(DataSetProto.PackageEntryProto.State.DURATION_MS, duration);
+            proto.write(DataSetProto.PackageEntryProto.State.COUNT, count);
+            proto.end(token);
+        }
+
+        void dump(ProtoOutputStream proto, long fieldId, long now, long nowElapsed, int filterUid) {
+            final long token = proto.start(fieldId);
+            final long period = getTotalTime(now);
+
+            proto.write(DataSetProto.START_CLOCK_TIME_MS, mStartClockTime);
+            proto.write(DataSetProto.ELAPSED_TIME_MS, nowElapsed - mStartElapsedTime);
+            proto.write(DataSetProto.PERIOD_MS, period);
+
+            final int NE = mEntries.size();
+            for (int i = 0; i < NE; i++) {
+                int uid = mEntries.keyAt(i);
+                if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
+                    continue;
+                }
+                ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
+                final int NP = uidMap.size();
+                for (int j = 0; j < NP; j++) {
+                    final long peToken = proto.start(DataSetProto.PACKAGE_ENTRIES);
+                    PackageEntry pe = uidMap.valueAt(j);
+
+                    proto.write(DataSetProto.PackageEntryProto.UID, uid);
+                    proto.write(DataSetProto.PackageEntryProto.PACKAGE_NAME, uidMap.keyAt(j));
+
+                    printPackageEntryState(proto, DataSetProto.PackageEntryProto.PENDING_STATE,
+                            pe.getPendingTime(now), pe.pendingCount);
+                    printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_STATE,
+                            pe.getActiveTime(now), pe.activeCount);
+                    printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_TOP_STATE,
+                            pe.getActiveTopTime(now), pe.activeTopCount);
+
+                    proto.write(DataSetProto.PackageEntryProto.PENDING,
+                          pe.pendingNesting > 0 || pe.hadPending);
+                    proto.write(DataSetProto.PackageEntryProto.ACTIVE,
+                          pe.activeNesting > 0 || pe.hadActive);
+                    proto.write(DataSetProto.PackageEntryProto.ACTIVE_TOP,
+                          pe.activeTopNesting > 0 || pe.hadActiveTop);
+
+                    for (int k = 0; k < pe.stopReasons.size(); k++) {
+                        final long srcToken =
+                                proto.start(DataSetProto.PackageEntryProto.STOP_REASONS);
+
+                        proto.write(DataSetProto.PackageEntryProto.StopReasonCount.REASON,
+                                pe.stopReasons.keyAt(k));
+                        proto.write(DataSetProto.PackageEntryProto.StopReasonCount.COUNT,
+                                pe.stopReasons.valueAt(k));
+
+                        proto.end(srcToken);
+                    }
+
+                    proto.end(peToken);
+                }
+            }
+
+            proto.write(DataSetProto.MAX_CONCURRENCY, mMaxTotalActive);
+            proto.write(DataSetProto.MAX_FOREGROUND_CONCURRENCY, mMaxFgActive);
+
+            proto.end(token);
+        }
     }
 
     void rebatchIfNeeded(long now) {
@@ -450,7 +518,7 @@
 
     public void dump(PrintWriter pw, String prefix, int filterUid) {
         final long now = sUptimeMillisClock.millis();
-        final long nowEllapsed = sElapsedRealtimeClock.millis();
+        final long nowElapsed = sElapsedRealtimeClock.millis();
         final DataSet total;
         if (mLastDataSets[0] != null) {
             total = new DataSet(mLastDataSets[0]);
@@ -461,11 +529,37 @@
         mCurDataSet.addTo(total, now);
         for (int i = 1; i < mLastDataSets.length; i++) {
             if (mLastDataSets[i] != null) {
-                mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowEllapsed, filterUid);
+                mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowElapsed, filterUid);
                 pw.println();
             }
         }
-        total.dump(pw, "Current stats", prefix, now, nowEllapsed, filterUid);
+        total.dump(pw, "Current stats", prefix, now, nowElapsed, filterUid);
+    }
+
+    public void dump(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final long token = proto.start(fieldId);
+        final long now = sUptimeMillisClock.millis();
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+
+        final DataSet total;
+        if (mLastDataSets[0] != null) {
+            total = new DataSet(mLastDataSets[0]);
+            mLastDataSets[0].addTo(total, now);
+        } else {
+            total = new DataSet(mCurDataSet);
+        }
+        mCurDataSet.addTo(total, now);
+
+        for (int i = 1; i < mLastDataSets.length; i++) {
+            if (mLastDataSets[i] != null) {
+                mLastDataSets[i].dump(proto, JobPackageTrackerDumpProto.HISTORICAL_STATS,
+                        now, nowElapsed, filterUid);
+            }
+        }
+        total.dump(proto, JobPackageTrackerDumpProto.CURRENT_STATS,
+                now, nowElapsed, filterUid);
+
+        proto.end(token);
     }
 
     public boolean dumpHistory(PrintWriter pw, String prefix, int filterUid) {
@@ -512,4 +606,40 @@
         }
         return true;
     }
+
+    public void dumpHistory(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final int size = mEventIndices.size();
+        if (size == 0) {
+            return;
+        }
+        final long token = proto.start(fieldId);
+
+        final long now = sElapsedRealtimeClock.millis();
+        for (int i = 0; i < size; i++) {
+            final int index = mEventIndices.indexOf(i);
+            final int uid = mEventUids[index];
+            if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
+                continue;
+            }
+            final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
+            if (cmd == EVENT_NULL) {
+                continue;
+            }
+            final long heToken = proto.start(JobPackageHistoryProto.HISTORY_EVENT);
+
+            proto.write(JobPackageHistoryProto.HistoryEvent.EVENT, cmd);
+            proto.write(JobPackageHistoryProto.HistoryEvent.TIME_SINCE_EVENT_MS, now - mEventTimes[index]);
+            proto.write(JobPackageHistoryProto.HistoryEvent.UID, uid);
+            proto.write(JobPackageHistoryProto.HistoryEvent.JOB_ID, mEventJobIds[index]);
+            proto.write(JobPackageHistoryProto.HistoryEvent.TAG, mEventTags[index]);
+            if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
+                proto.write(JobPackageHistoryProto.HistoryEvent.STOP_REASON,
+                    (mEventCmds[index] & EVENT_STOP_REASON_MASK) >> EVENT_STOP_REASON_SHIFT);
+            }
+
+            proto.end(heToken);
+        }
+
+        proto.end(token);
+    }
 }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index bcb57ef..987baf9 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -64,6 +64,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
@@ -74,6 +75,8 @@
 import com.android.server.DeviceIdleController;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
+import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
 import com.android.server.job.JobStore.JobStatusFunctor;
 import com.android.server.job.controllers.AppIdleController;
 import com.android.server.job.controllers.BackgroundJobsController;
@@ -185,13 +188,18 @@
     boolean mReportedActive;
 
     /**
+     * Are we currently in device-wide standby parole?
+     */
+    volatile boolean mInParole;
+
+    /**
      * Current limit on the number of concurrent JobServiceContext entries we want to
      * keep actively running a job.
      */
     int mMaxActiveJobs = 1;
 
     /**
-     * Which uids are currently in the foreground.
+     * A mapping of which uids are currently in the foreground to their effective priority.
      */
     final SparseIntArray mUidPriorityOverride = new SparseIntArray();
 
@@ -466,11 +474,11 @@
                         DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
                 MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT,
                         DEFAULT_MAX_WORK_RESCHEDULE_COUNT);
-                MIN_LINEAR_BACKOFF_TIME = mParser.getLong(KEY_MIN_LINEAR_BACKOFF_TIME,
+                MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME,
                         DEFAULT_MIN_LINEAR_BACKOFF_TIME);
-                MIN_EXP_BACKOFF_TIME = mParser.getLong(KEY_MIN_EXP_BACKOFF_TIME,
+                MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME,
                         DEFAULT_MIN_EXP_BACKOFF_TIME);
-                STANDBY_HEARTBEAT_TIME = mParser.getLong(KEY_STANDBY_HEARTBEAT_TIME,
+                STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME,
                         DEFAULT_STANDBY_HEARTBEAT_TIME);
                 STANDBY_BEATS[1] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
                         DEFAULT_STANDBY_WORKING_BEATS);
@@ -549,6 +557,36 @@
             }
             pw.println('}');
         }
+
+        void dump(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(ConstantsProto.MIN_IDLE_COUNT, MIN_IDLE_COUNT);
+            proto.write(ConstantsProto.MIN_CHARGING_COUNT, MIN_CHARGING_COUNT);
+            proto.write(ConstantsProto.MIN_BATTERY_NOT_LOW_COUNT, MIN_BATTERY_NOT_LOW_COUNT);
+            proto.write(ConstantsProto.MIN_STORAGE_NOT_LOW_COUNT, MIN_STORAGE_NOT_LOW_COUNT);
+            proto.write(ConstantsProto.MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT);
+            proto.write(ConstantsProto.MIN_CONTENT_COUNT, MIN_CONTENT_COUNT);
+            proto.write(ConstantsProto.MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT);
+            proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
+            proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);
+            proto.write(ConstantsProto.FG_JOB_COUNT, FG_JOB_COUNT);
+            proto.write(ConstantsProto.BG_NORMAL_JOB_COUNT, BG_NORMAL_JOB_COUNT);
+            proto.write(ConstantsProto.BG_MODERATE_JOB_COUNT, BG_MODERATE_JOB_COUNT);
+            proto.write(ConstantsProto.BG_LOW_JOB_COUNT, BG_LOW_JOB_COUNT);
+            proto.write(ConstantsProto.BG_CRITICAL_JOB_COUNT, BG_CRITICAL_JOB_COUNT);
+            proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT);
+            proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT);
+            proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME);
+            proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
+            proto.write(ConstantsProto.STANDBY_HEARTBEAT_TIME_MS, STANDBY_HEARTBEAT_TIME);
+
+            for (int period : STANDBY_BEATS) {
+                proto.write(ConstantsProto.STANDBY_BEATS, period);
+            }
+
+            proto.end(token);
+        }
     }
 
     final Constants mConstants;
@@ -1720,28 +1758,31 @@
         }
 
         // If the app is in a non-active standby bucket, make sure we've waited
-        // an appropriate amount of time since the last invocation
-        final int bucket = job.getStandbyBucket();
-        if (mHeartbeat < mNextBucketHeartbeat[bucket]) {
-            // Only skip this job if it's still waiting for the end of its (initial) nominal
-            // bucket interval.  Once it's waited that long, we let it go ahead and clear.
-            // The final (NEVER) bucket is special; we never age those apps' jobs into
-            // runnability.
-            if (bucket >= mConstants.STANDBY_BEATS.length
-                    || (mHeartbeat < job.getBaseHeartbeat() + mConstants.STANDBY_BEATS[bucket])) {
-                // TODO: log/trace that we're deferring the job due to bucketing if we hit this
-                if (job.getWhenStandbyDeferred() == 0) {
-                    if (DEBUG_STANDBY) {
-                        Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < "
-                                + mNextBucketHeartbeat[job.getStandbyBucket()] + " for " + job);
+        // an appropriate amount of time since the last invocation.  During device-
+        // wide parole, standby bucketing is ignored.
+        if (!mInParole) {
+            final int bucket = job.getStandbyBucket();
+            if (mHeartbeat < mNextBucketHeartbeat[bucket]) {
+                // Only skip this job if it's still waiting for the end of its (initial) nominal
+                // bucket interval.  Once it's waited that long, we let it go ahead and clear.
+                // The final (NEVER) bucket is special; we never age those apps' jobs into
+                // runnability.
+                if (bucket >= mConstants.STANDBY_BEATS.length
+                        || (mHeartbeat < job.getBaseHeartbeat() + mConstants.STANDBY_BEATS[bucket])) {
+                    // TODO: log/trace that we're deferring the job due to bucketing if we hit this
+                    if (job.getWhenStandbyDeferred() == 0) {
+                        if (DEBUG_STANDBY) {
+                            Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < "
+                                    + mNextBucketHeartbeat[job.getStandbyBucket()] + " for " + job);
+                        }
+                        job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
                     }
-                    job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
-                }
-                return false;
-            } else {
-                if (DEBUG_STANDBY) {
-                    Slog.v(TAG, "Bucket deferred job aged into runnability at "
-                            + mHeartbeat + " : " + job);
+                    return false;
+                } else {
+                    if (DEBUG_STANDBY) {
+                        Slog.v(TAG, "Bucket deferred job aged into runnability at "
+                                + mHeartbeat + " : " + job);
+                    }
                 }
             }
         }
@@ -2086,7 +2127,10 @@
 
         @Override
         public void onParoleStateChanged(boolean isParoleOn) {
-            // Unused
+            if (DEBUG_STANDBY) {
+                Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
+            }
+            mInParole = isParoleOn;
         }
     }
 
@@ -2322,9 +2366,46 @@
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
 
+            int filterUid = -1;
+            boolean proto = false;
+            if (!ArrayUtils.isEmpty(args)) {
+                int opti = 0;
+                while (opti < args.length) {
+                    String arg = args[opti];
+                    if ("-h".equals(arg)) {
+                        dumpHelp(pw);
+                        return;
+                    } else if ("-a".equals(arg)) {
+                        // Ignore, we always dump all.
+                    } else if ("--proto".equals(arg)) {
+                        proto = true;
+                    } else if (arg.length() > 0 && arg.charAt(0) == '-') {
+                        pw.println("Unknown option: " + arg);
+                        return;
+                    } else {
+                        break;
+                    }
+                    opti++;
+                }
+                if (opti < args.length) {
+                    String pkg = args[opti];
+                    try {
+                        filterUid = getContext().getPackageManager().getPackageUid(pkg,
+                                PackageManager.MATCH_ANY_USER);
+                    } catch (NameNotFoundException ignored) {
+                        pw.println("Invalid package: " + pkg);
+                        return;
+                    }
+                }
+            }
+
             long identityToken = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.dumpInternal(pw, args);
+                if (proto) {
+                    JobSchedulerService.this.dumpInternalProto(fd, filterUid);
+                } else {
+                    JobSchedulerService.this.dumpInternal(pw, filterUid);
+                }
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
@@ -2589,37 +2670,24 @@
         pw.println("  [package] is an optional package name to limit the output to.");
     }
 
-    void dumpInternal(final PrintWriter pw, String[] args) {
-        int filterUid = -1;
-        if (!ArrayUtils.isEmpty(args)) {
-            int opti = 0;
-            while (opti < args.length) {
-                String arg = args[opti];
-                if ("-h".equals(arg)) {
-                    dumpHelp(pw);
-                    return;
-                } else if ("-a".equals(arg)) {
-                    // Ignore, we always dump all.
-                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
-                    pw.println("Unknown option: " + arg);
-                    return;
-                } else {
-                    break;
+    /** Sort jobs by caller UID, then by Job ID. */
+    private static void sortJobs(List<JobStatus> jobs) {
+        Collections.sort(jobs, new Comparator<JobStatus>() {
+            @Override
+            public int compare(JobStatus o1, JobStatus o2) {
+                int uid1 = o1.getUid();
+                int uid2 = o2.getUid();
+                int id1 = o1.getJobId();
+                int id2 = o2.getJobId();
+                if (uid1 != uid2) {
+                    return uid1 < uid2 ? -1 : 1;
                 }
-                opti++;
+                return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0);
             }
-            if (opti < args.length) {
-                String pkg = args[opti];
-                try {
-                    filterUid = getContext().getPackageManager().getPackageUid(pkg,
-                            PackageManager.MATCH_ANY_USER);
-                } catch (NameNotFoundException ignored) {
-                    pw.println("Invalid package: " + pkg);
-                    return;
-                }
-            }
-        }
+        });
+    }
 
+    void dumpInternal(final PrintWriter pw, int filterUid) {
         final int filterUidFinal = UserHandle.getAppId(filterUid);
         final long nowElapsed = sElapsedRealtimeClock.millis();
         final long nowUptime = sUptimeMillisClock.millis();
@@ -2632,19 +2700,7 @@
             pw.println(" jobs:");
             if (mJobs.size() > 0) {
                 final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
-                Collections.sort(jobs, new Comparator<JobStatus>() {
-                    @Override
-                    public int compare(JobStatus o1, JobStatus o2) {
-                        int uid1 = o1.getUid();
-                        int uid2 = o2.getUid();
-                        int id1 = o1.getJobId();
-                        int id2 = o2.getJobId();
-                        if (uid1 != uid2) {
-                            return uid1 < uid2 ? -1 : 1;
-                        }
-                        return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0);
-                    }
-                });
+                sortJobs(jobs);
                 for (JobStatus job : jobs) {
                     pw.print("  JOB #"); job.printUniqueId(pw); pw.print(": ");
                     pw.println(job.toShortStringExceptUniqueId());
@@ -2781,4 +2837,144 @@
         }
         pw.println();
     }
+
+    void dumpInternalProto(final FileDescriptor fd, int filterUid) {
+        ProtoOutputStream proto = new ProtoOutputStream(fd);
+        final int filterUidFinal = UserHandle.getAppId(filterUid);
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        final long nowUptime = sUptimeMillisClock.millis();
+
+        synchronized (mLock) {
+            mConstants.dump(proto, JobSchedulerServiceDumpProto.SETTINGS);
+            for (int u : mStartedUsers) {
+                proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u);
+            }
+            if (mJobs.size() > 0) {
+                final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
+                sortJobs(jobs);
+                for (JobStatus job : jobs) {
+                    final long rjToken = proto.start(JobSchedulerServiceDumpProto.REGISTERED_JOBS);
+                    job.writeToShortProto(proto, JobSchedulerServiceDumpProto.RegisteredJob.INFO);
+
+                    // Skip printing details if the caller requested a filter
+                    if (!job.shouldDump(filterUidFinal)) {
+                        continue;
+                    }
+
+                    job.dump(proto, JobSchedulerServiceDumpProto.RegisteredJob.DUMP, true, nowElapsed);
+
+                    // isReadyToBeExecuted
+                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_READY,
+                            job.isReady());
+                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_USER_STARTED,
+                            ArrayUtils.contains(mStartedUsers, job.getUserId()));
+                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING,
+                            mPendingJobs.contains(job));
+                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE,
+                            isCurrentlyActiveLocked(job));
+                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_UID_BACKING_UP,
+                            mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0);
+                    boolean componentPresent = false;
+                    try {
+                        componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
+                                job.getServiceComponent(),
+                                PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+                                job.getUserId()) != null);
+                    } catch (RemoteException e) {
+                    }
+                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_COMPONENT_PRESENT,
+                            componentPresent);
+
+                    proto.end(rjToken);
+                }
+            }
+            for (StateController controller : mControllers) {
+                controller.dumpControllerStateLocked(
+                        proto, JobSchedulerServiceDumpProto.CONTROLLERS, filterUidFinal);
+            }
+            for (int i=0; i< mUidPriorityOverride.size(); i++) {
+                int uid = mUidPriorityOverride.keyAt(i);
+                if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
+                    long pToken = proto.start(JobSchedulerServiceDumpProto.PRIORITY_OVERRIDES);
+                    proto.write(JobSchedulerServiceDumpProto.PriorityOverride.UID, uid);
+                    proto.write(JobSchedulerServiceDumpProto.PriorityOverride.OVERRIDE_VALUE,
+                            mUidPriorityOverride.valueAt(i));
+                    proto.end(pToken);
+                }
+            }
+            for (int i = 0; i < mBackingUpUids.size(); i++) {
+                int uid = mBackingUpUids.keyAt(i);
+                if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
+                    proto.write(JobSchedulerServiceDumpProto.BACKING_UP_UIDS, uid);
+                }
+            }
+
+            mJobPackageTracker.dump(proto, JobSchedulerServiceDumpProto.PACKAGE_TRACKER,
+                    filterUidFinal);
+            mJobPackageTracker.dumpHistory(proto, JobSchedulerServiceDumpProto.HISTORY,
+                    filterUidFinal);
+
+            for (JobStatus job : mPendingJobs) {
+                final long pjToken = proto.start(JobSchedulerServiceDumpProto.PENDING_JOBS);
+
+                job.writeToShortProto(proto, PendingJob.INFO);
+                job.dump(proto, PendingJob.DUMP, false, nowElapsed);
+                int priority = evaluateJobPriorityLocked(job);
+                if (priority != JobInfo.PRIORITY_DEFAULT) {
+                    proto.write(PendingJob.EVALUATED_PRIORITY, priority);
+                }
+                proto.write(PendingJob.ENQUEUED_DURATION_MS, nowUptime - job.madePending);
+
+                proto.end(pjToken);
+            }
+            for (JobServiceContext jsc : mActiveServices) {
+                final long ajToken = proto.start(JobSchedulerServiceDumpProto.ACTIVE_JOBS);
+                final JobStatus job = jsc.getRunningJobLocked();
+
+                if (job == null) {
+                    final long ijToken = proto.start(ActiveJob.INACTIVE);
+
+                        proto.write(ActiveJob.InactiveJob.TIME_SINCE_STOPPED_MS,
+                                nowElapsed - jsc.mStoppedTime);
+                    if (jsc.mStoppedReason != null) {
+                        proto.write(ActiveJob.InactiveJob.STOPPED_REASON,
+                                jsc.mStoppedReason);
+                    }
+
+                    proto.end(ijToken);
+                } else {
+                    final long rjToken = proto.start(ActiveJob.RUNNING);
+
+                    job.writeToShortProto(proto, ActiveJob.RunningJob.INFO);
+
+                    proto.write(ActiveJob.RunningJob.RUNNING_DURATION_MS,
+                            nowElapsed - jsc.getExecutionStartTimeElapsed());
+                    proto.write(ActiveJob.RunningJob.TIME_UNTIL_TIMEOUT_MS,
+                            jsc.getTimeoutElapsed() - nowElapsed);
+
+                    job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
+
+                    int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked());
+                    if (priority != JobInfo.PRIORITY_DEFAULT) {
+                        proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY, priority);
+                    }
+
+                    proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
+                            nowUptime - job.madeActive);
+                    proto.write(ActiveJob.RunningJob.PENDING_DURATION_MS,
+                            job.madeActive - job.madePending);
+
+                    proto.end(rjToken);
+                }
+                proto.end(ajToken);
+            }
+            if (filterUid == -1) {
+                proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock);
+                proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive);
+                proto.write(JobSchedulerServiceDumpProto.MAX_ACTIVE_JOBS, mMaxActiveJobs);
+            }
+        }
+
+        proto.flush();
+    }
 }
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 6a3fd04..709deeb 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -38,12 +38,14 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.util.EventLog;
 import android.util.Slog;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
+import com.android.server.EventLogTags;
 import com.android.server.job.controllers.JobStatus;
 
 /**
@@ -222,17 +224,20 @@
                     isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
             mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
 
-            if (DEBUG_STANDBY) {
-                final long whenDeferred = job.getWhenStandbyDeferred();
-                if (whenDeferred > 0) {
+            final long whenDeferred = job.getWhenStandbyDeferred();
+            if (whenDeferred > 0) {
+                final long deferral = mExecutionStartTimeElapsed - whenDeferred;
+                EventLog.writeEvent(EventLogTags.JOB_DEFERRED_EXECUTION, deferral);
+                if (DEBUG_STANDBY) {
                     StringBuilder sb = new StringBuilder(128);
                     sb.append("Starting job deferred for standby by ");
-                    TimeUtils.formatDuration(mExecutionStartTimeElapsed - whenDeferred, sb);
-                    sb.append(" : ");
+                    TimeUtils.formatDuration(deferral, sb);
+                    sb.append(" ms : ");
                     sb.append(job.toShortString());
                     Slog.v(TAG, sb.toString());
                 }
             }
+
             // Once we'e begun executing a job, we by definition no longer care whether
             // it was inflated from disk with not-yet-coherent delay/deadline bounds.
             job.clearPersistedUtcTimes();
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index a7ed2f5..8d11d1e 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -20,10 +20,12 @@
 import android.content.Context;
 import android.os.UserHandle;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
+import com.android.server.job.StateControllerProto;
 
 import java.io.PrintWriter;
 
@@ -152,6 +154,38 @@
         });
     }
 
+    @Override
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final long token = proto.start(fieldId);
+        final long mToken = proto.start(StateControllerProto.APP_IDLE);
+
+        proto.write(StateControllerProto.AppIdleController.IS_PAROLE_ON, mAppIdleParoleOn);
+
+        mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
+            @Override public void process(JobStatus js) {
+                // Skip printing details if the caller requested a filter
+                if (!js.shouldDump(filterUid)) {
+                    return;
+                }
+
+                final long jsToken =
+                        proto.start(StateControllerProto.AppIdleController.TRACKED_JOBS);
+                js.writeToShortProto(proto, StateControllerProto.AppIdleController.TrackedJob.INFO);
+                proto.write(StateControllerProto.AppIdleController.TrackedJob.SOURCE_UID,
+                        js.getSourceUid());
+                proto.write(StateControllerProto.AppIdleController.TrackedJob.SOURCE_PACKAGE_NAME,
+                        js.getSourcePackageName());
+                proto.write(
+                        StateControllerProto.AppIdleController.TrackedJob.ARE_CONSTRAINTS_SATISFIED,
+                        (js.satisfiedConstraints & JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0);
+                proto.end(jsToken);
+            }
+        });
+
+        proto.end(mToken);
+        proto.end(token);
+    }
+
     void setAppIdleParoleOn(boolean isAppIdleParoleOn) {
         // Flag if any app's idle state has changed
         boolean changed = false;
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 fc4015d..5f95f1a 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -22,12 +22,15 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.ForceAppStandbyTracker;
 import com.android.server.ForceAppStandbyTracker.Listener;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
+import com.android.server.job.StateControllerProto;
+import com.android.server.job.StateControllerProto.BackgroundJobsController.TrackedJob;
 
 import java.io.PrintWriter;
 
@@ -90,6 +93,7 @@
                 return;
             }
             final int uid = jobStatus.getSourceUid();
+            final String sourcePkg = jobStatus.getSourcePackageName();
             pw.print("  #");
             jobStatus.printUniqueId(pw);
             pw.print(" from ");
@@ -100,11 +104,10 @@
                 pw.print(", whitelisted");
             }
             pw.print(": ");
-            pw.print(jobStatus.getSourcePackageName());
+            pw.print(sourcePkg);
 
             pw.print(" [RUN_ANY_IN_BACKGROUND ");
-            pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
-                    jobStatus.getSourceUid(), jobStatus.getSourcePackageName())
+            pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(uid, sourcePkg)
                     ? "allowed]" : "disallowed]");
 
             if ((jobStatus.satisfiedConstraints
@@ -116,6 +119,51 @@
         });
     }
 
+    @Override
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final long token = proto.start(fieldId);
+        final long mToken = proto.start(StateControllerProto.BACKGROUND);
+
+        mForceAppStandbyTracker.dumpProto(proto,
+                StateControllerProto.BackgroundJobsController.FORCE_APP_STANDBY_TRACKER);
+
+        mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
+            if (!jobStatus.shouldDump(filterUid)) {
+                return;
+            }
+            final long jsToken =
+                    proto.start(StateControllerProto.BackgroundJobsController.TRACKED_JOBS);
+
+            jobStatus.writeToShortProto(proto,
+                    TrackedJob.INFO);
+            final int sourceUid = jobStatus.getSourceUid();
+            proto.write(TrackedJob.SOURCE_UID, sourceUid);
+            final String sourcePkg = jobStatus.getSourcePackageName();
+            proto.write(TrackedJob.SOURCE_PACKAGE_NAME, sourcePkg);
+
+            proto.write(TrackedJob.IS_IN_FOREGROUND,
+                    mForceAppStandbyTracker.isInForeground(sourceUid));
+            proto.write(TrackedJob.IS_WHITELISTED,
+                    mForceAppStandbyTracker.isUidPowerSaveWhitelisted(sourceUid) ||
+                    mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(sourceUid));
+
+            proto.write(
+                    TrackedJob.CAN_RUN_ANY_IN_BACKGROUND,
+                    mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
+                            sourceUid, sourcePkg));
+
+            proto.write(
+                    TrackedJob.ARE_CONSTRAINTS_SATISFIED,
+                    (jobStatus.satisfiedConstraints &
+                            JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0);
+
+            proto.end(jsToken);
+        });
+
+        proto.end(mToken);
+        proto.end(token);
+    }
+
     private void updateAllJobRestrictionsLocked() {
         updateJobRestrictionsLocked(/*filterUid=*/ -1);
     }
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 76ff834..8d3d116 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -27,11 +27,13 @@
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
+import com.android.server.job.StateControllerProto;
 
 import java.io.PrintWriter;
 
@@ -263,4 +265,35 @@
             pw.println();
         }
     }
+
+    @Override
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final long token = proto.start(fieldId);
+        final long mToken = proto.start(StateControllerProto.BATTERY);
+
+        proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER,
+                mChargeTracker.isOnStablePower());
+        proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW,
+                mChargeTracker.isBatteryNotLow());
+
+        proto.write(StateControllerProto.BatteryController.IS_MONITORING,
+                mChargeTracker.isMonitoring());
+        proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER,
+                mChargeTracker.getSeq());
+
+        for (int i = 0; i < mTrackedTasks.size(); i++) {
+            final JobStatus js = mTrackedTasks.valueAt(i);
+            if (!js.shouldDump(filterUid)) {
+                continue;
+            }
+            final long jsToken = proto.start(StateControllerProto.BatteryController.TRACKED_JOBS);
+            js.writeToShortProto(proto, StateControllerProto.BatteryController.TrackedJob.INFO);
+            proto.write(StateControllerProto.BatteryController.TrackedJob.SOURCE_UID,
+                    js.getSourceUid());
+            proto.end(jsToken);
+        }
+
+        proto.end(mToken);
+        proto.end(token);
+    }
 }
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 da28769..03fd7b3 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -25,17 +25,20 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkPolicyManager;
+import android.net.NetworkRequest;
 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 android.util.proto.ProtoOutputStream;
 
 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 com.android.server.job.StateControllerProto;
 
 import java.io.PrintWriter;
 
@@ -290,4 +293,32 @@
             }
         }
     }
+
+    @Override
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final long token = proto.start(fieldId);
+        final long mToken = proto.start(StateControllerProto.CONNECTIVITY);
+
+        proto.write(StateControllerProto.ConnectivityController.IS_CONNECTED, mConnected);
+
+        for (int i = 0; i < mTrackedJobs.size(); i++) {
+            final JobStatus js = mTrackedJobs.valueAt(i);
+            if (!js.shouldDump(filterUid)) {
+                continue;
+            }
+            final long jsToken = proto.start(StateControllerProto.ConnectivityController.TRACKED_JOBS);
+            js.writeToShortProto(proto, StateControllerProto.ConnectivityController.TrackedJob.INFO);
+            proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID,
+                    js.getSourceUid());
+            NetworkRequest rn = js.getJob().getRequiredNetwork();
+            if (rn != null) {
+                rn.writeToProto(proto,
+                        StateControllerProto.ConnectivityController.TrackedJob.REQUIRED_NETWORK);
+            }
+            proto.end(jsToken);
+        }
+
+        proto.end(mToken);
+        proto.end(token);
+    }
 }
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
index ff807ec..7394e23f 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -28,10 +28,13 @@
 import android.util.TimeUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
+import com.android.server.job.StateControllerProto;
+import com.android.server.job.StateControllerProto.ContentObserverController.Observer.TriggerContentData;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -451,4 +454,103 @@
             }
         }
     }
+
+    @Override
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final long token = proto.start(fieldId);
+        final long mToken = proto.start(StateControllerProto.CONTENT_OBSERVER);
+
+        for (int i = 0; i < mTrackedTasks.size(); i++) {
+            JobStatus js = mTrackedTasks.valueAt(i);
+            if (!js.shouldDump(filterUid)) {
+                continue;
+            }
+            final long jsToken =
+                    proto.start(StateControllerProto.ContentObserverController.TRACKED_JOBS);
+            js.writeToShortProto(proto,
+                    StateControllerProto.ContentObserverController.TrackedJob.INFO);
+            proto.write(StateControllerProto.ContentObserverController.TrackedJob.SOURCE_UID,
+                    js.getSourceUid());
+            proto.end(jsToken);
+        }
+
+        final int n = mObservers.size();
+        for (int userIdx = 0; userIdx < n; userIdx++) {
+            final long oToken =
+                    proto.start(StateControllerProto.ContentObserverController.OBSERVERS);
+            final int userId = mObservers.keyAt(userIdx);
+
+            proto.write(StateControllerProto.ContentObserverController.Observer.USER_ID, userId);
+
+            ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observersOfUser =
+                    mObservers.get(userId);
+            int numbOfObserversPerUser = observersOfUser.size();
+            for (int observerIdx = 0 ; observerIdx < numbOfObserversPerUser; observerIdx++) {
+                ObserverInstance obs = observersOfUser.valueAt(observerIdx);
+                int m = obs.mJobs.size();
+                boolean shouldDump = false;
+                for (int j = 0; j < m; j++) {
+                    JobInstance inst = obs.mJobs.valueAt(j);
+                    if (inst.mJobStatus.shouldDump(filterUid)) {
+                        shouldDump = true;
+                        break;
+                    }
+                }
+                if (!shouldDump) {
+                    continue;
+                }
+                final long tToken = proto.start(
+                        StateControllerProto.ContentObserverController.Observer.TRIGGERS);
+
+                JobInfo.TriggerContentUri trigger = observersOfUser.keyAt(observerIdx);
+                Uri u = trigger.getUri();
+                if (u != null) {
+                    proto.write(TriggerContentData.URI, u.toString());
+                }
+                proto.write(TriggerContentData.FLAGS, trigger.getFlags());
+
+                for (int j = 0; j < m; j++) {
+                    final long jToken = proto.start(TriggerContentData.JOBS);
+                    JobInstance inst = obs.mJobs.valueAt(j);
+
+                    inst.mJobStatus.writeToShortProto(proto, TriggerContentData.JobInstance.INFO);
+                    proto.write(TriggerContentData.JobInstance.SOURCE_UID,
+                            inst.mJobStatus.getSourceUid());
+
+                    if (inst.mChangedAuthorities == null) {
+                        proto.end(jToken);
+                        continue;
+                    }
+                    if (inst.mTriggerPending) {
+                        proto.write(TriggerContentData.JobInstance.TRIGGER_CONTENT_UPDATE_DELAY_MS,
+                                inst.mJobStatus.getTriggerContentUpdateDelay());
+                        proto.write(TriggerContentData.JobInstance.TRIGGER_CONTENT_MAX_DELAY_MS,
+                                inst.mJobStatus.getTriggerContentMaxDelay());
+                    }
+                    for (int k = 0; k < inst.mChangedAuthorities.size(); k++) {
+                        proto.write(TriggerContentData.JobInstance.CHANGED_AUTHORITIES,
+                                inst.mChangedAuthorities.valueAt(k));
+                    }
+                    if (inst.mChangedUris != null) {
+                        for (int k = 0; k < inst.mChangedUris.size(); k++) {
+                            u = inst.mChangedUris.valueAt(k);
+                            if (u != null) {
+                                proto.write(TriggerContentData.JobInstance.CHANGED_URIS,
+                                        u.toString());
+                            }
+                        }
+                    }
+
+                    proto.end(jToken);
+                }
+
+                proto.end(tToken);
+            }
+
+            proto.end(oToken);
+        }
+
+        proto.end(mToken);
+        proto.end(token);
+    }
 }
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 b7eb9e0..0dbcbee 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -29,12 +29,15 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.DeviceIdleController;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
+import com.android.server.job.StateControllerProto;
+import com.android.server.job.StateControllerProto.DeviceIdleJobsController.TrackedJob;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -270,6 +273,38 @@
         });
     }
 
+    @Override
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final long token = proto.start(fieldId);
+        final long mToken = proto.start(StateControllerProto.DEVICE_IDLE);
+
+        proto.write(StateControllerProto.DeviceIdleJobsController.IS_DEVICE_IDLE_MODE,
+                mDeviceIdleMode);
+        mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
+            @Override public void process(JobStatus jobStatus) {
+                if (!jobStatus.shouldDump(filterUid)) {
+                    return;
+                }
+                final long jsToken =
+                        proto.start(StateControllerProto.DeviceIdleJobsController.TRACKED_JOBS);
+
+                jobStatus.writeToShortProto(proto, TrackedJob.INFO);
+                proto.write(TrackedJob.SOURCE_UID, jobStatus.getSourceUid());
+                proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName());
+                proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED,
+                        (jobStatus.satisfiedConstraints &
+                            JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
+                proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.dozeWhitelisted);
+                proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus));
+
+                proto.end(jsToken);
+            }
+        });
+
+        proto.end(mToken);
+        proto.end(token);
+    }
+
     final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor {
         boolean mChanged;
 
@@ -300,4 +335,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 7bde174..a9bc7e0 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -27,10 +27,12 @@
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.server.am.ActivityManagerService;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
+import com.android.server.job.StateControllerProto;
 
 import java.io.PrintWriter;
 
@@ -216,4 +218,27 @@
             pw.println();
         }
     }
+
+    @Override
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final long token = proto.start(fieldId);
+        final long mToken = proto.start(StateControllerProto.IDLE);
+
+        proto.write(StateControllerProto.IdleController.IS_IDLE, mIdleTracker.isIdle());
+
+        for (int i = 0; i < mTrackedTasks.size(); i++) {
+            final JobStatus js = mTrackedTasks.valueAt(i);
+            if (!js.shouldDump(filterUid)) {
+                continue;
+            }
+            final long jsToken = proto.start(StateControllerProto.IdleController.TRACKED_JOBS);
+            js.writeToShortProto(proto, StateControllerProto.IdleController.TrackedJob.INFO);
+            proto.write(StateControllerProto.IdleController.TrackedJob.SOURCE_UID,
+                    js.getSourceUid());
+            proto.end(jsToken);
+        }
+
+        proto.end(mToken);
+        proto.end(token);
+    }
 }
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 e71b8ec..8f68713 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -33,11 +33,14 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.server.LocalServices;
 import com.android.server.job.GrantedUriPermissions;
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobStatusDumpProto;
+import com.android.server.job.JobStatusShortInfoProto;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -968,6 +971,20 @@
         return sb.toString();
     }
 
+    /**
+     * Convenience function to dump data that identifies a job uniquely to proto. This is intended
+     * to mimic {@link #toShortString}.
+     */
+    public void writeToShortProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        proto.write(JobStatusShortInfoProto.CALLING_UID, callingUid);
+        proto.write(JobStatusShortInfoProto.JOB_ID, job.getId());
+        proto.write(JobStatusShortInfoProto.BATTERY_NAME, batteryName);
+
+        proto.end(token);
+    }
+
     void dumpConstraints(PrintWriter pw, int constraints) {
         if ((constraints&CONSTRAINT_CHARGING) != 0) {
             pw.print(" CHARGING");
@@ -1001,6 +1018,40 @@
         }
     }
 
+    /** Writes constraints to the given repeating proto field. */
+    void dumpConstraints(ProtoOutputStream proto, long fieldId, int constraints) {
+        if ((constraints & CONSTRAINT_CHARGING) != 0) {
+            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_CHARGING);
+        }
+        if ((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0) {
+            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_BATTERY_NOT_LOW);
+        }
+        if ((constraints & CONSTRAINT_STORAGE_NOT_LOW) != 0) {
+            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_STORAGE_NOT_LOW);
+        }
+        if ((constraints & CONSTRAINT_TIMING_DELAY) != 0) {
+            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_TIMING_DELAY);
+        }
+        if ((constraints & CONSTRAINT_DEADLINE) != 0) {
+            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_DEADLINE);
+        }
+        if ((constraints & CONSTRAINT_IDLE) != 0) {
+            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_IDLE);
+        }
+        if ((constraints & CONSTRAINT_CONNECTIVITY) != 0) {
+            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_CONNECTIVITY);
+        }
+        if ((constraints & CONSTRAINT_APP_NOT_IDLE) != 0) {
+            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_APP_NOT_IDLE);
+        }
+        if ((constraints & CONSTRAINT_CONTENT_TRIGGER) != 0) {
+            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_CONTENT_TRIGGER);
+        }
+        if ((constraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
+            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_DEVICE_NOT_DOZING);
+        }
+    }
+
     private void dumpJobWorkItem(PrintWriter pw, String prefix, JobWorkItem work, int index) {
         pw.print(prefix); pw.print("  #"); pw.print(index); pw.print(": #");
         pw.print(work.getWorkId()); pw.print(" "); pw.print(work.getDeliveryCount());
@@ -1011,6 +1062,22 @@
         }
     }
 
+    private void dumpJobWorkItem(ProtoOutputStream proto, long fieldId, JobWorkItem work) {
+        final long token = proto.start(fieldId);
+
+        proto.write(JobStatusDumpProto.JobWorkItem.WORK_ID, work.getWorkId());
+        proto.write(JobStatusDumpProto.JobWorkItem.DELIVERY_COUNT, work.getDeliveryCount());
+        if (work.getIntent() != null) {
+            work.getIntent().writeToProto(proto, JobStatusDumpProto.JobWorkItem.INTENT);
+        }
+        Object grants = work.getGrants();
+        if (grants != null) {
+            ((GrantedUriPermissions) grants).dump(proto, JobStatusDumpProto.JobWorkItem.URI_GRANTS);
+        }
+
+        proto.end(token);
+    }
+
     // normalized bucket indices, not the AppStandby constants
     private String bucketName(int bucket) {
         switch (bucket) {
@@ -1198,4 +1265,168 @@
             pw.println(t.format(format));
         }
     }
+
+    public void dump(ProtoOutputStream proto, long fieldId, boolean full, long elapsedRealtimeMillis) {
+        final long token = proto.start(fieldId);
+
+        proto.write(JobStatusDumpProto.CALLING_UID, callingUid);
+        proto.write(JobStatusDumpProto.TAG, tag);
+        proto.write(JobStatusDumpProto.SOURCE_UID, getSourceUid());
+        proto.write(JobStatusDumpProto.SOURCE_USER_ID, getSourceUserId());
+        proto.write(JobStatusDumpProto.SOURCE_PACKAGE_NAME, getSourcePackageName());
+
+        if (full) {
+            final long jiToken = proto.start(JobStatusDumpProto.JOB_INFO);
+
+            job.getService().writeToProto(proto, JobStatusDumpProto.JobInfo.SERVICE);
+
+            proto.write(JobStatusDumpProto.JobInfo.IS_PERIODIC, job.isPeriodic());
+            proto.write(JobStatusDumpProto.JobInfo.PERIOD_INTERVAL_MS, job.getIntervalMillis());
+            proto.write(JobStatusDumpProto.JobInfo.PERIOD_FLEX_MS, job.getFlexMillis());
+
+            proto.write(JobStatusDumpProto.JobInfo.IS_PERSISTED, job.isPersisted());
+            proto.write(JobStatusDumpProto.JobInfo.PRIORITY, job.getPriority());
+            proto.write(JobStatusDumpProto.JobInfo.FLAGS, job.getFlags());
+
+            proto.write(JobStatusDumpProto.JobInfo.REQUIRES_CHARGING, job.isRequireCharging());
+            proto.write(JobStatusDumpProto.JobInfo.REQUIRES_BATTERY_NOT_LOW, job.isRequireBatteryNotLow());
+            proto.write(JobStatusDumpProto.JobInfo.REQUIRES_DEVICE_IDLE, job.isRequireDeviceIdle());
+
+            if (job.getTriggerContentUris() != null) {
+                for (int i = 0; i < job.getTriggerContentUris().length; i++) {
+                    final long tcuToken = proto.start(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_URIS);
+                    JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i];
+
+                    proto.write(JobStatusDumpProto.JobInfo.TriggerContentUri.FLAGS, trig.getFlags());
+                    Uri u = trig.getUri();
+                    if (u != null) {
+                        proto.write(JobStatusDumpProto.JobInfo.TriggerContentUri.URI, u.toString());
+                    }
+
+                    proto.end(tcuToken);
+                }
+                if (job.getTriggerContentUpdateDelay() >= 0) {
+                    proto.write(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_UPDATE_DELAY_MS,
+                            job.getTriggerContentUpdateDelay());
+                }
+                if (job.getTriggerContentMaxDelay() >= 0) {
+                    proto.write(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_MAX_DELAY_MS,
+                            job.getTriggerContentMaxDelay());
+                }
+            }
+            if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
+                job.getExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.EXTRAS);
+            }
+            if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
+                job.getTransientExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.TRANSIENT_EXTRAS);
+            }
+            if (job.getClipData() != null) {
+                job.getClipData().writeToProto(proto, JobStatusDumpProto.JobInfo.CLIP_DATA);
+            }
+            if (uriPerms != null) {
+                uriPerms.dump(proto, JobStatusDumpProto.JobInfo.GRANTED_URI_PERMISSIONS);
+            }
+            if (job.getRequiredNetwork() != null) {
+                job.getRequiredNetwork().writeToProto(proto, JobStatusDumpProto.JobInfo.REQUIRED_NETWORK);
+            }
+            if (totalNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_BYTES, totalNetworkBytes);
+            }
+            proto.write(JobStatusDumpProto.JobInfo.MIN_LATENCY_MS, job.getMinLatencyMillis());
+            proto.write(JobStatusDumpProto.JobInfo.MAX_EXECUTION_DELAY_MS, job.getMaxExecutionDelayMillis());
+
+            final long bpToken = proto.start(JobStatusDumpProto.JobInfo.BACKOFF_POLICY);
+            proto.write(JobStatusDumpProto.JobInfo.Backoff.POLICY, job.getBackoffPolicy());
+            proto.write(JobStatusDumpProto.JobInfo.Backoff.INITIAL_BACKOFF_MS,
+                    job.getInitialBackoffMillis());
+            proto.end(bpToken);
+
+            proto.write(JobStatusDumpProto.JobInfo.HAS_EARLY_CONSTRAINT, job.hasEarlyConstraint());
+            proto.write(JobStatusDumpProto.JobInfo.HAS_LATE_CONSTRAINT, job.hasLateConstraint());
+
+            proto.end(jiToken);
+        }
+
+        dumpConstraints(proto, JobStatusDumpProto.REQUIRED_CONSTRAINTS, requiredConstraints);
+        if (full) {
+            dumpConstraints(proto, JobStatusDumpProto.SATISFIED_CONSTRAINTS, satisfiedConstraints);
+            dumpConstraints(proto, JobStatusDumpProto.UNSATISFIED_CONSTRAINTS,
+                    (requiredConstraints & ~satisfiedConstraints));
+            proto.write(JobStatusDumpProto.IS_DOZE_WHITELISTED, dozeWhitelisted);
+        }
+
+        // Tracking controllers
+        if ((trackingControllers&TRACKING_BATTERY) != 0) {
+            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
+                    JobStatusDumpProto.TRACKING_BATTERY);
+        }
+        if ((trackingControllers&TRACKING_CONNECTIVITY) != 0) {
+            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
+                    JobStatusDumpProto.TRACKING_CONNECTIVITY);
+        }
+        if ((trackingControllers&TRACKING_CONTENT) != 0) {
+            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
+                    JobStatusDumpProto.TRACKING_CONTENT);
+        }
+        if ((trackingControllers&TRACKING_IDLE) != 0) {
+            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
+                    JobStatusDumpProto.TRACKING_IDLE);
+        }
+        if ((trackingControllers&TRACKING_STORAGE) != 0) {
+            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
+                    JobStatusDumpProto.TRACKING_STORAGE);
+        }
+        if ((trackingControllers&TRACKING_TIME) != 0) {
+            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
+                    JobStatusDumpProto.TRACKING_TIME);
+        }
+
+        if (changedAuthorities != null) {
+            for (int k = 0; k < changedAuthorities.size(); k++) {
+                proto.write(JobStatusDumpProto.CHANGED_AUTHORITIES, changedAuthorities.valueAt(k));
+            }
+        }
+        if (changedUris != null) {
+            for (int i = 0; i < changedUris.size(); i++) {
+                Uri u = changedUris.valueAt(i);
+                proto.write(JobStatusDumpProto.CHANGED_URIS, u.toString());
+            }
+        }
+
+        if (network != null) {
+            network.writeToProto(proto, JobStatusDumpProto.NETWORK);
+        }
+
+        if (pendingWork != null && pendingWork.size() > 0) {
+            for (int i = 0; i < pendingWork.size(); i++) {
+                dumpJobWorkItem(proto, JobStatusDumpProto.PENDING_WORK, pendingWork.get(i));
+            }
+        }
+        if (executingWork != null && executingWork.size() > 0) {
+            for (int i = 0; i < executingWork.size(); i++) {
+                dumpJobWorkItem(proto, JobStatusDumpProto.EXECUTING_WORK, executingWork.get(i));
+            }
+        }
+
+        proto.write(JobStatusDumpProto.STANDBY_BUCKET, standbyBucket);
+        proto.write(JobStatusDumpProto.ENQUEUE_DURATION_MS, elapsedRealtimeMillis - enqueueTime);
+        if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) {
+            proto.write(JobStatusDumpProto.TIME_UNTIL_EARLIEST_RUNTIME_MS, 0);
+        } else {
+            proto.write(JobStatusDumpProto.TIME_UNTIL_EARLIEST_RUNTIME_MS,
+                    earliestRunTimeElapsedMillis - elapsedRealtimeMillis);
+        }
+        if (latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
+            proto.write(JobStatusDumpProto.TIME_UNTIL_LATEST_RUNTIME_MS, 0);
+        } else {
+            proto.write(JobStatusDumpProto.TIME_UNTIL_LATEST_RUNTIME_MS,
+                    latestRunTimeElapsedMillis - elapsedRealtimeMillis);
+        }
+
+        proto.write(JobStatusDumpProto.NUM_FAILURES, numFailures);
+        proto.write(JobStatusDumpProto.LAST_SUCCESSFUL_RUN_TIME, mLastSuccessfulRunTime);
+        proto.write(JobStatusDumpProto.LAST_FAILED_RUN_TIME, mLastFailedRunTime);
+
+        proto.end(token);
+    }
 }
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index 497faab..d3055e6 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -17,6 +17,7 @@
 package com.android.server.job.controllers;
 
 import android.content.Context;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
@@ -65,4 +66,6 @@
     }
 
     public abstract void dumpControllerStateLocked(PrintWriter pw, int filterUid);
+    public abstract void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            int filterUid);
 }
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
index 84782f5..0519b63 100644
--- a/services/core/java/com/android/server/job/controllers/StorageController.java
+++ b/services/core/java/com/android/server/job/controllers/StorageController.java
@@ -25,10 +25,12 @@
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
+import com.android.server.job.StateControllerProto;
 import com.android.server.storage.DeviceStorageMonitorService;
 
 import java.io.PrintWriter;
@@ -119,7 +121,7 @@
          */
         private boolean mStorageLow;
         /** Sequence number of last broadcast. */
-        private int mLastBatterySeq = -1;
+        private int mLastStorageSeq = -1;
 
         public StorageTracker() {
         }
@@ -139,7 +141,7 @@
         }
 
         public int getSeq() {
-            return mLastBatterySeq;
+            return mLastStorageSeq;
         }
 
         @Override
@@ -150,8 +152,8 @@
         @VisibleForTesting
         public void onReceiveInternal(Intent intent) {
             final String action = intent.getAction();
-            mLastBatterySeq = intent.getIntExtra(DeviceStorageMonitorService.EXTRA_SEQUENCE,
-                    mLastBatterySeq);
+            mLastStorageSeq = intent.getIntExtra(DeviceStorageMonitorService.EXTRA_SEQUENCE,
+                    mLastStorageSeq);
             if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Available storage too low to do work. @ "
@@ -190,4 +192,30 @@
             pw.println();
         }
     }
+
+    @Override
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final long token = proto.start(fieldId);
+        final long mToken = proto.start(StateControllerProto.STORAGE);
+
+        proto.write(StateControllerProto.StorageController.IS_STORAGE_NOT_LOW,
+                mStorageTracker.isStorageNotLow());
+        proto.write(StateControllerProto.StorageController.LAST_BROADCAST_SEQUENCE_NUMBER,
+                mStorageTracker.getSeq());
+
+        for (int i = 0; i < mTrackedTasks.size(); i++) {
+            final JobStatus js = mTrackedTasks.valueAt(i);
+            if (!js.shouldDump(filterUid)) {
+                continue;
+            }
+            final long jsToken = proto.start(StateControllerProto.StorageController.TRACKED_JOBS);
+            js.writeToShortProto(proto, StateControllerProto.StorageController.TrackedJob.INFO);
+            proto.write(StateControllerProto.StorageController.TrackedJob.SOURCE_UID,
+                    js.getSourceUid());
+            proto.end(jsToken);
+        }
+
+        proto.end(mToken);
+        proto.end(token);
+    }
 }
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 cb9e43a..bbee0eb 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -25,9 +25,11 @@
 import android.os.WorkSource;
 import android.util.Slog;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
+import com.android.server.job.StateControllerProto;
 
 import java.io.PrintWriter;
 import java.util.Iterator;
@@ -331,7 +333,7 @@
     public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
         final long nowElapsed = sElapsedRealtimeClock.millis();
         pw.print("Alarms: now=");
-        pw.print(sElapsedRealtimeClock.millis());
+        pw.print(nowElapsed);
         pw.println();
         pw.print("Next delay alarm in ");
         TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
@@ -365,4 +367,40 @@
             pw.println();
         }
     }
-}
\ No newline at end of file
+
+    @Override
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+        final long token = proto.start(fieldId);
+        final long mToken = proto.start(StateControllerProto.TIME);
+
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        proto.write(StateControllerProto.TimeController.NOW_ELAPSED_REALTIME, nowElapsed);
+        proto.write(StateControllerProto.TimeController.TIME_UNTIL_NEXT_DELAY_ALARM_MS,
+                mNextDelayExpiredElapsedMillis - nowElapsed);
+        proto.write(StateControllerProto.TimeController.TIME_UNTIL_NEXT_DEADLINE_ALARM_MS,
+                mNextJobExpiredElapsedMillis - nowElapsed);
+
+        for (JobStatus ts : mTrackedJobs) {
+            if (!ts.shouldDump(filterUid)) {
+                continue;
+            }
+            final long tsToken = proto.start(StateControllerProto.TimeController.TRACKED_JOBS);
+            ts.writeToShortProto(proto, StateControllerProto.TimeController.TrackedJob.INFO);
+
+            proto.write(StateControllerProto.TimeController.TrackedJob.HAS_TIMING_DELAY_CONSTRAINT,
+                    ts.hasTimingDelayConstraint());
+            proto.write(StateControllerProto.TimeController.TrackedJob.DELAY_TIME_REMAINING_MS,
+                    ts.getEarliestRunTime() - nowElapsed);
+
+            proto.write(StateControllerProto.TimeController.TrackedJob.HAS_DEADLINE_CONSTRAINT,
+                    ts.hasDeadlineConstraint());
+            proto.write(StateControllerProto.TimeController.TrackedJob.TIME_REMAINING_UNTIL_DEADLINE_MS,
+                    ts.getLatestRunTimeElapsed() - nowElapsed);
+
+            proto.end(tsToken);
+        }
+
+        proto.end(mToken);
+        proto.end(token);
+    }
+}
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
index c356b63..033437a 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -221,7 +221,7 @@
             case Result.NOT_INIT:
                 return ContextHubTransaction.RESULT_FAILED_UNINITIALIZED;
             case Result.TRANSACTION_PENDING:
-                return ContextHubTransaction.RESULT_FAILED_PENDING;
+                return ContextHubTransaction.RESULT_FAILED_BUSY;
             case Result.TRANSACTION_FAILED:
             case Result.UNKNOWN_FAILURE:
             default: /* fall through */
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 3bd6446..e6de07d 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -414,16 +414,16 @@
     private WorkSource mClientSource = new WorkSource();
 
     private GeofenceHardwareImpl mGeofenceHardwareImpl;
-    private int mYearOfHardware = 0;
+
+    // Volatile for simple inter-thread sync on these values.
+    private volatile int mHardwareYear = 0;
+    private volatile String mHardwareModelName = LocationManager.GNSS_HARDWARE_MODEL_NAME_UNKNOWN;
 
     // Set lower than the current ITAR limit of 600m/s to allow this to trigger even if GPS HAL
     // 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;
 
-    // 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
@@ -1833,33 +1833,53 @@
     /**
      * called from native code to inform us what the GPS engine capabilities are
      */
-    private void setEngineCapabilities(int capabilities) {
-        mEngineCapabilities = capabilities;
+    private void setEngineCapabilities(final int capabilities) {
+        // send to handler thread for fast native return, and in-order handling
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mEngineCapabilities = capabilities;
 
-        if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) {
-            mOnDemandTimeInjection = true;
-            requestUtcTime();
-        }
+                if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) {
+                    mOnDemandTimeInjection = true;
+                    requestUtcTime();
+                }
 
-        mGnssMeasurementsProvider.onCapabilitiesUpdated(
-                (capabilities & GPS_CAPABILITY_MEASUREMENTS) == GPS_CAPABILITY_MEASUREMENTS);
-        mGnssNavigationMessageProvider.onCapabilitiesUpdated(
-                (capabilities & GPS_CAPABILITY_NAV_MESSAGES) == GPS_CAPABILITY_NAV_MESSAGES);
+                mGnssMeasurementsProvider.onCapabilitiesUpdated(hasCapability(
+                        GPS_CAPABILITY_MEASUREMENTS));
+                mGnssNavigationMessageProvider.onCapabilitiesUpdated(hasCapability(
+                        GPS_CAPABILITY_NAV_MESSAGES));
+            }
+        });
+   }
+
+    /**
+     * Called from native code to inform us the hardware year.
+     */
+    private void setGnssYearOfHardware(final int yearOfHardware) {
+        // mHardwareYear is simply set here, to be read elsewhere, and is volatile for safe sync
+        if (DEBUG) Log.d(TAG, "setGnssYearOfHardware called with " + yearOfHardware);
+        mHardwareYear = yearOfHardware;
     }
 
     /**
-     * Called from native code to inform us the hardware information.
+     * Called from native code to inform us the hardware model name.
      */
-    private void setGnssYearOfHardware(int yearOfHardware) {
-        if (DEBUG) Log.d(TAG, "setGnssYearOfHardware called with " + yearOfHardware);
-        mYearOfHardware = yearOfHardware;
+    private void setGnssHardwareModelName(final String modelName) {
+        // mHardwareModelName is simply set here, to be read elsewhere, and volatile for safe sync
+        if (DEBUG) Log.d(TAG, "setGnssModelName called with " + modelName);
+        mHardwareModelName = modelName;
     }
 
     public interface GnssSystemInfoProvider {
         /**
-         * Returns the year of GPS hardware.
+         * Returns the year of underlying GPS hardware.
          */
         int getGnssYearOfHardware();
+        /**
+         * Returns the model name of underlying GPS hardware.
+         */
+        String getGnssHardwareModelName();
     }
 
     /**
@@ -1869,7 +1889,11 @@
         return new GnssSystemInfoProvider() {
             @Override
             public int getGnssYearOfHardware() {
-                return mYearOfHardware;
+                return mHardwareYear;
+            }
+            @Override
+            public String getGnssHardwareModelName() {
+                return mHardwareModelName;
             }
         };
     }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index eef4d9b..516828b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -912,8 +912,11 @@
     }
 
     private void notifySeparateProfileChallengeChanged(int userId) {
-        LocalServices.getService(DevicePolicyManagerInternal.class)
-                .reportSeparateProfileChallengeChanged(userId);
+        final DevicePolicyManagerInternal dpmi = LocalServices.getService(
+                DevicePolicyManagerInternal.class);
+        if (dpmi != null) {
+            dpmi.reportSeparateProfileChallengeChanged(userId);
+        }
     }
 
     @Override
@@ -1284,6 +1287,7 @@
             fixateNewestUserKeyAuth(userId);
             synchronizeUnifiedWorkChallengeForProfiles(userId, null);
             notifyActivePasswordMetricsAvailable(null, userId);
+            mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId);
             return;
         }
         if (credential == null) {
@@ -1333,6 +1337,8 @@
                     .verifyChallenge(userId, 0, willStore.hash, credential.getBytes());
             setUserKeyProtection(userId, credential, convertResponse(gkResponse));
             fixateNewestUserKeyAuth(userId);
+            mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential,
+                userId);
             // Refresh the auth token
             doVerifyCredential(credential, credentialType, true, 0, userId, null /* progressCallback */);
             synchronizeUnifiedWorkChallengeForProfiles(userId, null);
@@ -1581,6 +1587,8 @@
                 userId, progressCallback);
         // The user employs synthetic password based credential.
         if (response != null) {
+            mRecoverableKeyStoreManager.lockScreenSecretAvailable(credentialType, credential,
+                    userId);
             return response;
         }
 
@@ -1705,6 +1713,9 @@
                                 /* TODO(roosa): keep the same password quality */, userId);
                 if (!hasChallenge) {
                     notifyActivePasswordMetricsAvailable(credential, userId);
+                    // Use credentials to create recoverable keystore snapshot.
+                    mRecoverableKeyStoreManager.lockScreenSecretAvailable(
+                            storedHash.type, credential, userId);
                     return VerifyCredentialResponse.OK;
                 }
                 // Fall through to get the auth token. Technically this should never happen,
@@ -1951,81 +1962,88 @@
 
     @Override
     public void initRecoveryService(@NonNull String rootCertificateAlias,
-            @NonNull byte[] signedPublicKeyList, @UserIdInt int userId)
-            throws RemoteException {
+            @NonNull byte[] signedPublicKeyList) throws RemoteException {
         mRecoverableKeyStoreManager.initRecoveryService(rootCertificateAlias,
-                signedPublicKeyList, userId);
+                signedPublicKeyList);
     }
 
     @Override
-    public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, @UserIdInt int userId)
-            throws RemoteException {
-        return mRecoverableKeyStoreManager.getRecoveryData(account, userId);
+    public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RemoteException {
+        return mRecoverableKeyStoreManager.getRecoveryData(account);
     }
 
-    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
             throws RemoteException {
-        mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, userId);
+        mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent);
     }
 
-    public Map getRecoverySnapshotVersions(int userId) throws RemoteException {
-        return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(userId);
+    public Map getRecoverySnapshotVersions() throws RemoteException {
+        return mRecoverableKeyStoreManager.getRecoverySnapshotVersions();
     }
 
     @Override
-    public void setServerParameters(long serverParameters, @UserIdInt int userId)
-            throws RemoteException {
-        mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId);
+    public void setServerParameters(long serverParameters) throws RemoteException {
+        mRecoverableKeyStoreManager.setServerParameters(serverParameters);
     }
 
     @Override
     public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases,
-            int status, @UserIdInt int userId) throws RemoteException {
-        mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId);
+            int status) throws RemoteException {
+        mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status);
     }
 
-    public Map getRecoveryStatus(@Nullable String packageName, int userId) throws RemoteException {
-        return mRecoverableKeyStoreManager.getRecoveryStatus(packageName, userId);
+    public Map getRecoveryStatus(@Nullable String packageName) throws RemoteException {
+        return mRecoverableKeyStoreManager.getRecoveryStatus(packageName);
     }
 
     @Override
     public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
-            int[] secretTypes, @UserIdInt int userId) throws RemoteException {
-        mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes, userId);
+            int[] secretTypes) throws RemoteException {
+        mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
     }
 
     @Override
-    public int[] getRecoverySecretTypes(@UserIdInt int userId) throws RemoteException {
-        return mRecoverableKeyStoreManager.getRecoverySecretTypes(userId);
+    public int[] getRecoverySecretTypes() throws RemoteException {
+        return mRecoverableKeyStoreManager.getRecoverySecretTypes();
 
     }
 
     @Override
-    public int[] getPendingRecoverySecretTypes(@UserIdInt int userId) throws RemoteException {
+    public int[] getPendingRecoverySecretTypes() throws RemoteException {
         throw new SecurityException("Not implemented");
     }
 
     @Override
-    public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret,
-            @UserIdInt int userId) throws RemoteException {
-        mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret, userId);
+    public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret)
+            throws RemoteException {
+        mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret);
     }
 
     @Override
     public byte[] startRecoverySession(@NonNull String sessionId,
             @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,
-            @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets,
-            @UserIdInt int userId) throws RemoteException {
+            @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets)
+            throws RemoteException {
         return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey,
-                vaultParams, vaultChallenge, secrets, userId);
+                vaultParams, vaultChallenge, secrets);
     }
 
     @Override
-    public void recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob,
-            @NonNull List<KeyEntryRecoveryData> applicationKeys, @UserIdInt int userId)
+    public Map<String, byte[]> recoverKeys(@NonNull String sessionId,
+            @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys)
             throws RemoteException {
-        mRecoverableKeyStoreManager.recoverKeys(sessionId, recoveryKeyBlob, applicationKeys,
-                userId);
+        return mRecoverableKeyStoreManager.recoverKeys(
+                sessionId, recoveryKeyBlob, applicationKeys);
+    }
+
+    @Override
+    public void removeKey(@NonNull String alias) throws RemoteException {
+        mRecoverableKeyStoreManager.removeKey(alias);
+    }
+
+    @Override
+    public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
+        return mRecoverableKeyStoreManager.generateAndStoreKey(alias);
     }
 
     private static final String[] VALID_SETTINGS = new String[] {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
deleted file mode 100644
index 9a4d051..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
+++ /dev/null
@@ -1,34 +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.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.AndroidKeyStoreProvider;
-
-import java.security.KeyStoreException;
-import java.security.NoSuchProviderException;
-
-public interface AndroidKeyStoreFactory {
-    KeyStoreProxy getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException;
-
-    class Impl implements AndroidKeyStoreFactory {
-        @Override
-        public KeyStoreProxy getKeyStoreForUid(int uid)
-                throws KeyStoreException, NoSuchProviderException {
-            return new KeyStoreProxyImpl(AndroidKeyStoreProvider.getKeyStoreForUid(uid));
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
new file mode 100644
index 0000000..e028fef
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.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 com.android.server.locksettings.recoverablekeystore;
+
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.security.recoverablekeystore.KeyDerivationParameters;
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+/**
+ * Task to sync application keys to a remote vault service.
+ *
+ * @hide
+ */
+public class KeySyncTask implements Runnable {
+    private static final String TAG = "KeySyncTask";
+
+    private static final String RECOVERY_KEY_ALGORITHM = "AES";
+    private static final int RECOVERY_KEY_SIZE_BITS = 256;
+    private static final int SALT_LENGTH_BYTES = 16;
+    private static final int LENGTH_PREFIX_BYTES = Integer.BYTES;
+    private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256";
+    private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10;
+
+    private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private final int mUserId;
+    private final int mCredentialType;
+    private final String mCredential;
+    private final boolean mCredentialUpdated;
+    private final PlatformKeyManager.Factory mPlatformKeyManagerFactory;
+    private final RecoverySnapshotStorage mRecoverySnapshotStorage;
+    private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
+
+    public static KeySyncTask newInstance(
+            Context context,
+            RecoverableKeyStoreDb recoverableKeyStoreDb,
+            RecoverySnapshotStorage snapshotStorage,
+            RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
+            int userId,
+            int credentialType,
+            String credential,
+            boolean credentialUpdated
+    ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
+        return new KeySyncTask(
+                recoverableKeyStoreDb,
+                snapshotStorage,
+                recoverySnapshotListenersStorage,
+                userId,
+                credentialType,
+                credential,
+                credentialUpdated,
+                () -> PlatformKeyManager.getInstance(context, recoverableKeyStoreDb));
+    }
+
+    /**
+     * A new task.
+     *
+     * @param recoverableKeyStoreDb Database where the keys are stored.
+     * @param userId The uid of the user whose profile has been unlocked.
+     * @param credentialType The type of credential - i.e., pattern or password.
+     * @param credential The credential, encoded as a {@link String}.
+     * @param credentialUpdated signals weather credentials were updated.
+     * @param platformKeyManagerFactory Instantiates a {@link PlatformKeyManager} for the user.
+     *     This is a factory to enable unit testing, as otherwise it would be impossible to test
+     *     without a screen unlock occurring!
+     */
+    @VisibleForTesting
+    KeySyncTask(
+            RecoverableKeyStoreDb recoverableKeyStoreDb,
+            RecoverySnapshotStorage snapshotStorage,
+            RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
+            int userId,
+            int credentialType,
+            String credential,
+            boolean credentialUpdated,
+            PlatformKeyManager.Factory platformKeyManagerFactory) {
+        mSnapshotListenersStorage = recoverySnapshotListenersStorage;
+        mRecoverableKeyStoreDb = recoverableKeyStoreDb;
+        mUserId = userId;
+        mCredentialType = credentialType;
+        mCredential = credential;
+        mCredentialUpdated = credentialUpdated;
+        mPlatformKeyManagerFactory = platformKeyManagerFactory;
+        mRecoverySnapshotStorage = snapshotStorage;
+    }
+
+    @Override
+    public void run() {
+        try {
+            // Only one task is active If user unlocks phone many times in a short time interval.
+            synchronized(KeySyncTask.class) {
+                syncKeys();
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
+        }
+    }
+
+    private void syncKeys() {
+        if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+            // Application keys for the user will not be available for sync.
+            Log.w(TAG, "Credentials are not set for user " + mUserId);
+            return;
+        }
+
+        List<Integer> recoveryAgents = mRecoverableKeyStoreDb.getRecoveryAgents(mUserId);
+        for (int uid : recoveryAgents) {
+            syncKeysForAgent(uid);
+        }
+        if (recoveryAgents.isEmpty()) {
+            Log.w(TAG, "No recovery agent initialized for user " + mUserId);
+        }
+    }
+
+    private void syncKeysForAgent(int recoveryAgentUid) {
+        if (!shoudCreateSnapshot(recoveryAgentUid)) {
+            Log.d(TAG, "Key sync not needed.");
+            return;
+        }
+
+        if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) {
+            Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid);
+            return;
+        }
+
+        PublicKey publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
+                recoveryAgentUid);
+        if (publicKey == null) {
+            Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task.");
+            return;
+        }
+
+        Long deviceId = mRecoverableKeyStoreDb.getServerParameters(mUserId, recoveryAgentUid);
+        if (deviceId == null) {
+            Log.w(TAG, "No device ID set for user " + mUserId);
+            return;
+        }
+
+        byte[] salt = generateSalt();
+        byte[] localLskfHash = hashCredentials(salt, mCredential);
+
+        Map<String, SecretKey> rawKeys;
+        try {
+            rawKeys = getKeysToSync(recoveryAgentUid);
+        } catch (GeneralSecurityException e) {
+            Log.e(TAG, "Failed to load recoverable keys for sync", e);
+            return;
+        } catch (InsecureUserException e) {
+            Log.wtf(TAG, "A screen unlock triggered the key sync flow, so user must have "
+                    + "lock screen. This should be impossible.", e);
+            return;
+        } catch (BadPlatformKeyException e) {
+            Log.wtf(TAG, "Loaded keys for same generation ID as platform key, so "
+                    + "BadPlatformKeyException should be impossible.", e);
+            return;
+        }
+
+        SecretKey recoveryKey;
+        try {
+            recoveryKey = generateRecoveryKey();
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf("AES should never be unavailable", e);
+            return;
+        }
+
+        Map<String, byte[]> encryptedApplicationKeys;
+        try {
+            encryptedApplicationKeys = KeySyncUtils.encryptKeysWithRecoveryKey(
+                    recoveryKey, rawKeys);
+        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
+            Log.wtf(TAG,
+                    "Should be impossible: could not encrypt application keys with random key",
+                    e);
+            return;
+        }
+
+        Long counterId;
+        // counter id is generated exactly once for each credentials value.
+        if (mCredentialUpdated) {
+            counterId = generateAndStoreCounterId(recoveryAgentUid);
+        } else {
+            counterId = mRecoverableKeyStoreDb.getCounterId(mUserId, recoveryAgentUid);
+            if (counterId == null) {
+                counterId = generateAndStoreCounterId(recoveryAgentUid);
+            }
+        }
+        byte[] vaultParams = KeySyncUtils.packVaultParams(
+                publicKey,
+                counterId,
+                TRUSTED_HARDWARE_MAX_ATTEMPTS,
+                deviceId);
+
+        byte[] encryptedRecoveryKey;
+        try {
+            encryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey(
+                    publicKey,
+                    localLskfHash,
+                    vaultParams,
+                    recoveryKey);
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e);
+            return;
+        } catch (InvalidKeyException e) {
+            Log.e(TAG,"Could not encrypt with recovery key", e);
+            return;
+        }
+        // TODO: store raw data in RecoveryServiceMetadataEntry and generate Parcelables later
+        KeyStoreRecoveryMetadata metadata = new KeyStoreRecoveryMetadata(
+                /*userSecretType=*/ TYPE_LOCKSCREEN,
+                /*lockScreenUiFormat=*/ mCredentialType,
+                /*keyDerivationParameters=*/ KeyDerivationParameters.createSha256Parameters(salt),
+                /*secret=*/ new byte[0]);
+        ArrayList<KeyStoreRecoveryMetadata> metadataList = new ArrayList<>();
+        metadataList.add(metadata);
+
+        int snapshotVersion = incrementSnapshotVersion(recoveryAgentUid);
+
+        // If application keys are not updated, snapshot will not be created on next unlock.
+        mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);
+
+        mRecoverySnapshotStorage.put(recoveryAgentUid, new KeyStoreRecoveryData(
+                snapshotVersion,
+                /*recoveryMetadata=*/ metadataList,
+                /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys),
+                /*encryptedRecoveryKeyblob=*/ encryptedRecoveryKey));
+
+        mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
+    }
+
+    @VisibleForTesting
+    int incrementSnapshotVersion(int recoveryAgentUid) {
+        Long snapshotVersion = mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid);
+        snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion + 1;
+        mRecoverableKeyStoreDb.setSnapshotVersion(mUserId, recoveryAgentUid, snapshotVersion);
+
+        return snapshotVersion.intValue();
+    }
+
+    private long generateAndStoreCounterId(int recoveryAgentUid) {
+        long counter = new SecureRandom().nextLong();
+        mRecoverableKeyStoreDb.setCounterId(mUserId, recoveryAgentUid, counter);
+        return counter;
+    }
+
+    /**
+     * Returns all of the recoverable keys for the user.
+     */
+    private Map<String, SecretKey> getKeysToSync(int recoveryAgentUid)
+            throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
+            NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException {
+        PlatformKeyManager platformKeyManager = mPlatformKeyManagerFactory.newInstance();
+        PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey(mUserId);
+        Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
+                mUserId, recoveryAgentUid, decryptKey.getGenerationId());
+        return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
+    }
+
+    /**
+     * Returns {@code true} if a sync is pending.
+     * @param recoveryAgentUid uid of the recovery agent.
+     */
+    private boolean shoudCreateSnapshot(int recoveryAgentUid) {
+        if (mCredentialUpdated) {
+            // Sync credential if at least one snapshot was created.
+            if (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null) {
+                mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, true);
+                return true;
+            }
+        }
+
+        return mRecoverableKeyStoreDb.getShouldCreateSnapshot(mUserId, recoveryAgentUid);
+    }
+
+    /**
+     * The UI best suited to entering the given lock screen. This is synced with the vault so the
+     * user can be shown the same UI when recovering the vault on another device.
+     *
+     * @return The format - either pattern, pin, or password.
+     */
+    @VisibleForTesting
+    @KeyStoreRecoveryMetadata.LockScreenUiFormat static int getUiFormat(
+            int credentialType, String credential) {
+        if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
+            return KeyStoreRecoveryMetadata.TYPE_PATTERN;
+        } else if (isPin(credential)) {
+            return KeyStoreRecoveryMetadata.TYPE_PIN;
+        } else {
+            return KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+        }
+    }
+
+    /**
+     * Generates a salt to include with the lock screen hash.
+     *
+     * @return The salt.
+     */
+    private byte[] generateSalt() {
+        byte[] salt = new byte[SALT_LENGTH_BYTES];
+        new SecureRandom().nextBytes(salt);
+        return salt;
+    }
+
+    /**
+     * Returns {@code true} if {@code credential} looks like a pin.
+     */
+    @VisibleForTesting
+    static boolean isPin(@NonNull String credential) {
+        int length = credential.length();
+        for (int i = 0; i < length; i++) {
+            if (!Character.isDigit(credential.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Hashes {@code credentials} with the given {@code salt}.
+     *
+     * @return The SHA-256 hash.
+     */
+    @VisibleForTesting
+    static byte[] hashCredentials(byte[] salt, String credentials) {
+        byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
+        ByteBuffer byteBuffer = ByteBuffer.allocate(
+                salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        byteBuffer.putInt(salt.length);
+        byteBuffer.put(salt);
+        byteBuffer.putInt(credentialsBytes.length);
+        byteBuffer.put(credentialsBytes);
+        byte[] bytes = byteBuffer.array();
+
+        try {
+            return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
+        } catch (NoSuchAlgorithmException e) {
+            // Impossible, SHA-256 must be supported on Android.
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
+        keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
+        return keyGenerator.generateKey();
+    }
+
+    private static List<KeyEntryRecoveryData> createApplicationKeyEntries(
+            Map<String, byte[]> encryptedApplicationKeys) {
+        ArrayList<KeyEntryRecoveryData> keyEntries = new ArrayList<>();
+        for (String alias : encryptedApplicationKeys.keySet()) {
+            keyEntries.add(
+                    new KeyEntryRecoveryData(
+                            alias,
+                            encryptedApplicationKeys.get(alias)));
+        }
+        return keyEntries;
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index 74e25e4..e851d8c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -18,6 +18,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
 import java.security.KeyFactory;
@@ -54,10 +56,13 @@
             "V1 encrypted_application_key".getBytes(StandardCharsets.UTF_8);
     private static final byte[] RECOVERY_CLAIM_HEADER =
             "V1 KF_claim".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] RECOVERY_RESPONSE_HEADER =
+            "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
 
     private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8);
 
     private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+    private static final int VAULT_PARAMS_LENGTH_BYTES = 85;
 
     /**
      * Encrypts the recovery key using both the lock screen hash and the remote storage's public
@@ -74,7 +79,7 @@
      *
      * @hide
      */
-    public byte[] thmEncryptRecoveryKey(
+    public static byte[] thmEncryptRecoveryKey(
             PublicKey publicKey,
             byte[] lockScreenHash,
             byte[] vaultParams,
@@ -204,6 +209,28 @@
     }
 
     /**
+     * Decrypts response from recovery claim, returning the locally encrypted key.
+     *
+     * @param keyClaimant The key claimant, used by the remote service to encrypt the response.
+     * @param vaultParams Vault params associated with the claim.
+     * @param encryptedResponse The encrypted response.
+     * @return The locally encrypted recovery key.
+     * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present.
+     * @throws InvalidKeyException if the {@code keyClaimant} could not be used to decrypt.
+     * @throws AEADBadTagException if the message has been tampered with or was encrypted with a
+     *     different key.
+     */
+    public static byte[] decryptRecoveryClaimResponse(
+            byte[] keyClaimant, byte[] vaultParams, byte[] encryptedResponse)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        return SecureBox.decrypt(
+                /*ourPrivateKey=*/ null,
+                /*sharedSecret=*/ keyClaimant,
+                /*header=*/ concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*encryptedPayload=*/ encryptedResponse);
+    }
+
+    /**
      * Decrypts a recovery key, after having retrieved it from a remote server.
      *
      * @param lskfHash The lock screen hash associated with the key.
@@ -257,6 +284,26 @@
     }
 
     /**
+     * Packs vault params into a binary format.
+     *
+     * @param thmPublicKey Public key of the trusted hardware module.
+     * @param counterId ID referring to the specific counter in the hardware module.
+     * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key.
+     * @param deviceId ID of the device.
+     * @return The binary vault params, ready for sync.
+     */
+    public static byte[] packVaultParams(
+            PublicKey thmPublicKey, long counterId, int maxAttempts, long deviceId) {
+        return ByteBuffer.allocate(VAULT_PARAMS_LENGTH_BYTES)
+                .order(ByteOrder.LITTLE_ENDIAN)
+                .put(SecureBox.encodePublicKey(thmPublicKey))
+                .putLong(counterId)
+                .putLong(deviceId)
+                .putInt(maxAttempts)
+                .array();
+    }
+
+    /**
      * Returns the concatenation of all the given {@code arrays}.
      */
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
deleted file mode 100644
index 0f17294..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
+++ /dev/null
@@ -1,68 +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.server.locksettings.recoverablekeystore;
-
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Map;
-import java.util.HashMap;
-
-/**
- * In memory storage for listeners to be notified when new recovery snapshot is available.
- * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent}
- * class.
- *
- * @hide
- */
-public class ListenersStorage {
-    private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>();
-
-    private static final ListenersStorage mInstance = new ListenersStorage();
-    public static ListenersStorage getInstance() {
-        return mInstance;
-    }
-
-    /**
-     * Sets new listener for the recovery agent, identified by {@code uid}
-     *
-     * @param recoveryAgentUid uid
-     * @param intent PendingIntent which will be triggered than new snapshot is available.
-     */
-    public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) {
-        mAgentIntents.put(recoveryAgentUid, intent);
-    }
-
-    /**
-     * Notifies recovery agent, that new snapshot is available.
-     * Does nothing if a listener was not registered.
-     *
-     * @param recoveryAgentUid uid.
-     */
-    public void recoverySnapshotAvailable(int recoveryAgentUid) {
-        PendingIntent intent = mAgentIntents.get(recoveryAgentUid);
-        if (intent != null) {
-            try {
-                intent.send();
-            } catch (PendingIntent.CanceledException e) {
-                // Ignore - sending intent is not allowed.
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 24f3f65..7005de5 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -71,7 +71,6 @@
     private final Context mContext;
     private final KeyStoreProxy mKeyStore;
     private final RecoverableKeyStoreDb mDatabase;
-    private final int mUserId;
 
     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
 
@@ -80,33 +79,25 @@
      * defined by {@code context}.
      *
      * @param context This should be the context of the RecoverableKeyStoreLoader service.
-     * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @throws KeyStoreException if failed to initialize AndroidKeyStore.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws SecurityException if the caller does not have permission to write to /data/system.
      *
      * @hide
      */
-    public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, int userId)
-            throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
-        context = context.getApplicationContext();
-        PlatformKeyManager keyManager = new PlatformKeyManager(
-                userId,
-                context,
+    public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database)
+            throws KeyStoreException, NoSuchAlgorithmException {
+        return new PlatformKeyManager(
+                context.getApplicationContext(),
                 new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
                 database);
-        keyManager.init();
-        return keyManager;
     }
 
     @VisibleForTesting
     PlatformKeyManager(
-            int userId,
             Context context,
             KeyStoreProxy keyStore,
             RecoverableKeyStoreDb database) {
-        mUserId = userId;
         mKeyStore = keyStore;
         mContext = context;
         mDatabase = database;
@@ -115,74 +106,93 @@
     /**
      * Returns the current generation ID of the platform key. This increments whenever a platform
      * key has to be replaced. (e.g., because the user has removed and then re-added their lock
-     * screen).
+     * screen). Returns -1 if no key has been generated yet.
+     *
+     * @param userId The ID of the user to whose lock screen the platform key must be bound.
      *
      * @hide
      */
-    public int getGenerationId() {
-        int generationId = mDatabase.getPlatformKeyGenerationId(mUserId);
-        if (generationId == -1) {
-            return 1;
-        }
-        return generationId;
+    public int getGenerationId(int userId) {
+        return mDatabase.getPlatformKeyGenerationId(userId);
     }
 
     /**
      * Returns {@code true} if the platform key is available. A platform key won't be available if
      * the user has not set up a lock screen.
      *
+     * @param userId The ID of the user to whose lock screen the platform key must be bound.
+     *
      * @hide
      */
-    public boolean isAvailable() {
-        return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mUserId);
+    public boolean isAvailable(int userId) {
+        return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
     }
 
     /**
      * Generates a new key and increments the generation ID. Should be invoked if the platform key
      * is corrupted and needs to be rotated.
      *
+     * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
      * @throws KeyStoreException if there is an error in AndroidKeyStore.
+     * @throws InsecureUserException if the user does not have a lock screen set.
      *
      * @hide
      */
-    public void regenerate() throws NoSuchAlgorithmException, KeyStoreException {
-        int nextId = getGenerationId() + 1;
-        generateAndLoadKey(nextId);
-        setGenerationId(nextId);
+    public void regenerate(int userId)
+            throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
+        if (!isAvailable(userId)) {
+            throw new InsecureUserException(String.format(
+                    Locale.US, "%d does not have a lock screen set.", userId));
+        }
+
+        int generationId = getGenerationId(userId);
+        int nextId;
+        if (generationId == -1) {
+            nextId = 1;
+        } else {
+            nextId = generationId + 1;
+        }
+        generateAndLoadKey(userId, nextId);
     }
 
     /**
      * Returns the platform key used for encryption.
      *
+     * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
+     * @throws InsecureUserException if the user does not have a lock screen set.
      *
      * @hide
      */
-    public PlatformEncryptionKey getEncryptKey()
-            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
-        int generationId = getGenerationId();
+    public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException,
+           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+        init(userId);
+        int generationId = getGenerationId(userId);
         AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
-                getEncryptAlias(generationId), /*password=*/ null);
+                getEncryptAlias(userId, generationId), /*password=*/ null);
         return new PlatformEncryptionKey(generationId, key);
     }
 
     /**
      * Returns the platform key used for decryption. Only works after a recent screen unlock.
      *
+     * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
+     * @throws InsecureUserException if the user does not have a lock screen set.
      *
      * @hide
      */
-    public PlatformDecryptionKey getDecryptKey()
-            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
-        int generationId = getGenerationId();
+    public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException,
+           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+        init(userId);
+        int generationId = getGenerationId(userId);
         AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
-                getDecryptAlias(generationId), /*password=*/ null);
+                getDecryptAlias(userId, generationId), /*password=*/ null);
         return new PlatformDecryptionKey(generationId, key);
     }
 
@@ -190,31 +200,36 @@
      * Initializes the class. If there is no current platform key, and the user has a lock screen
      * set, will create the platform key and set the generation ID.
      *
+     * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @throws KeyStoreException if there was an error in AndroidKeyStore.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
      *
      * @hide
      */
-    public void init() throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
-        if (!isAvailable()) {
+    void init(int userId)
+            throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
+        if (!isAvailable(userId)) {
             throw new InsecureUserException(String.format(
-                    Locale.US, "%d does not have a lock screen set.", mUserId));
+                    Locale.US, "%d does not have a lock screen set.", userId));
         }
 
-        int generationId = getGenerationId();
-        if (isKeyLoaded(generationId)) {
+        int generationId = getGenerationId(userId);
+        if (isKeyLoaded(userId, generationId)) {
             Log.i(TAG, String.format(
                     Locale.US, "Platform key generation %d exists already.", generationId));
             return;
         }
-        if (generationId == 1) {
+        if (generationId == -1) {
             Log.i(TAG, "Generating initial platform ID.");
+            generationId = 1;
         } else {
             Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
                     + "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
+            // Had to generate a fresh key, bump the generation id
+            generationId++;
         }
 
-        generateAndLoadKey(generationId);
+        generateAndLoadKey(userId, generationId);
     }
 
     /**
@@ -224,11 +239,12 @@
      * <p>These IDs look as follows:
      * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/encrypt}
      *
+     * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @param generationId The generation ID.
      * @return The alias.
      */
-    private String getEncryptAlias(int generationId) {
-        return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
+    private String getEncryptAlias(int userId, int generationId) {
+        return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
     }
 
     /**
@@ -238,18 +254,19 @@
      * <p>These IDs look as follows:
      * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt}
      *
+     * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @param generationId The generation ID.
      * @return The alias.
      */
-    private String getDecryptAlias(int generationId) {
-        return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
+    private String getDecryptAlias(int userId, int generationId) {
+        return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
     }
 
     /**
      * Sets the current generation ID to {@code generationId}.
      */
-    private void setGenerationId(int generationId) {
-        mDatabase.setPlatformKeyGenerationId(mUserId, generationId);
+    private void setGenerationId(int userId, int generationId) {
+        mDatabase.setPlatformKeyGenerationId(userId, generationId);
     }
 
     /**
@@ -258,9 +275,9 @@
      *
      * @throws KeyStoreException if there was an error checking AndroidKeyStore.
      */
-    private boolean isKeyLoaded(int generationId) throws KeyStoreException {
-        return mKeyStore.containsAlias(getEncryptAlias(generationId))
-                && mKeyStore.containsAlias(getDecryptAlias(generationId));
+    private boolean isKeyLoaded(int userId, int generationId) throws KeyStoreException {
+        return mKeyStore.containsAlias(getEncryptAlias(userId, generationId))
+                && mKeyStore.containsAlias(getDecryptAlias(userId, generationId));
     }
 
     /**
@@ -271,10 +288,10 @@
      *     available since API version 1.
      * @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore.
      */
-    private void generateAndLoadKey(int generationId)
+    private void generateAndLoadKey(int userId, int generationId)
             throws NoSuchAlgorithmException, KeyStoreException {
-        String encryptAlias = getEncryptAlias(generationId);
-        String decryptAlias = getDecryptAlias(generationId);
+        String encryptAlias = getEncryptAlias(userId, generationId);
+        String decryptAlias = getDecryptAlias(userId, generationId);
         SecretKey secretKey = generateAesKey();
 
         mKeyStore.setEntry(
@@ -293,9 +310,11 @@
                             USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                    .setBoundToSpecificSecureUserId(mUserId)
+                    .setBoundToSpecificSecureUserId(userId)
                     .build());
 
+        setGenerationId(userId, generationId);
+
         try {
             secretKey.destroy();
         } catch (DestroyFailedException e) {
@@ -332,4 +351,17 @@
         }
         return keyStore;
     }
+
+    /**
+     * @hide
+     */
+    public interface Factory {
+        /**
+         * New PlatformKeyManager instance.
+         *
+         * @hide
+         */
+        PlatformKeyManager newInstance()
+                throws NoSuchAlgorithmException, InsecureUserException, KeyStoreException;
+    }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
index bb40fd8..2fe3f4e 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
@@ -16,22 +16,15 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.util.Log;
-
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 
 import java.security.InvalidKeyException;
-import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
 import java.util.Locale;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
-import javax.security.auth.DestroyFailedException;
 
 /**
  * Generates keys and stores them both in AndroidKeyStore and on disk, in wrapped form.
@@ -43,8 +36,6 @@
  * @hide
  */
 public class RecoverableKeyGenerator {
-    private static final String TAG = "RecoverableKeyGenerator";
-
     private static final int RESULT_CANNOT_INSERT_ROW = -1;
     private static final String KEY_GENERATOR_ALGORITHM = "AES";
     private static final int KEY_SIZE_BITS = 256;
@@ -62,20 +53,16 @@
         // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key
         // material, so that it can be synced to disk in encrypted form.
         KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM);
-        return new RecoverableKeyGenerator(
-                keyGenerator, database, new AndroidKeyStoreFactory.Impl());
+        return new RecoverableKeyGenerator(keyGenerator, database);
     }
 
     private final KeyGenerator mKeyGenerator;
     private final RecoverableKeyStoreDb mDatabase;
-    private final AndroidKeyStoreFactory mAndroidKeyStoreFactory;
 
     private RecoverableKeyGenerator(
             KeyGenerator keyGenerator,
-            RecoverableKeyStoreDb recoverableKeyStoreDb,
-            AndroidKeyStoreFactory androidKeyStoreFactory) {
+            RecoverableKeyStoreDb recoverableKeyStoreDb) {
         mKeyGenerator = keyGenerator;
-        mAndroidKeyStoreFactory = androidKeyStoreFactory;
         mDatabase = recoverableKeyStoreDb;
     }
 
@@ -89,69 +76,30 @@
      * @param platformKey The user's platform key, with which to wrap the generated key.
      * @param userId The user ID of the profile to which the calling app belongs.
      * @param uid The uid of the application that will own the key.
-     * @param alias The alias by which the key will be known in AndroidKeyStore.
+     * @param alias The alias by which the key will be known in the recoverable key store.
      * @throws RecoverableKeyStorageException if there is some error persisting the key either to
-     *     the AndroidKeyStore or the database.
+     *     the database.
      * @throws KeyStoreException if there is a KeyStore error wrapping the generated key.
      * @throws InvalidKeyException if the platform key cannot be used to wrap keys.
      *
      * @hide
      */
-    public void generateAndStoreKey(
+    public byte[] generateAndStoreKey(
             PlatformEncryptionKey platformKey, int userId, int uid, String alias)
             throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
         mKeyGenerator.init(KEY_SIZE_BITS);
         SecretKey key = mKeyGenerator.generateKey();
 
-        KeyStoreProxy keyStore;
-
-        try {
-            keyStore = mAndroidKeyStoreFactory.getKeyStoreForUid(uid);
-        } catch (NoSuchProviderException e) {
-            throw new RecoverableKeyStorageException(
-                    "Impossible: AndroidKeyStore provider did not exist", e);
-        } catch (KeyStoreException e) {
-            throw new RecoverableKeyStorageException(
-                    "Could not load AndroidKeyStore for " + uid, e);
-        }
-
-        try {
-            keyStore.setEntry(
-                    alias,
-                    new KeyStore.SecretKeyEntry(key),
-                    new KeyProtection.Builder(
-                            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                            .build());
-        } catch (KeyStoreException e) {
-            throw new RecoverableKeyStorageException(
-                    "Failed to load (%d, %s) into AndroidKeyStore", e);
-        }
-
         WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
-        try {
-            // Keep raw key material in memory for minimum possible time.
-            key.destroy();
-        } catch (DestroyFailedException e) {
-            Log.w(TAG, "Could not destroy SecretKey.");
-        }
         long result = mDatabase.insertKey(userId, uid, alias, wrappedKey);
 
         if (result == RESULT_CANNOT_INSERT_ROW) {
-            // Attempt to clean up
-            try {
-                keyStore.deleteEntry(alias);
-            } catch (KeyStoreException e) {
-                Log.e(TAG, String.format(Locale.US,
-                        "Could not delete recoverable key (%d, %s) from "
-                                + "AndroidKeyStore after error writing to database.", uid, alias),
-                        e);
-            }
-
             throw new RecoverableKeyStorageException(
                     String.format(
                             Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
         }
+
+        mDatabase.setShouldCreateSnapshot(userId, uid, true);
+        return key.getEncoded();
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index a50b425..a6f7766 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -16,6 +16,19 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
+import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
+        .ERROR_BAD_X509_CERTIFICATE;
+import static android.security.recoverablekeystore.RecoverableKeyStoreLoader.ERROR_DATABASE_ERROR;
+import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
+        .ERROR_DECRYPTION_FAILED;
+import static android.security.recoverablekeystore.RecoverableKeyStoreLoader.ERROR_INSECURE_USER;
+import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
+        .ERROR_KEYSTORE_INTERNAL_ERROR;
+import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
+        .ERROR_NOT_YET_SUPPORTED;
+import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
+        .ERROR_UNEXPECTED_MISSING_ALGORITHM;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -29,19 +42,31 @@
 import android.security.recoverablekeystore.KeyStoreRecoveryData;
 import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
 import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
 
+import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
+import java.security.KeyStoreException;
+import java.security.KeyFactory;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
 import java.security.spec.InvalidKeySpecException;
-import java.util.ArrayList;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.crypto.AEADBadTagException;
 
 /**
  * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact
@@ -50,25 +75,45 @@
  * @hide
  */
 public class RecoverableKeyStoreManager {
-    private static final String TAG = "RecoverableKeyStoreManager";
+    private static final String TAG = "RecoverableKeyStoreMgr";
+
     private static RecoverableKeyStoreManager mInstance;
 
     private final Context mContext;
     private final RecoverableKeyStoreDb mDatabase;
     private final RecoverySessionStorage mRecoverySessionStorage;
+    private final ExecutorService mExecutorService;
+    private final RecoverySnapshotListenersStorage mListenersStorage;
+    private final RecoverableKeyGenerator mRecoverableKeyGenerator;
+    private final RecoverySnapshotStorage mSnapshotStorage;
+    private final PlatformKeyManager mPlatformKeyManager;
 
     /**
      * Returns a new or existing instance.
      *
      * @hide
      */
-    public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) {
+    public static synchronized RecoverableKeyStoreManager getInstance(Context context) {
         if (mInstance == null) {
-            RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(mContext);
+            RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
+            PlatformKeyManager platformKeyManager;
+            try {
+                platformKeyManager = PlatformKeyManager.getInstance(context, db);
+            } catch (NoSuchAlgorithmException e) {
+                // Impossible: all algorithms must be supported by AOSP
+                throw new RuntimeException(e);
+            } catch (KeyStoreException e) {
+                throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
+            }
+
             mInstance = new RecoverableKeyStoreManager(
-                    mContext.getApplicationContext(),
+                    context.getApplicationContext(),
                     db,
-                    new RecoverySessionStorage());
+                    new RecoverySessionStorage(),
+                    Executors.newSingleThreadExecutor(),
+                    new RecoverySnapshotStorage(),
+                    new RecoverySnapshotListenersStorage(),
+                    platformKeyManager);
         }
         return mInstance;
     }
@@ -77,18 +122,47 @@
     RecoverableKeyStoreManager(
             Context context,
             RecoverableKeyStoreDb recoverableKeyStoreDb,
-            RecoverySessionStorage recoverySessionStorage) {
+            RecoverySessionStorage recoverySessionStorage,
+            ExecutorService executorService,
+            RecoverySnapshotStorage snapshotStorage,
+            RecoverySnapshotListenersStorage listenersStorage,
+            PlatformKeyManager platformKeyManager) {
         mContext = context;
         mDatabase = recoverableKeyStoreDb;
         mRecoverySessionStorage = recoverySessionStorage;
+        mExecutorService = executorService;
+        mListenersStorage = listenersStorage;
+        mSnapshotStorage = snapshotStorage;
+        mPlatformKeyManager = platformKeyManager;
+
+        try {
+            mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
+            throw new ServiceSpecificException(ERROR_UNEXPECTED_MISSING_ALGORITHM, e.getMessage());
+        }
     }
 
-    public int initRecoveryService(
-            @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, int userId)
+    public void initRecoveryService(
+            @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
             throws RemoteException {
         checkRecoverKeyStorePermission();
-        // TODO open /system/etc/security/... cert file
-        throw new UnsupportedOperationException();
+        int userId = UserHandle.getCallingUserId();
+        // TODO: open /system/etc/security/... cert file, and check the signature on the public keys
+        PublicKey publicKey;
+        try {
+            KeyFactory kf = KeyFactory.getInstance("EC");
+            // TODO: Randomly choose a key from the list -- right now we just use the whole input
+            X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(signedPublicKeyList);
+            publicKey = kf.generatePublic(pkSpec);
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf(TAG, "EC algorithm not available. AOSP must support this.", e);
+            throw new ServiceSpecificException(ERROR_UNEXPECTED_MISSING_ALGORITHM, e.getMessage());
+        } catch (InvalidKeySpecException e) {
+            throw new ServiceSpecificException(
+                    ERROR_BAD_X509_CERTIFICATE, "Not a valid X509 certificate.");
+        }
+        mDatabase.setRecoveryServicePublicKey(userId, Binder.getCallingUid(), publicKey);
     }
 
     /**
@@ -97,33 +171,22 @@
      * @return recovery data
      * @hide
      */
-    public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
+    public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
             throws RemoteException {
         checkRecoverKeyStorePermission();
-        final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
-        final int callingUserId = UserHandle.getCallingUserId();
-        final long callingIdentiy = Binder.clearCallingIdentity();
-        try {
-            // TODO: Return the latest snapshot for the calling recovery agent.
-        } finally {
-            Binder.restoreCallingIdentity(callingIdentiy);
+        int uid = Binder.getCallingUid();
+        KeyStoreRecoveryData snapshot = mSnapshotStorage.get(uid);
+        if (snapshot == null) {
+            throw new ServiceSpecificException(RecoverableKeyStoreLoader.ERROR_NO_SNAPSHOT_PENDING);
         }
-
-        // KeyStoreRecoveryData without application keys and empty recovery blob.
-        KeyStoreRecoveryData recoveryData =
-                new KeyStoreRecoveryData(
-                        /*snapshotVersion=*/ 1,
-                        new ArrayList<KeyStoreRecoveryMetadata>(),
-                        new ArrayList<KeyEntryRecoveryData>(),
-                        /*encryptedRecoveryKeyBlob=*/ new byte[] {});
-        throw new ServiceSpecificException(
-                RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY);
+        return snapshot;
     }
 
-    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
             throws RemoteException {
         checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
+        int uid = Binder.getCallingUid();
+        mListenersStorage.setSnapshotListener(uid, intent);
     }
 
     /**
@@ -132,37 +195,56 @@
      *
      * @return Map from Recovery agent account to snapshot version.
      */
-    public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions(int userId)
+    public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
             throws RemoteException {
         checkRecoverKeyStorePermission();
         throw new UnsupportedOperationException();
     }
 
-    public void setServerParameters(long serverParameters, int userId) throws RemoteException {
+    public void setServerParameters(long serverParameters) throws RemoteException {
         checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
-    }
-
-    public void setRecoveryStatus(
-            @NonNull String packageName, @Nullable String[] aliases, int status, int userId)
-            throws RemoteException {
-        checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
+        int userId = UserHandle.getCallingUserId();
+        mDatabase.setServerParameters(userId, Binder.getCallingUid(), serverParameters);
     }
 
     /**
-     * Gets recovery status for keys {@code packageName}.
+     * Updates recovery status for the application given its {@code packageName}.
      *
-     * @param packageName which recoverable keys statuses will be returned
-     * @return Map from KeyStore alias to recovery status
+     * @param packageName which recoverable key statuses will be returned
+     * @param aliases - KeyStore aliases or {@code null} for all aliases of the app
+     * @param status - new status
      */
-    public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId)
+    public void setRecoveryStatus(
+            @NonNull String packageName, @Nullable String[] aliases, int status)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+        int uid = Binder.getCallingUid();
+        if (packageName != null) {
+            // TODO: get uid for package name, when many apps are supported.
+        }
+        if (aliases == null) {
+            // Get all keys for the app.
+            Map<String, Integer> allKeys = mDatabase.getStatusForAllKeys(uid);
+            aliases = new String[allKeys.size()];
+            allKeys.keySet().toArray(aliases);
+        }
+        for (String alias: aliases) {
+            mDatabase.setRecoveryStatus(uid, alias, status);
+        }
+    }
+
+    /**
+     * Gets recovery status for caller or other application {@code packageName}.
+     * @param packageName which recoverable keys statuses will be returned.
+     *
+     * @return {@code Map} from KeyStore alias to recovery status.
+     */
+    public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
             throws RemoteException {
         // Any application should be able to check status for its own keys.
         // If caller is a recovery agent it can check statuses for other packages, but
         // only for recoverable keys it manages.
-        checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
+        return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
     }
 
     /**
@@ -171,10 +253,11 @@
      * @hide
      */
     public void setRecoverySecretTypes(
-            @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId)
+            @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes)
             throws RemoteException {
         checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
+        mDatabase.setRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid(),
+            secretTypes);
     }
 
     /**
@@ -183,9 +266,10 @@
      * @return secret types
      * @hide
      */
-    public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException {
+    public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
         checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
+        return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
+            Binder.getCallingUid());
     }
 
     /**
@@ -194,17 +278,17 @@
      * @return secret types
      * @hide
      */
-    public @NonNull int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
+    public @NonNull int[] getPendingRecoverySecretTypes() throws RemoteException {
         checkRecoverKeyStorePermission();
         throw new UnsupportedOperationException();
     }
 
     public void recoverySecretAvailable(
-            @NonNull KeyStoreRecoveryMetadata recoverySecret, int userId) throws RemoteException {
-        final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
+            @NonNull KeyStoreRecoveryMetadata recoverySecret) throws RemoteException {
+        int uid = Binder.getCallingUid();
         if (recoverySecret.getLockScreenUiFormat() == KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN) {
             throw new SecurityException(
-                    "Caller " + callingUid + "is not allowed to set lock screen secret");
+                    "Caller " + uid + " is not allowed to set lock screen secret");
         }
         checkRecoverKeyStorePermission();
         // TODO: add hook from LockSettingsService to set lock screen secret.
@@ -218,7 +302,7 @@
      * @param verifierPublicKey X509-encoded public key.
      * @param vaultParams Additional params associated with vault.
      * @param vaultChallenge Challenge issued by vault service.
-     * @param secrets Lock-screen hashes. Should have a single element. TODO: why is this a list?
+     * @param secrets Lock-screen hashes. For now only a single secret is supported.
      * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
      *
      * @hide
@@ -228,24 +312,43 @@
             @NonNull byte[] verifierPublicKey,
             @NonNull byte[] vaultParams,
             @NonNull byte[] vaultChallenge,
-            @NonNull List<KeyStoreRecoveryMetadata> secrets,
-            int userId)
+            @NonNull List<KeyStoreRecoveryMetadata> secrets)
             throws RemoteException {
         checkRecoverKeyStorePermission();
+        int uid = Binder.getCallingUid();
 
         if (secrets.size() != 1) {
             // TODO: support multiple secrets
-            throw new RemoteException("Only a single KeyStoreRecoveryMetadata is supported");
+            throw new ServiceSpecificException(
+                    ERROR_NOT_YET_SUPPORTED,
+                    "Only a single KeyStoreRecoveryMetadata is supported");
+        }
+
+        PublicKey publicKey;
+        try {
+            publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
+        } catch (NoSuchAlgorithmException e) {
+            // Should never happen
+            throw new RuntimeException(e);
+        } catch (InvalidKeySpecException e) {
+            throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, "Not a valid X509 key");
+        }
+        // The raw public key bytes contained in vaultParams must match the ones given in
+        // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned
+        // by the original recovery service.
+        if (!publicKeysMatch(publicKey, vaultParams)) {
+            throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE,
+                    "The public keys given in verifierPublicKey and vaultParams do not match.");
         }
 
         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
         byte[] kfHash = secrets.get(0).getSecret();
         mRecoverySessionStorage.add(
-                userId, new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant));
+                uid,
+                new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
 
         try {
             byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
-            PublicKey publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
             return KeySyncUtils.encryptRecoveryClaim(
                     publicKey,
                     vaultParams,
@@ -253,35 +356,145 @@
                     thmKfHash,
                     keyClaimant);
         } catch (NoSuchAlgorithmException e) {
-            // Should never happen: all the algorithms used are required by AOSP implementations.
-            throw new RemoteException(
-                    "Missing required algorithm",
-                    e,
-                    /*enableSuppression=*/ true,
-                    /*writeableStackTrace=*/ true);
-        } catch (InvalidKeySpecException | InvalidKeyException e) {
-            throw new RemoteException(
-                    "Not a valid X509 key",
-                    e,
-                    /*enableSuppression=*/ true,
-                    /*writeableStackTrace=*/ true);
+            Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e);
+            throw new ServiceSpecificException(ERROR_UNEXPECTED_MISSING_ALGORITHM, e.getMessage());
+        } catch (InvalidKeyException e) {
+            throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, e.getMessage());
         }
     }
 
-    public void recoverKeys(
+    /**
+     * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
+     * service.
+     *
+     * @param sessionId The session ID used to generate the claim. See
+     *     {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
+     * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
+     *     service.
+     * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
+     *     were wrapped with the recovery key.
+     * @return Map from alias to raw key material.
+     * @throws RemoteException if an error occurred recovering the keys.
+     */
+    public Map<String, byte[]> recoverKeys(
             @NonNull String sessionId,
-            @NonNull byte[] recoveryKeyBlob,
-            @NonNull List<KeyEntryRecoveryData> applicationKeys,
-            int userId)
+            @NonNull byte[] encryptedRecoveryKey,
+            @NonNull List<KeyEntryRecoveryData> applicationKeys)
             throws RemoteException {
         checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
+        int uid = Binder.getCallingUid();
+        RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
+        if (sessionEntry == null) {
+            throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR,
+                    String.format(Locale.US,
+                    "Application uid=%d does not have pending session '%s'", uid, sessionId));
+        }
+
+        try {
+            byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
+            return recoverApplicationKeys(recoveryKey, applicationKeys);
+        } finally {
+            sessionEntry.destroy();
+            mRecoverySessionStorage.remove(uid);
+        }
+    }
+
+    /**
+     * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
+     * returns the raw key material.
+     *
+     * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
+     *
+     * @hide
+     */
+    public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
+        int uid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
+
+        PlatformEncryptionKey encryptionKey;
+        try {
+            encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
+        } catch (NoSuchAlgorithmException e) {
+            // Impossible: all algorithms must be supported by AOSP
+            throw new RuntimeException(e);
+        } catch (KeyStoreException | UnrecoverableKeyException e) {
+            throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
+        } catch (InsecureUserException e) {
+            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
+        }
+
+        try {
+            return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
+        } catch (KeyStoreException | InvalidKeyException e) {
+            throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
+        } catch (RecoverableKeyStorageException e) {
+            throw new ServiceSpecificException(ERROR_DATABASE_ERROR, e.getMessage());
+        }
+    }
+
+    public void removeKey(@NonNull String alias) throws RemoteException {
+        int uid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
+
+        boolean wasRemoved = mDatabase.removeKey(uid, alias);
+        if (wasRemoved) {
+            mDatabase.setShouldCreateSnapshot(userId, uid, true);
+        }
+    }
+
+    private byte[] decryptRecoveryKey(
+            RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
+            throws RemoteException, ServiceSpecificException {
+        try {
+            byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
+                    sessionEntry.getKeyClaimant(),
+                    sessionEntry.getVaultParams(),
+                    encryptedClaimResponse);
+            return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
+        } catch (InvalidKeyException | AEADBadTagException e) {
+            throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR,
+                    "Failed to decrypt recovery key " + e.getMessage());
+
+        } catch (NoSuchAlgorithmException e) {
+            // Should never happen: all the algorithms used are required by AOSP implementations
+            throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
+        }
+    }
+
+    /**
+     * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
+     *
+     * @return Map from alias to raw key material.
+     * @throws RemoteException if an error occurred decrypting the keys.
+     */
+    private Map<String, byte[]> recoverApplicationKeys(
+            @NonNull byte[] recoveryKey,
+            @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException {
+        HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
+        for (KeyEntryRecoveryData applicationKey : applicationKeys) {
+            String alias = applicationKey.getAlias();
+            byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
+
+            try {
+                byte[] keyMaterial =
+                        KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
+                keyMaterialByAlias.put(alias, keyMaterial);
+            } catch (NoSuchAlgorithmException e) {
+                Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
+                throw new ServiceSpecificException(
+                        ERROR_UNEXPECTED_MISSING_ALGORITHM, e.getMessage());
+            } catch (InvalidKeyException | AEADBadTagException e) {
+                throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
+                        "Failed to recover key with alias '" + alias + "': " + e.getMessage());
+            }
+        }
+        return keyMaterialByAlias;
     }
 
     /**
      * This function can only be used inside LockSettingsService.
      *
-     * @param storedHashType from {@Code CredentialHash}
+     * @param storedHashType from {@code CredentialHash}
      * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
      *     mPasswordMaxLength}
      * @param userId for user who just unlocked the device.
@@ -289,36 +502,55 @@
      */
     public void lockScreenSecretAvailable(
             int storedHashType, @NonNull String credential, int userId) {
-        // Notify RecoverableKeystoreLoader about unlock
-        @KeyStoreRecoveryMetadata.LockScreenUiFormat int uiFormat;
-        if (storedHashType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
-            uiFormat = KeyStoreRecoveryMetadata.TYPE_PATTERN;
-        } else if (isPin(credential)) {
-            uiFormat = KeyStoreRecoveryMetadata.TYPE_PIN;
-        } else {
-            uiFormat = KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+        // So as not to block the critical path unlocking the phone, defer to another thread.
+        try {
+            mExecutorService.execute(KeySyncTask.newInstance(
+                    mContext,
+                    mDatabase,
+                    mSnapshotStorage,
+                    mListenersStorage,
+                    userId,
+                    storedHashType,
+                    credential,
+                    /*credentialUpdated=*/ false));
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
+        } catch (KeyStoreException e) {
+            Log.e(TAG, "Key store error encountered during recoverable key sync", e);
+        } catch (InsecureUserException e) {
+            Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
         }
-        // TODO: check getPendingRecoverySecretTypes.
-        // TODO: compute SHA256 or Argon2id depending on secret type.
     }
 
-    /** This function can only be used inside LockSettingsService. */
+    /**
+     * This function can only be used inside LockSettingsService.
+     * @param storedHashType from {@code CredentialHash}
+     * @param credential - unencrypted String
+     * @param userId for the user whose lock screen credentials were changed.
+     * @hide
+     */
     public void lockScreenSecretChanged(
-            @KeyStoreRecoveryMetadata.LockScreenUiFormat int type,
+            int storedHashType,
             @Nullable String credential,
             int userId) {
-        throw new UnsupportedOperationException();
-    }
-
-    @VisibleForTesting
-    boolean isPin(@NonNull String credential) {
-        for (int i = 0; i < credential.length(); i++) {
-            char c = credential.charAt(i);
-            if (c < '0' || c > '9') {
-                return false;
-            }
+        // So as not to block the critical path unlocking the phone, defer to another thread.
+        try {
+            mExecutorService.execute(KeySyncTask.newInstance(
+                    mContext,
+                    mDatabase,
+                    mSnapshotStorage,
+                    mListenersStorage,
+                    userId,
+                    storedHashType,
+                    credential,
+                    /*credentialUpdated=*/ true));
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
+        } catch (KeyStoreException e) {
+            Log.e(TAG, "Key store error encountered during recoverable key sync", e);
+        } catch (InsecureUserException e) {
+            Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
         }
-        return true;
     }
 
     private void checkRecoverKeyStorePermission() {
@@ -326,4 +558,9 @@
                 RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE,
                 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
     }
+
+    private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
+        byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
+        return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length));
+    }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java
new file mode 100644
index 0000000..c925329
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.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.server.locksettings.recoverablekeystore;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * In memory storage for listeners to be notified when new recovery snapshot is available. This
+ * class is thread-safe. It is used on two threads - the service thread and the thread that runs the
+ * {@link KeySyncTask}.
+ *
+ * @hide
+ */
+public class RecoverySnapshotListenersStorage {
+    private static final String TAG = "RecoverySnapshotLstnrs";
+
+    @GuardedBy("this")
+    private SparseArray<PendingIntent> mAgentIntents = new SparseArray<>();
+
+    /**
+     * Sets new listener for the recovery agent, identified by {@code uid}.
+     *
+     * @param recoveryAgentUid uid of the recovery agent.
+     * @param intent PendingIntent which will be triggered when new snapshot is available.
+     */
+    public synchronized void setSnapshotListener(
+            int recoveryAgentUid, @Nullable PendingIntent intent) {
+        Log.i(TAG, "Registered listener for agent with uid " + recoveryAgentUid);
+        mAgentIntents.put(recoveryAgentUid, intent);
+    }
+
+    /**
+     * Returns {@code true} if a listener has been set for the recovery agent.
+     */
+    public synchronized boolean hasListener(int recoveryAgentUid) {
+        return mAgentIntents.get(recoveryAgentUid) != null;
+    }
+
+    /**
+     * Notifies recovery agent that new snapshot is available. Does nothing if a listener was not
+     * registered.
+     *
+     * @param recoveryAgentUid uid of recovery agent.
+     */
+    public synchronized void recoverySnapshotAvailable(int recoveryAgentUid) {
+        PendingIntent intent = mAgentIntents.get(recoveryAgentUid);
+        if (intent != null) {
+            try {
+                intent.send();
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(TAG,
+                        "Failed to trigger PendingIntent for " + recoveryAgentUid,
+                        e);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
index d8a2d31..807ee03 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -230,7 +230,7 @@
      * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
      * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
      * @throws AEADBadTagException if the authentication tag contained in {@code encryptedPayload}
-     *     cannot be validated
+     *     cannot be validated, or if the payload is not a valid SecureBox V2 payload.
      * @hide
      */
     public static byte[] decrypt(
@@ -244,12 +244,14 @@
             throw new IllegalArgumentException("Both the private key and shared secret are empty");
         }
         header = emptyByteArrayIfNull(header);
-        encryptedPayload = emptyByteArrayIfNull(encryptedPayload);
+        if (encryptedPayload == null) {
+            throw new NullPointerException("Encrypted payload must not be null.");
+        }
 
         ByteBuffer ciphertextBuffer = ByteBuffer.wrap(encryptedPayload);
         byte[] version = readEncryptedPayload(ciphertextBuffer, VERSION.length);
         if (!Arrays.equals(version, VERSION)) {
-            throw new IllegalArgumentException("The payload was not encrypted by SecureBox v2");
+            throw new AEADBadTagException("The payload was not encrypted by SecureBox v2");
         }
 
         byte[] senderPublicKeyBytes;
@@ -271,12 +273,13 @@
         return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
     }
 
-    private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) {
+    private static byte[] readEncryptedPayload(ByteBuffer buffer, int length)
+            throws AEADBadTagException {
         byte[] output = new byte[length];
         try {
             buffer.get(output);
         } catch (BufferUnderflowException ex) {
-            throw new IllegalArgumentException("The encrypted payload is too short");
+            throw new AEADBadTagException("The encrypted payload is too short");
         }
         return output;
     }
@@ -369,7 +372,13 @@
         }
     }
 
-    @VisibleForTesting
+    /**
+     * Encodes public key in format expected by the secure hardware module. This is used as part
+     * of the vault params.
+     *
+     * @param publicKey The public key.
+     * @return The key packed into a 65-byte array.
+     */
     static byte[] encodePublicKey(PublicKey publicKey) {
         ECPoint point = ((ECPublicKey) publicKey).getW();
         byte[] x = point.getAffineX().toByteArray();
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
index dfa173c..54aa9f0 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
@@ -17,6 +17,7 @@
 package com.android.server.locksettings.recoverablekeystore;
 
 import android.util.Log;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
 
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -45,6 +46,7 @@
     private static final int GCM_TAG_LENGTH_BITS = 128;
 
     private final int mPlatformKeyGenerationId;
+    private final int mRecoveryStatus;
     private final byte[] mNonce;
     private final byte[] mKeyMaterial;
 
@@ -94,7 +96,25 @@
         return new WrappedKey(
                 /*nonce=*/ cipher.getIV(),
                 /*keyMaterial=*/ encryptedKeyMaterial,
-                /*platformKeyGenerationId=*/ wrappingKey.getGenerationId());
+                /*platformKeyGenerationId=*/ wrappingKey.getGenerationId(),
+                RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+    }
+
+    /**
+     * A new instance with default recovery status.
+     *
+     * @param nonce The nonce with which the key material was encrypted.
+     * @param keyMaterial The encrypted bytes of the key material.
+     * @param platformKeyGenerationId The generation ID of the key used to wrap this key.
+     *
+     * @see RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS
+     * @hide
+     */
+    public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) {
+        mNonce = nonce;
+        mKeyMaterial = keyMaterial;
+        mPlatformKeyGenerationId = platformKeyGenerationId;
+        mRecoveryStatus = RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS;
     }
 
     /**
@@ -103,13 +123,16 @@
      * @param nonce The nonce with which the key material was encrypted.
      * @param keyMaterial The encrypted bytes of the key material.
      * @param platformKeyGenerationId The generation ID of the key used to wrap this key.
+     * @param recoveryStatus recovery status of the key.
      *
      * @hide
      */
-    public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) {
+    public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId,
+            int recoveryStatus) {
         mNonce = nonce;
         mKeyMaterial = keyMaterial;
         mPlatformKeyGenerationId = platformKeyGenerationId;
+        mRecoveryStatus = recoveryStatus;
     }
 
     /**
@@ -130,7 +153,6 @@
         return mKeyMaterial;
     }
 
-
     /**
      * Returns the generation ID of the platform key, with which this key was wrapped.
      *
@@ -141,6 +163,15 @@
     }
 
     /**
+     * Returns recovery status of the key.
+     *
+     * @hide
+     */
+    public int getRecoveryStatus() {
+        return mRecoveryStatus;
+    }
+
+    /**
      * Unwraps the {@code wrappedKeys} with the {@code platformKey}.
      *
      * @return The unwrapped keys, indexed by alias.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index ed570c3..c6f3ede 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -16,20 +16,32 @@
 
 package com.android.server.locksettings.recoverablekeystore.storage;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.server.locksettings.recoverablekeystore.WrappedKey;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
 
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.StringJoiner;
 
 /**
  * Database of recoverable key information.
@@ -80,6 +92,7 @@
         values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial());
         values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, LAST_SYNCED_AT_UNSYNCED);
         values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId());
+        values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, wrappedKey.getRecoveryStatus());
         return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
     }
 
@@ -94,7 +107,8 @@
                 KeysEntry._ID,
                 KeysEntry.COLUMN_NAME_NONCE,
                 KeysEntry.COLUMN_NAME_WRAPPED_KEY,
-                KeysEntry.COLUMN_NAME_GENERATION_ID};
+                KeysEntry.COLUMN_NAME_GENERATION_ID,
+                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
         String selection =
                 KeysEntry.COLUMN_NAME_UID + " = ? AND "
                 + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
@@ -128,32 +142,115 @@
                     cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
             int generationId = cursor.getInt(
                     cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID));
-            return new WrappedKey(nonce, keyMaterial, generationId);
+            int recoveryStatus = cursor.getInt(
+                    cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
+            return new WrappedKey(nonce, keyMaterial, generationId, recoveryStatus);
         }
     }
 
     /**
-     * Returns all keys for the given {@code userId} and {@code platformKeyGenerationId}.
+     * Removes key with {@code alias} for app with {@code uid}.
+     *
+     * @return {@code true} if deleted a row.
+     */
+    public boolean removeKey(int uid, String alias) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        String selection = KeysEntry.COLUMN_NAME_UID + " = ? AND " +
+                KeysEntry.COLUMN_NAME_ALIAS + " = ?";
+        String[] selectionArgs = { Integer.toString(uid), alias };
+        return db.delete(KeysEntry.TABLE_NAME, selection, selectionArgs) > 0;
+    }
+
+    /**
+     * Returns all statuses for keys {@code uid} and {@code platformKeyGenerationId}.
+     *
+     * @param uid of the application
+     *
+     * @return Map from Aliases to status.
+     *
+     * @hide
+     */
+    public @NonNull Map<String, Integer> getStatusForAllKeys(int uid) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+        String[] projection = {
+                KeysEntry._ID,
+                KeysEntry.COLUMN_NAME_ALIAS,
+                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
+        String selection =
+                KeysEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(uid)};
+
+        try (
+            Cursor cursor = db.query(
+                KeysEntry.TABLE_NAME,
+                projection,
+                selection,
+                selectionArguments,
+                /*groupBy=*/ null,
+                /*having=*/ null,
+                /*orderBy=*/ null)
+        ) {
+            HashMap<String, Integer> statuses = new HashMap<>();
+            while (cursor.moveToNext()) {
+                String alias = cursor.getString(
+                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
+                int recoveryStatus = cursor.getInt(
+                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
+                statuses.put(alias, recoveryStatus);
+            }
+            return statuses;
+        }
+    }
+
+    /**
+     * Updates status for given key.
+     * @param uid of the application
+     * @param alias of the key
+     * @param status - new status
+     * @return number of updated entries.
+     * @hide
+     **/
+    public int setRecoveryStatus(int uid, String alias, int status) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, status);
+        String selection =
+                KeysEntry.COLUMN_NAME_UID + " = ? AND "
+                + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
+        return db.update(KeysEntry.TABLE_NAME, values, selection,
+            new String[] {String.valueOf(uid), alias});
+    }
+
+    /**
+     * Returns all keys for the given {@code userId} {@code recoveryAgentUid}
+     * and {@code platformKeyGenerationId}.
      *
      * @param userId User id of the profile to which all the keys are associated.
+     * @param recoveryAgentUid Uid of the recovery agent which will perform the sync
      * @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys.
      *     (i.e., this should be the most recent generation ID, as older platform keys are not
      *     usable.)
      *
      * @hide
      */
-    public Map<String, WrappedKey> getAllKeys(int userId, int platformKeyGenerationId) {
+    public Map<String, WrappedKey> getAllKeys(int userId, int recoveryAgentUid,
+            int platformKeyGenerationId) {
         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
         String[] projection = {
                 KeysEntry._ID,
                 KeysEntry.COLUMN_NAME_NONCE,
                 KeysEntry.COLUMN_NAME_WRAPPED_KEY,
-                KeysEntry.COLUMN_NAME_ALIAS};
+                KeysEntry.COLUMN_NAME_ALIAS,
+                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
         String selection =
                 KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                + KeysEntry.COLUMN_NAME_UID + " = ? AND "
                 + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?";
         String[] selectionArguments = {
-                Integer.toString(userId), Integer.toString(platformKeyGenerationId) };
+                Integer.toString(userId),
+                Integer.toString(recoveryAgentUid),
+                Integer.toString(platformKeyGenerationId)
+            };
 
         try (
             Cursor cursor = db.query(
@@ -173,7 +270,10 @@
                         cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
                 String alias = cursor.getString(
                         cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
-                keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId));
+                int recoveryStatus = cursor.getInt(
+                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
+                keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId,
+                        recoveryStatus));
             }
             return keys;
         }
@@ -226,11 +326,492 @@
     }
 
     /**
+     * Updates the public key of the recovery service into the database.
+     *
+     * @param userId The uid of the profile the application is running under.
+     * @param uid The uid of the application to whom the key belongs.
+     * @param publicKey The public key of the recovery service.
+     * @return The primary key of the inserted row, or -1 if failed.
+     *
+     * @hide
+     */
+    public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded());
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        ensureRecoveryServiceMetadataEntryExists(userId, uid);
+        return db.update(
+                RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+    }
+
+    /**
+     * Returns the list of recovery agents initialized for given {@code userId}
+     * @param userId The userId of the profile the application is running under.
+     * @return The list of recovery agents
+     * @hide
+     */
+    public @NonNull List<Integer> getRecoveryAgents(int userId) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+        String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID };
+        String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+        String[] selectionArguments = { Integer.toString(userId) };
+
+        try (
+            Cursor cursor = db.query(
+                    RecoveryServiceMetadataEntry.TABLE_NAME,
+                    projection,
+                    selection,
+                    selectionArguments,
+                    /*groupBy=*/ null,
+                    /*having=*/ null,
+                    /*orderBy=*/ null)
+        ) {
+            int count = cursor.getCount();
+            ArrayList<Integer> result = new ArrayList<>(count);
+            while (cursor.moveToNext()) {
+                int uid = cursor.getInt(
+                        cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID));
+                result.add(uid);
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Returns the public key of the recovery service.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application who initializes the local recovery components.
+     *
+     * @hide
+     */
+    @Nullable
+    public PublicKey getRecoveryServicePublicKey(int userId, int uid) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+        String[] projection = {
+                RecoveryServiceMetadataEntry._ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY};
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        try (
+                Cursor cursor = db.query(
+                        RecoveryServiceMetadataEntry.TABLE_NAME,
+                        projection,
+                        selection,
+                        selectionArguments,
+                        /*groupBy=*/ null,
+                        /*having=*/ null,
+                        /*orderBy=*/ null)
+        ) {
+            int count = cursor.getCount();
+            if (count == 0) {
+                return null;
+            }
+            if (count > 1) {
+                Log.wtf(TAG,
+                        String.format(Locale.US,
+                                "%d PublicKey entries found for userId=%d uid=%d. "
+                                        + "Should only ever be 0 or 1.", count, userId, uid));
+                return null;
+            }
+            cursor.moveToFirst();
+            int idx = cursor.getColumnIndexOrThrow(
+                    RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
+            if (cursor.isNull(idx)) {
+                return null;
+            }
+            byte[] keyBytes = cursor.getBlob(idx);
+            try {
+                return decodeX509Key(keyBytes);
+            } catch (InvalidKeySpecException e) {
+                Log.wtf(TAG,
+                        String.format(Locale.US,
+                                "Recovery service public key entry cannot be decoded for "
+                                        + "userId=%d uid=%d.",
+                                userId, uid));
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Updates the list of user secret types used for end-to-end encryption.
+     * If no secret types are set, recovery snapshot will not be created.
+     * See {@code KeyStoreRecoveryMetadata}
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application.
+     * @param secretTypes list of secret types
+     * @return The primary key of the updated row, or -1 if failed.
+     *
+     * @hide
+     */
+    public long setRecoverySecretTypes(int userId, int uid, int[] secretTypes) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        StringJoiner joiner = new StringJoiner(",");
+        Arrays.stream(secretTypes).forEach(i -> joiner.add(Integer.toString(i)));
+        String typesAsCsv = joiner.toString();
+        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, typesAsCsv);
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        ensureRecoveryServiceMetadataEntryExists(userId, uid);
+        return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, selection,
+            new String[] {String.valueOf(userId), String.valueOf(uid)});
+    }
+
+    /**
+     * Returns the list of secret types used for end-to-end encryption.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application who initialized the local recovery components.
+     * @return Secret types or empty array, if types were not set.
+     *
+     * @hide
+     */
+    public @NonNull int[] getRecoverySecretTypes(int userId, int uid) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+        String[] projection = {
+                RecoveryServiceMetadataEntry._ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES};
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        try (
+                Cursor cursor = db.query(
+                        RecoveryServiceMetadataEntry.TABLE_NAME,
+                        projection,
+                        selection,
+                        selectionArguments,
+                        /*groupBy=*/ null,
+                        /*having=*/ null,
+                        /*orderBy=*/ null)
+        ) {
+            int count = cursor.getCount();
+            if (count == 0) {
+                return new int[]{};
+            }
+            if (count > 1) {
+                Log.wtf(TAG,
+                        String.format(Locale.US,
+                                "%d deviceId entries found for userId=%d uid=%d. "
+                                        + "Should only ever be 0 or 1.", count, userId, uid));
+                return new int[]{};
+            }
+            cursor.moveToFirst();
+            int idx = cursor.getColumnIndexOrThrow(
+                    RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES);
+            if (cursor.isNull(idx)) {
+                return new int[]{};
+            }
+            String csv = cursor.getString(idx);
+            if (TextUtils.isEmpty(csv)) {
+                return new int[]{};
+            }
+            String[] types = csv.split(",");
+            int[] result = new int[types.length];
+            for (int i = 0; i < types.length; i++) {
+                try {
+                    result[i] = Integer.parseInt(types[i]);
+                } catch (NumberFormatException e) {
+                    Log.wtf(TAG, "String format error " + e);
+                }
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Returns the first (and only?) public key for {@code userId}.
+     *
+     * @param userId The userId of the profile whose keys are to be synced.
+     * @return The public key, or null if none exists.
+     */
+    @Nullable
+    public PublicKey getRecoveryServicePublicKey(int userId) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+        String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY };
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+        String[] selectionArguments = { Integer.toString(userId) };
+
+        try (
+            Cursor cursor = db.query(
+                    RecoveryServiceMetadataEntry.TABLE_NAME,
+                    projection,
+                    selection,
+                    selectionArguments,
+                    /*groupBy=*/ null,
+                    /*having=*/ null,
+                    /*orderBy=*/ null)
+        ) {
+            if (cursor.getCount() < 1) {
+                return null;
+            }
+
+            cursor.moveToFirst();
+            byte[] keyBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(
+                    RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY));
+
+            try {
+                return decodeX509Key(keyBytes);
+            } catch (InvalidKeySpecException e) {
+                Log.wtf(TAG, "Could not decode public key for " + userId);
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Updates the counterId
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application.
+     * @param counterId The counterId.
+     * @return The primary key of the inserted row, or -1 if failed.
+     *
+     * @hide
+     */
+    public long setCounterId(int userId, int uid, long counterId) {
+        return setLong(userId, uid,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID, counterId);
+    }
+
+    /**
+     * Returns the counter id.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application who initialized the local recovery components.
+     * @return The counter id
+     *
+     * @hide
+     */
+    @Nullable
+    public Long getCounterId(int userId, int uid) {
+        return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID);
+    }
+
+
+    /**
+     * Updates the server parameters given by the application initializing the local recovery
+     * components.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application.
+     * @param serverParameters The server parameters.
+     * @return The primary key of the inserted row, or -1 if failed.
+     *
+     * @hide
+     */
+    public long setServerParameters(int userId, int uid, long serverParameters) {
+        return setLong(userId, uid,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS, serverParameters);
+    }
+
+    /**
+     * Returns the server paramters that was previously set by the application who initialized the
+     * local recovery service components.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application who initialized the local recovery components.
+     * @return The server parameters that were previously set, or null if there's none.
+     *
+     * @hide
+     */
+    @Nullable
+    public Long getServerParameters(int userId, int uid) {
+        return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS);
+
+    }
+
+    /**
+     * Updates the snapshot version.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application.
+     * @param snapshotVersion The snapshot version
+     * @return The primary key of the inserted row, or -1 if failed.
+     *
+     * @hide
+     */
+    public long setSnapshotVersion(int userId, int uid, long snapshotVersion) {
+        return setLong(userId, uid,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION, snapshotVersion);
+    }
+
+    /**
+     * Returns the snapshot version
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application who initialized the local recovery components.
+     * @return The server parameters that were previously set, or null if there's none.
+     *
+     * @hide
+     */
+    @Nullable
+    public Long getSnapshotVersion(int userId, int uid) {
+        return getLong(userId, uid,
+            RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION);
+    }
+
+    /**
+     * Updates the snapshot version.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application.
+     * @param pending The server parameters.
+     * @return The primary key of the inserted row, or -1 if failed.
+     *
+     * @hide
+     */
+    public long setShouldCreateSnapshot(int userId, int uid, boolean pending) {
+        return setLong(userId, uid,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT, pending ? 1 : 0);
+    }
+
+    /**
+     * Returns {@code true} if new snapshot should be created.
+     * Returns {@code false} if the flag was never set.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application who initialized the local recovery components.
+     * @return snapshot outdated flag.
+     *
+     * @hide
+     */
+    public boolean getShouldCreateSnapshot(int userId, int uid) {
+        Long res = getLong(userId, uid,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT);
+        return res != null && res != 0L;
+    }
+
+    /**
+     * Returns given long value from the database.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application who initialized the local recovery components.
+     * @param key from {@code RecoveryServiceMetadataEntry}
+     * @return The value that were previously set, or null if there's none.
+     *
+     * @hide
+     */
+    private Long getLong(int userId, int uid, String key) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+        String[] projection = {
+                RecoveryServiceMetadataEntry._ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+                key};
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        try (
+            Cursor cursor = db.query(
+                    RecoveryServiceMetadataEntry.TABLE_NAME,
+                    projection,
+                    selection,
+                    selectionArguments,
+                    /*groupBy=*/ null,
+                    /*having=*/ null,
+                    /*orderBy=*/ null)
+        ) {
+            int count = cursor.getCount();
+            if (count == 0) {
+                return null;
+            }
+            if (count > 1) {
+                Log.wtf(TAG,
+                        String.format(Locale.US,
+                                "%d entries found for userId=%d uid=%d. "
+                                        + "Should only ever be 0 or 1.", count, userId, uid));
+                return null;
+            }
+            cursor.moveToFirst();
+            int idx = cursor.getColumnIndexOrThrow(key);
+            if (cursor.isNull(idx)) {
+                return null;
+            } else {
+                return cursor.getLong(idx);
+            }
+        }
+    }
+
+    /**
+     * Sets a long value in the database.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application who initialized the local recovery components.
+     * @param key defined in {@code RecoveryServiceMetadataEntry}
+     * @param value new value.
+     * @return The primary key of the inserted row, or -1 if failed.
+     *
+     * @hide
+     */
+
+    private long setLong(int userId, int uid, String key, long value) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(key, value);
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        ensureRecoveryServiceMetadataEntryExists(userId, uid);
+        return db.update(
+                RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+    }
+
+    /**
+     * Creates an empty row in the recovery service metadata table if such a row doesn't exist for
+     * the given userId and uid, so db.update will succeed.
+     */
+    private void ensureRecoveryServiceMetadataEntryExists(int userId, int uid) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, userId);
+        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, uid);
+        db.insertWithOnConflict(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
+                values, SQLiteDatabase.CONFLICT_IGNORE);
+    }
+
+    /**
      * Closes all open connections to the database.
      */
     public void close() {
         mKeyStoreDbHelper.close();
     }
 
-    // TODO: Add method for updating the 'last synced' time.
+    @Nullable
+    private static PublicKey decodeX509Key(byte[] keyBytes) throws InvalidKeySpecException {
+        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes);
+        try {
+            return KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
+        } catch (NoSuchAlgorithmException e) {
+            // Should never happen
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index dcd1827..597ae4c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -62,6 +62,11 @@
          * Timestamp of when this key was last synced with remote storage, or -1 if never synced.
          */
         static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at";
+
+        /**
+         * Status of the key sync {@code RecoverableKeyStoreLoader#setRecoveryStatus}
+         */
+        static final String COLUMN_NAME_RECOVERY_STATUS = "recovery_status";
     }
 
     /**
@@ -81,4 +86,50 @@
          */
         static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id";
     }
+
+    /**
+     * Table holding metadata of the recovery service.
+     */
+    static class RecoveryServiceMetadataEntry implements BaseColumns {
+        static final String TABLE_NAME = "recovery_service_metadata";
+
+        /**
+         * The user id of the profile the application is running under.
+         */
+        static final String COLUMN_NAME_USER_ID = "user_id";
+
+        /**
+         * The uid of the application that initializes the local recovery components.
+         */
+        static final String COLUMN_NAME_UID = "uid";
+
+        /**
+         * Version of the latest recovery snapshot
+         */
+        static final String COLUMN_NAME_SNAPSHOT_VERSION = "snapshot_version";
+        /**
+         * Flag to generate new snapshot.
+         */
+        static final String COLUMN_NAME_SHOULD_CREATE_SNAPSHOT = "should_create_snapshot";
+
+        /**
+         * The public key of the recovery service.
+         */
+        static final String COLUMN_NAME_PUBLIC_KEY = "public_key";
+
+        /**
+         * Secret types used for end-to-end encryption.
+         */
+        static final String COLUMN_NAME_SECRET_TYPES = "secret_types";
+
+        /**
+         * Locally generated random number.
+         */
+        static final String COLUMN_NAME_COUNTER_ID = "counter_id";
+
+        /**
+         * The server parameters of the recovery service.
+         */
+        static final String COLUMN_NAME_SERVER_PARAMETERS = "server_parameters";
+    }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index b017fbb..6eb47ee 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -1,3 +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 com.android.server.locksettings.recoverablekeystore.storage;
 
 import android.content.Context;
@@ -5,6 +21,7 @@
 import android.database.sqlite.SQLiteOpenHelper;
 
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
 
 /**
@@ -24,6 +41,7 @@
                     + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB,"
                     + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER,"
                     + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER,"
+                    + KeysEntry.COLUMN_NAME_RECOVERY_STATUS + " INTEGER,"
                     + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + ","
                     + KeysEntry.COLUMN_NAME_ALIAS + "))";
 
@@ -33,12 +51,30 @@
                     + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
                     + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";
 
+    private static final String SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY =
+            "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " ("
+                    + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION + " INTEGER,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT + " INTEGER,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID + " INTEGER,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS + " INTEGER,"
+                    + "UNIQUE("
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID  + ","
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";
+
     private static final String SQL_DELETE_KEYS_ENTRY =
             "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;
 
     private static final String SQL_DELETE_USER_METADATA_ENTRY =
             "DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME;
 
+    private static final String SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY =
+            "DROP TABLE IF EXISTS " + RecoveryServiceMetadataEntry.TABLE_NAME;
+
     RecoverableKeyStoreDbHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
     }
@@ -47,12 +83,14 @@
     public void onCreate(SQLiteDatabase db) {
         db.execSQL(SQL_CREATE_KEYS_ENTRY);
         db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
+        db.execSQL(SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY);
     }
 
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
         db.execSQL(SQL_DELETE_KEYS_ENTRY);
         db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
+        db.execSQL(SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY);
         onCreate(db);
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
index bc56ae1..f7633e4 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
@@ -129,15 +129,17 @@
     public static class Entry implements Destroyable {
         private final byte[] mLskfHash;
         private final byte[] mKeyClaimant;
+        private final byte[] mVaultParams;
         private final String mSessionId;
 
         /**
          * @hide
          */
-        public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant) {
-            this.mLskfHash = lskfHash;
-            this.mSessionId = sessionId;
-            this.mKeyClaimant = keyClaimant;
+        public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant, byte[] vaultParams) {
+            mLskfHash = lskfHash;
+            mSessionId = sessionId;
+            mKeyClaimant = keyClaimant;
+            mVaultParams = vaultParams;
         }
 
         /**
@@ -160,6 +162,15 @@
         }
 
         /**
+         * Returns the vault params associated with the session.
+         *
+         * @hide
+         */
+        public byte[] getVaultParams() {
+            return mVaultParams;
+        }
+
+        /**
          * Overwrites the memory for the lskf hash and key claimant.
          *
          * @hide
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
new file mode 100644
index 0000000..011b374
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.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.server.locksettings.recoverablekeystore.storage;
+
+import android.annotation.Nullable;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * In-memory storage for recovery snapshots.
+ *
+ * <p>Recovery snapshots are generated after a successful screen unlock. They are only generated if
+ * the recoverable keystore has been mutated since the previous snapshot. This class stores only the
+ * latest snapshot for each recovery agent.
+ *
+ * <p>This class is thread-safe. It is used both on the service thread and the
+ * {@link com.android.server.locksettings.recoverablekeystore.KeySyncTask} thread.
+ */
+public class RecoverySnapshotStorage {
+    @GuardedBy("this")
+    private final SparseArray<KeyStoreRecoveryData> mSnapshotByUid = new SparseArray<>();
+
+    /**
+     * Sets the latest {@code snapshot} for the recovery agent {@code uid}.
+     */
+    public synchronized void put(int uid, KeyStoreRecoveryData snapshot) {
+        mSnapshotByUid.put(uid, snapshot);
+    }
+
+    /**
+     * Returns the latest snapshot for the recovery agent {@code uid}, or null if none exists.
+     */
+    @Nullable
+    public synchronized KeyStoreRecoveryData get(int uid) {
+        return mSnapshotByUid.get(uid);
+    }
+
+    /**
+     * Removes any (if any) snapshot associated with recovery agent {@code uid}.
+     */
+    public synchronized void remove(int uid) {
+        mSnapshotByUid.remove(uid);
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index fdfe241..3c47e85 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -2799,6 +2799,17 @@
     }
 
     @Override
+    public String getSubscriptionPlansOwner(int subId) {
+        if (UserHandle.getCallingAppId() != android.os.Process.SYSTEM_UID) {
+            throw new SecurityException();
+        }
+
+        synchronized (mNetworkPoliciesSecondLock) {
+            return mSubscriptionPlansOwner.get(subId);
+        }
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
 
diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
index 171703a..3bcc36f 100644
--- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
+++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
@@ -29,10 +29,12 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.net.INetworkWatchlistManager;
@@ -52,9 +54,6 @@
     private static final String TAG = NetworkWatchlistService.class.getSimpleName();
     static final boolean DEBUG = false;
 
-    private static final String PROPERTY_NETWORK_WATCHLIST_ENABLED =
-            "ro.network_watchlist_enabled";
-
     private static final int MAX_NUM_OF_WATCHLIST_DIGESTS = 10000;
 
     public static class Lifecycle extends SystemService {
@@ -66,8 +65,10 @@
 
         @Override
         public void onStart() {
-            if (!SystemProperties.getBoolean(PROPERTY_NETWORK_WATCHLIST_ENABLED, false)) {
+            if (Settings.Global.getInt(getContext().getContentResolver(),
+                    Settings.Global.NETWORK_WATCHLIST_ENABLED, 0) == 0) {
                 // Watchlist service is disabled
+                Slog.i(TAG, "Network Watchlist service is disabled");
                 return;
             }
             mService = new NetworkWatchlistService(getContext());
@@ -76,11 +77,13 @@
 
         @Override
         public void onBootPhase(int phase) {
-            if (!SystemProperties.getBoolean(PROPERTY_NETWORK_WATCHLIST_ENABLED, false)) {
-                // Watchlist service is disabled
-                return;
-            }
             if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+                if (Settings.Global.getInt(getContext().getContentResolver(),
+                        Settings.Global.NETWORK_WATCHLIST_ENABLED, 0) == 0) {
+                    // Watchlist service is disabled
+                    Slog.i(TAG, "Network Watchlist service is disabled");
+                    return;
+                }
                 try {
                     mService.initIpConnectivityMetrics();
                     mService.startWatchlistLogging();
@@ -92,6 +95,7 @@
         }
     }
 
+    @GuardedBy("mLoggingSwitchLock")
     private volatile boolean mIsLoggingEnabled = false;
     private final Object mLoggingSwitchLock = new Object();
 
@@ -220,36 +224,11 @@
         }
     }
 
-    /**
-     * Set a new network watchlist.
-     * This method should be called by ConfigUpdater only.
-     *
-     * @return True if network watchlist is updated.
-     */
-    public boolean setNetworkSecurityWatchlist(List<byte[]> domainsCrc32Digests,
-            List<byte[]> domainsSha256Digests,
-            List<byte[]> ipAddressesCrc32Digests,
-            List<byte[]> ipAddressesSha256Digests) {
-        Slog.i(TAG, "Setting network watchlist");
-        if (domainsCrc32Digests == null || domainsSha256Digests == null
-                || ipAddressesCrc32Digests == null || ipAddressesSha256Digests == null) {
-            Slog.e(TAG, "Parameters cannot be null");
-            return false;
-        }
-        if (domainsCrc32Digests.size() != domainsSha256Digests.size()
-                || ipAddressesCrc32Digests.size() != ipAddressesSha256Digests.size()) {
-            Slog.e(TAG, "Must need to have the same number of CRC32 and SHA256 digests");
-            return false;
-        }
-        if (domainsSha256Digests.size() + ipAddressesSha256Digests.size()
-                > MAX_NUM_OF_WATCHLIST_DIGESTS) {
-            Slog.e(TAG, "Total watchlist size cannot exceed " + MAX_NUM_OF_WATCHLIST_DIGESTS);
-            return false;
-        }
-        mSettings.writeSettingsToDisk(domainsCrc32Digests, domainsSha256Digests,
-                ipAddressesCrc32Digests, ipAddressesSha256Digests);
-        Slog.i(TAG, "Set network watchlist: Success");
-        return true;
+    @Override
+    public void reloadWatchlist() throws RemoteException {
+        enforceWatchlistLoggingPermission();
+        Slog.i(TAG, "Reloading watchlist");
+        mSettings.reloadSettings();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
index f48463f..838aa53 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
@@ -21,10 +21,12 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Environment;
 import android.util.Pair;
 
 import com.android.internal.util.HexDump;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.GregorianCalendar;
 import java.util.HashMap;
@@ -83,9 +85,12 @@
         HashMap<String, String> appDigestCNCList;
     }
 
+    static File getSystemWatchlistDbFile() {
+        return new File(Environment.getDataSystemDirectory(), NAME);
+    }
+
     private WatchlistReportDbHelper(Context context) {
-        super(context, WatchlistSettings.getSystemWatchlistFile(NAME).getAbsolutePath(),
-                null, VERSION);
+        super(context, getSystemWatchlistDbFile().getAbsolutePath(), null, VERSION);
         // Memory optimization - close idle connections after 30s of inactivity
         setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
     }
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
index c50f0d5..70002ea 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
@@ -19,8 +19,10 @@
 import android.os.Environment;
 import android.util.AtomicFile;
 import android.util.Log;
+import android.util.Slog;
 import android.util.Xml;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.HexDump;
@@ -51,10 +53,9 @@
 class WatchlistSettings {
     private static final String TAG = "WatchlistSettings";
 
-    // Settings xml will be stored in /data/system/network_watchlist/watchlist_settings.xml
-    static final String SYSTEM_WATCHLIST_DIR = "network_watchlist";
-
-    private static final String WATCHLIST_XML_FILE = "watchlist_settings.xml";
+    // Watchlist config that pushed by ConfigUpdater.
+    private static final String NETWORK_WATCHLIST_DB_PATH =
+            "/data/misc/network_watchlist/network_watchlist.xml";
 
     private static class XmlTags {
         private static final String WATCHLIST_SETTINGS = "watchlist-settings";
@@ -65,86 +66,74 @@
         private static final String HASH = "hash";
     }
 
-    private static WatchlistSettings sInstance = new WatchlistSettings();
-    private final AtomicFile mXmlFile;
-    private final Object mLock = new Object();
-    private HarmfulDigests mCrc32DomainDigests = new HarmfulDigests(new ArrayList<>());
-    private HarmfulDigests mSha256DomainDigests = new HarmfulDigests(new ArrayList<>());
-    private HarmfulDigests mCrc32IpDigests = new HarmfulDigests(new ArrayList<>());
-    private HarmfulDigests mSha256IpDigests = new HarmfulDigests(new ArrayList<>());
+    private static class CrcShaDigests {
+        final HarmfulDigests crc32Digests;
+        final HarmfulDigests sha256Digests;
 
-    public static synchronized WatchlistSettings getInstance() {
+        public CrcShaDigests(HarmfulDigests crc32Digests, HarmfulDigests sha256Digests) {
+            this.crc32Digests = crc32Digests;
+            this.sha256Digests = sha256Digests;
+        }
+    }
+
+    private final static WatchlistSettings sInstance = new WatchlistSettings();
+    private final AtomicFile mXmlFile;
+
+    private volatile CrcShaDigests mDomainDigests;
+    private volatile CrcShaDigests mIpDigests;
+
+    public static WatchlistSettings getInstance() {
         return sInstance;
     }
 
     private WatchlistSettings() {
-        this(getSystemWatchlistFile(WATCHLIST_XML_FILE));
+        this(new File(NETWORK_WATCHLIST_DB_PATH));
     }
 
     @VisibleForTesting
     protected WatchlistSettings(File xmlFile) {
         mXmlFile = new AtomicFile(xmlFile);
-        readSettingsLocked();
+        reloadSettings();
     }
 
-    static File getSystemWatchlistFile(String filename) {
-        final File dataSystemDir = Environment.getDataSystemDirectory();
-        final File systemWatchlistDir = new File(dataSystemDir, SYSTEM_WATCHLIST_DIR);
-        systemWatchlistDir.mkdirs();
-        return new File(systemWatchlistDir, filename);
-    }
-
-    private void readSettingsLocked() {
-        synchronized (mLock) {
-            FileInputStream stream;
-            try {
-                stream = mXmlFile.openRead();
-            } catch (FileNotFoundException e) {
-                Log.i(TAG, "No watchlist settings: " + mXmlFile.getBaseFile().getAbsolutePath());
-                return;
-            }
+    public void reloadSettings() {
+        try (FileInputStream stream = mXmlFile.openRead()){
 
             final List<byte[]> crc32DomainList = new ArrayList<>();
             final List<byte[]> sha256DomainList = new ArrayList<>();
             final List<byte[]> crc32IpList = new ArrayList<>();
             final List<byte[]> sha256IpList = new ArrayList<>();
 
-            try {
-                XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(stream, StandardCharsets.UTF_8.name());
-                parser.nextTag();
-                parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
-                while (parser.nextTag() == XmlPullParser.START_TAG) {
-                    String tagName = parser.getName();
-                    switch (tagName) {
-                        case XmlTags.CRC32_DOMAIN:
-                            parseHash(parser, tagName, crc32DomainList);
-                            break;
-                        case XmlTags.CRC32_IP:
-                            parseHash(parser, tagName, crc32IpList);
-                            break;
-                        case XmlTags.SHA256_DOMAIN:
-                            parseHash(parser, tagName, sha256DomainList);
-                            break;
-                        case XmlTags.SHA256_IP:
-                            parseHash(parser, tagName, sha256IpList);
-                            break;
-                        default:
-                            Log.w(TAG, "Unknown element: " + parser.getName());
-                            XmlUtils.skipCurrentTag(parser);
-                    }
-                }
-                parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
-                writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
-            } catch (IllegalStateException | NullPointerException | NumberFormatException |
-                    XmlPullParserException | IOException | IndexOutOfBoundsException e) {
-                Log.w(TAG, "Failed parsing " + e);
-            } finally {
-                try {
-                    stream.close();
-                } catch (IOException e) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, StandardCharsets.UTF_8.name());
+            parser.nextTag();
+            parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+            while (parser.nextTag() == XmlPullParser.START_TAG) {
+                String tagName = parser.getName();
+                switch (tagName) {
+                    case XmlTags.CRC32_DOMAIN:
+                        parseHash(parser, tagName, crc32DomainList);
+                        break;
+                    case XmlTags.CRC32_IP:
+                        parseHash(parser, tagName, crc32IpList);
+                        break;
+                    case XmlTags.SHA256_DOMAIN:
+                        parseHash(parser, tagName, sha256DomainList);
+                        break;
+                    case XmlTags.SHA256_IP:
+                        parseHash(parser, tagName, sha256IpList);
+                        break;
+                    default:
+                        Log.w(TAG, "Unknown element: " + parser.getName());
+                        XmlUtils.skipCurrentTag(parser);
                 }
             }
+            parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+            writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
+            Log.i(TAG, "Reload watchlist done");
+        } catch (IllegalStateException | NullPointerException | NumberFormatException |
+                XmlPullParserException | IOException | IndexOutOfBoundsException e) {
+            Slog.e(TAG, "Failed parsing xml", e);
         }
     }
 
@@ -161,101 +150,61 @@
     }
 
     /**
-     * Write network watchlist settings to disk.
-     * Adb should not use it, should use writeSettingsToMemory directly instead.
-     */
-    public void writeSettingsToDisk(List<byte[]> newCrc32DomainList,
-            List<byte[]> newSha256DomainList,
-            List<byte[]> newCrc32IpList,
-            List<byte[]> newSha256IpList) {
-        synchronized (mLock) {
-            FileOutputStream stream;
-            try {
-                stream = mXmlFile.startWrite();
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to write display settings: " + e);
-                return;
-            }
-
-            try {
-                XmlSerializer out = new FastXmlSerializer();
-                out.setOutput(stream, StandardCharsets.UTF_8.name());
-                out.startDocument(null, true);
-                out.startTag(null, XmlTags.WATCHLIST_SETTINGS);
-
-                writeHashSetToXml(out, XmlTags.SHA256_DOMAIN, newSha256DomainList);
-                writeHashSetToXml(out, XmlTags.SHA256_IP, newSha256IpList);
-                writeHashSetToXml(out, XmlTags.CRC32_DOMAIN, newCrc32DomainList);
-                writeHashSetToXml(out, XmlTags.CRC32_IP, newCrc32IpList);
-
-                out.endTag(null, XmlTags.WATCHLIST_SETTINGS);
-                out.endDocument();
-                mXmlFile.finishWrite(stream);
-                writeSettingsToMemory(newCrc32DomainList, newSha256DomainList, newCrc32IpList,
-                        newSha256IpList);
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to write display settings, restoring backup.", e);
-                mXmlFile.failWrite(stream);
-            }
-        }
-    }
-
-    /**
      * Write network watchlist settings to memory.
      */
     public void writeSettingsToMemory(List<byte[]> newCrc32DomainList,
             List<byte[]> newSha256DomainList,
             List<byte[]> newCrc32IpList,
             List<byte[]> newSha256IpList) {
-        synchronized (mLock) {
-            mCrc32DomainDigests = new HarmfulDigests(newCrc32DomainList);
-            mCrc32IpDigests = new HarmfulDigests(newCrc32IpList);
-            mSha256DomainDigests = new HarmfulDigests(newSha256DomainList);
-            mSha256IpDigests = new HarmfulDigests(newSha256IpList);
-        }
-    }
-
-    private static void writeHashSetToXml(XmlSerializer out, String tagName, List<byte[]> hashSet)
-            throws IOException {
-        out.startTag(null, tagName);
-        for (byte[] hash : hashSet) {
-            out.startTag(null, XmlTags.HASH);
-            out.text(HexDump.toHexString(hash));
-            out.endTag(null, XmlTags.HASH);
-        }
-        out.endTag(null, tagName);
+        mDomainDigests = new CrcShaDigests(new HarmfulDigests(newCrc32DomainList),
+                new HarmfulDigests(newSha256DomainList));
+        mIpDigests = new CrcShaDigests(new HarmfulDigests(newCrc32IpList),
+                new HarmfulDigests(newSha256IpList));
     }
 
     public boolean containsDomain(String domain) {
+        final CrcShaDigests domainDigests = mDomainDigests;
+        if (domainDigests == null) {
+            Slog.wtf(TAG, "domainDigests should not be null");
+            return false;
+        }
         // First it does a quick CRC32 check.
         final byte[] crc32 = getCrc32(domain);
-        if (!mCrc32DomainDigests.contains(crc32)) {
+        if (!domainDigests.crc32Digests.contains(crc32)) {
             return false;
         }
         // Now we do a slow SHA256 check.
         final byte[] sha256 = getSha256(domain);
-        return mSha256DomainDigests.contains(sha256);
+        return domainDigests.sha256Digests.contains(sha256);
     }
 
     public boolean containsIp(String ip) {
+        final CrcShaDigests ipDigests = mIpDigests;
+        if (ipDigests == null) {
+            Slog.wtf(TAG, "ipDigests should not be null");
+            return false;
+        }
         // First it does a quick CRC32 check.
         final byte[] crc32 = getCrc32(ip);
-        if (!mCrc32IpDigests.contains(crc32)) {
+        if (!ipDigests.crc32Digests.contains(crc32)) {
             return false;
         }
         // Now we do a slow SHA256 check.
         final byte[] sha256 = getSha256(ip);
-        return mSha256IpDigests.contains(sha256);
+        return ipDigests.sha256Digests.contains(sha256);
     }
 
 
-    /** Get CRC32 of a string */
+    /** Get CRC32 of a string
+     *
+     * TODO: Review if we should use CRC32 or other algorithms
+     */
     private byte[] getCrc32(String str) {
         final CRC32 crc = new CRC32();
         crc.update(str.getBytes());
         final long tmp = crc.getValue();
-        return new byte[]{(byte)(tmp >> 24 & 255), (byte)(tmp >> 16 & 255),
-                (byte)(tmp >> 8 & 255), (byte)(tmp & 255)};
+        return new byte[]{(byte) (tmp >> 24 & 255), (byte) (tmp >> 16 & 255),
+                (byte) (tmp >> 8 & 255), (byte) (tmp & 255)};
     }
 
     /** Get SHA256 of a string */
@@ -273,12 +222,12 @@
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Domain CRC32 digest list:");
-        mCrc32DomainDigests.dump(fd, pw, args);
+        mDomainDigests.crc32Digests.dump(fd, pw, args);
         pw.println("Domain SHA256 digest list:");
-        mSha256DomainDigests.dump(fd, pw, args);
+        mDomainDigests.sha256Digests.dump(fd, pw, args);
         pw.println("Ip CRC32 digest list:");
-        mCrc32IpDigests.dump(fd, pw, args);
+        mIpDigests.crc32Digests.dump(fd, pw, args);
         pw.println("Ip SHA256 digest list:");
-        mSha256IpDigests.dump(fd, pw, args);
+        mIpDigests.sha256Digests.dump(fd, pw, args);
     }
 }
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 7d64aed4..37e6ae9 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -251,23 +251,17 @@
         for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
             if (filter != null && !filter.matches(cmpt)) continue;
 
-            final long cToken = proto.start(ManagedServicesProto.ENABLED);
-            cmpt.toProto(proto);
-            proto.end(cToken);
+            cmpt.writeToProto(proto, ManagedServicesProto.ENABLED);
         }
 
         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);
+            info.writeToProto(proto, ManagedServicesProto.LIVE_SERVICES, this);
         }
 
         for (ComponentName name : mSnoozingForCurrentProfiles) {
-            final long cToken = proto.start(ManagedServicesProto.SNOOZED);
-            name.toProto(proto);
-            proto.end(cToken);
+            name.writeToProto(proto, ManagedServicesProto.SNOOZED);
         }
     }
 
@@ -1132,14 +1126,16 @@
                     .append(']').toString();
         }
 
-        public void toProto(ProtoOutputStream proto, ManagedServices host) {
-            final long cToken = proto.start(ManagedServiceInfoProto.COMPONENT);
-            component.toProto(proto);
-            proto.end(cToken);
+        public void writeToProto(ProtoOutputStream proto, long fieldId, ManagedServices host) {
+            final long token = proto.start(fieldId);
+
+            component.writeToProto(proto, ManagedServiceInfoProto.COMPONENT);
             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));
+
+            proto.end(token);
         }
 
         public boolean enabledAndUserMatches(int nid) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index cf01400..575c44d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3376,9 +3376,7 @@
                     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);
+                    listener.writeToProto(proto, ListenersDisablingEffectsProto.LISTENERS, null);
                 }
 
                 proto.end(effectsToken);
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 5b3d1ec..2a2ff06 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -178,14 +178,21 @@
             }
         }
 
+        /** Currently MasterClearConfirm will call isOemUnlockAllowed()
+         * to sync PersistentDataBlockOemUnlockAllowedBit which
+         * is needed before factory reset
+         * TODO: Figure out better place to run sync e.g. adding new API
+         */
         @Override
         public boolean isOemUnlockAllowed() {
             enforceOemUnlockReadPermission();
 
             final long token = Binder.clearCallingIdentity();
             try {
-                return mOemLock.isOemUnlockAllowedByCarrier() &&
-                        mOemLock.isOemUnlockAllowedByDevice();
+                boolean allowed = mOemLock.isOemUnlockAllowedByCarrier()
+                        && mOemLock.isOemUnlockAllowedByDevice();
+                setPersistentDataBlockOemUnlockAllowedBit(allowed);
+                return allowed;
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -213,7 +220,8 @@
         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)) {
+        if (pdbm != null && !(mOemLock instanceof PersistentDataBlockLock)
+                && pdbm.getOemUnlockEnabled() != allowed) {
             Slog.i(TAG, "Update OEM Unlock bit in pst partition to " + allowed);
             pdbm.setOemUnlockEnabled(allowed);
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 14128a7..16fae99 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -415,7 +415,12 @@
             params.installFlags |= PackageManager.INSTALL_FROM_ADB;
 
         } else {
-            mAppOps.checkPackage(callingUid, installerPackageName);
+            // Only apps with INSTALL_PACKAGES are allowed to set an installer that is not the
+            // caller.
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) !=
+                    PackageManager.PERMISSION_GRANTED) {
+                mAppOps.checkPackage(callingUid, installerPackageName);
+            }
 
             params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
             params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5cf08dc..5577de8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -346,13 +346,14 @@
                 || (isSelfUpdatePermissionGranted
                     && mPm.getPackageUid(mPackageName, 0, userId) == mInstallerUid);
         final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
+        final boolean isInstallerSystem = (mInstallerUid == Process.SYSTEM_UID);
         final boolean forcePermissionPrompt =
                 (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
 
         // Device owners and affiliated profile owners  are allowed to silently install packages, so
         // the permission check is waived if the installer is the device owner.
         return forcePermissionPrompt || !(isPermissionGranted || isInstallerRoot
-                || isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked());
+                || isInstallerSystem || isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked());
     }
 
     public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 696d895..44aad44 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -163,10 +163,12 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageList;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
 import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ActivityIntentInfo;
 import android.content.pm.PackageParser.Package;
@@ -757,6 +759,9 @@
     @GuardedBy("mPackages")
     final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers = new SparseArray<>();
 
+    @GuardedBy("mPackages")
+    final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>();
+
     class PackageParserCallback implements PackageParser.Callback {
         @Override public final boolean hasFeature(String feature) {
             return PackageManagerService.this.hasSystemFeature(feature, 0);
@@ -2095,6 +2100,10 @@
                 }
             }
 
+            if (allNewUsers && !update) {
+                notifyPackageAdded(packageName);
+            }
+
             // Log current value of "unknown sources" setting
             EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
                     getUnknownSourcesSettings());
@@ -12983,6 +12992,34 @@
         });
     }
 
+    @Override
+    public void notifyPackageAdded(String packageName) {
+        final PackageListObserver[] observers;
+        synchronized (mPackages) {
+            if (mPackageListObservers.size() == 0) {
+                return;
+            }
+            observers = (PackageListObserver[]) mPackageListObservers.toArray();
+        }
+        for (int i = observers.length - 1; i >= 0; --i) {
+            observers[i].onPackageAdded(packageName);
+        }
+    }
+
+    @Override
+    public void notifyPackageRemoved(String packageName) {
+        final PackageListObserver[] observers;
+        synchronized (mPackages) {
+            if (mPackageListObservers.size() == 0) {
+                return;
+            }
+            observers = (PackageListObserver[]) mPackageListObservers.toArray();
+        }
+        for (int i = observers.length - 1; i >= 0; --i) {
+            observers[i].onPackageRemoved(packageName);
+        }
+    }
+
     /**
      * Sends a broadcast for the given action.
      * <p>If {@code isInstantApp} is {@code true}, then the broadcast is protected with
@@ -13104,80 +13141,6 @@
         }
     }
 
-    @Override
-    public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
-            int installFlags, String installerPackageName, int userId) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
-
-        final int callingUid = Binder.getCallingUid();
-        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, true /* checkShell */, "installPackageAsUser");
-
-        if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
-            try {
-                if (observer != null) {
-                    observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null);
-                }
-            } catch (RemoteException re) {
-            }
-            return;
-        }
-
-        if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
-            installFlags |= PackageManager.INSTALL_FROM_ADB;
-
-        } else {
-            // Caller holds INSTALL_PACKAGES permission, so we're less strict
-            // about installerPackageName.
-
-            installFlags &= ~PackageManager.INSTALL_FROM_ADB;
-            installFlags &= ~PackageManager.INSTALL_ALL_USERS;
-        }
-
-        UserHandle user;
-        if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
-            user = UserHandle.ALL;
-        } else {
-            user = new UserHandle(userId);
-        }
-
-        // Only system components can circumvent runtime permissions when installing.
-        if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
-                && mContext.checkCallingOrSelfPermission(Manifest.permission
-                .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
-            throw new SecurityException("You need the "
-                    + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
-                    + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
-        }
-
-        if ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0
-                || (installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
-            throw new IllegalArgumentException(
-                    "New installs into ASEC containers no longer supported");
-        }
-
-        final File originFile = new File(originPath);
-        final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
-
-        final Message msg = mHandler.obtainMessage(INIT_COPY);
-        final VerificationInfo verificationInfo = new VerificationInfo(
-                null /*originatingUri*/, null /*referrer*/, -1 /*originatingUid*/, callingUid);
-        final InstallParams params = new InstallParams(origin, null /*moveInfo*/, observer,
-                installFlags, installerPackageName, null /*volumeUuid*/, verificationInfo, user,
-                null /*packageAbiOverride*/, null /*grantedPermissions*/,
-                null /*certificates*/, PackageManager.INSTALL_REASON_UNKNOWN);
-        params.setTraceMethod("installAsUser").setTraceCookie(System.identityHashCode(params));
-        msg.obj = params;
-
-        Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installAsUser",
-                System.identityHashCode(msg.obj));
-        Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
-                System.identityHashCode(msg.obj));
-
-        mHandler.sendMessage(msg);
-    }
-
-
     /**
      * Ensure that the install reason matches what we know about the package installer (e.g. whether
      * it is acting on behalf on an enterprise or the user).
@@ -17640,6 +17603,7 @@
                         removedPackage, extras,
                         Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
                         null, null, broadcastUsers, instantUserIds);
+                    packageSender.notifyPackageRemoved(removedPackage);
                 }
             }
             if (removedAppId >= 0) {
@@ -20395,10 +20359,9 @@
             }
         }
         sUserManager.systemReady();
-
         // If we upgraded grant all default permissions before kicking off.
         for (int userId : grantPermissionsUserIds) {
-            mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
+            mDefaultPermissionPolicy.grantDefaultPermissions(userId);
         }
 
         if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
@@ -22445,8 +22408,8 @@
     }
 
     void onNewUserCreated(final int userId) {
+        mDefaultPermissionPolicy.grantDefaultPermissions(userId);
         synchronized(mPackages) {
-            mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
             // If permission review for legacy apps is required, we represent
             // dagerous permissions for such apps as always granted runtime
             // permissions to keep per user flag state whether review is needed.
@@ -22933,6 +22896,29 @@
         }
 
         @Override
+        public PackageList getPackageList(PackageListObserver observer) {
+            synchronized (mPackages) {
+                final int N = mPackages.size();
+                final ArrayList<String> list = new ArrayList<>(N);
+                for (int i = 0; i < N; i++) {
+                    list.add(mPackages.keyAt(i));
+                }
+                final PackageList packageList = new PackageList(list, observer);
+                if (observer != null) {
+                    mPackageListObservers.add(packageList);
+                }
+                return packageList;
+            }
+        }
+
+        @Override
+        public void removePackageListObserver(PackageListObserver observer) {
+            synchronized (mPackages) {
+                mPackageListObservers.remove(observer);
+            }
+        }
+
+        @Override
         public PackageParser.Package getDisabledPackage(String packageName) {
             synchronized (mPackages) {
                 final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
@@ -22989,6 +22975,11 @@
         }
 
         @Override
+        public void setUseOpenWifiAppPackagesProvider(PackagesProvider provider) {
+            mDefaultPermissionPolicy.setUseOpenWifiAppPackagesProvider(provider);
+        }
+
+        @Override
         public void setSyncAdapterPackagesprovider(SyncAdapterPackagesProvider provider) {
             mDefaultPermissionPolicy.setSyncAdapterPackagesProvider(provider);
         }
@@ -23013,6 +23004,12 @@
         }
 
         @Override
+        public void grantDefaultPermissionsToDefaultUseOpenWifiApp(String packageName, int userId) {
+            mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultUseOpenWifiApp(
+                    packageName, userId);
+        }
+
+        @Override
         public void setKeepUninstalledPackages(final List<String> packageList) {
             Preconditions.checkNotNull(packageList);
             List<String> removedFromList = null;
@@ -23594,4 +23591,6 @@
         final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds);
     void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
         boolean includeStopped, int appId, int[] userIds, int[] instantUserIds);
+    void notifyPackageAdded(String packageName);
+    void notifyPackageRemoved(String packageName);
 }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 648f847..4cf1814 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3122,7 +3122,7 @@
                             ATTR_VOLUME_UUID);
                     final VersionInfo ver = findOrCreateVersion(volumeUuid);
                     ver.sdkVersion = XmlUtils.readIntAttribute(parser, ATTR_SDK_VERSION);
-                    ver.databaseVersion = XmlUtils.readIntAttribute(parser, ATTR_SDK_VERSION);
+                    ver.databaseVersion = XmlUtils.readIntAttribute(parser, ATTR_DATABASE_VERSION);
                     ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT);
                 } else {
                     Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 7bab318..ebf6672 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -459,8 +459,7 @@
         }
 
         // Then, for the pinned set for each launcher, set the pin flag one by one.
-        mShortcutUser.mService.getUserShortcutsLocked(getPackageUserId())
-                .forAllLaunchers(launcherShortcuts -> {
+        mShortcutUser.forAllLaunchers(launcherShortcuts -> {
             final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
                     getPackageName(), getPackageUserId());
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 768eb8f..7d57566 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -27,7 +27,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
-import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.KeyguardManager;
@@ -64,6 +63,7 @@
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -80,6 +80,7 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
+import android.util.SparseLongArray;
 import android.util.TimeUtils;
 import android.util.Xml;
 
@@ -242,8 +243,7 @@
     private static final IBinder mUserRestriconToken = new Binder();
 
     /**
-     * User-related information that is used for persisting to flash. Only UserInfo is
-     * directly exposed to other system apps.
+     * Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps.
      */
     @VisibleForTesting
     static class UserData {
@@ -261,6 +261,12 @@
         // Whether to perist the seed account information to be available after a boot
         boolean persistSeedData;
 
+        /** Elapsed realtime since boot when the user started. */
+        long startRealtime;
+
+        /** Elapsed realtime since boot when the user was unlocked. */
+        long unlockRealtime;
+
         void clearSeedAccountData() {
             seedAccountName = null;
             seedAccountType = null;
@@ -454,6 +460,37 @@
                 mUms.cleanupPartialUsers();
             }
         }
+
+        @Override
+        public void onStartUser(int userHandle) {
+            synchronized (mUms.mUsersLock) {
+                final UserData user = mUms.getUserDataLU(userHandle);
+                if (user != null) {
+                    user.startRealtime = SystemClock.elapsedRealtime();
+                }
+            }
+        }
+
+        @Override
+        public void onUnlockUser(int userHandle) {
+            synchronized (mUms.mUsersLock) {
+                final UserData user = mUms.getUserDataLU(userHandle);
+                if (user != null) {
+                    user.unlockRealtime = SystemClock.elapsedRealtime();
+                }
+            }
+        }
+
+        @Override
+        public void onStopUser(int userHandle) {
+            synchronized (mUms.mUsersLock) {
+                final UserData user = mUms.getUserDataLU(userHandle);
+                if (user != null) {
+                    user.startRealtime = 0;
+                    user.unlockRealtime = 0;
+                }
+            }
+        }
     }
 
     // TODO b/28848102 Add support for test dependencies injection
@@ -795,12 +832,7 @@
                     "target should only be specified when we are disabling quiet mode.");
         }
 
-        if (!isAllowedToSetWorkMode(callingPackage, Binder.getCallingUid())) {
-            throw new SecurityException("Not allowed to call trySetQuietModeEnabled, "
-                    + "caller is foreground default launcher "
-                    + "nor with MANAGE_USERS/MODIFY_QUIET_MODE permission");
-        }
-
+        ensureCanModifyQuietMode(callingPackage, Binder.getCallingUid(), target != null);
         final long identity = Binder.clearCallingIdentity();
         try {
             if (enableQuietMode) {
@@ -824,35 +856,44 @@
     }
 
     /**
-     * An app can modify quiet mode if the caller meets one of the condition:
+     * The caller can modify quiet mode if it meets one of these conditions:
      * <ul>
      *     <li>Has system UID or root UID</li>
      *     <li>Has {@link Manifest.permission#MODIFY_QUIET_MODE}</li>
      *     <li>Has {@link Manifest.permission#MANAGE_USERS}</li>
      * </ul>
+     * <p>
+     * If caller wants to start an intent after disabling the quiet mode, it must has
+     * {@link Manifest.permission#MANAGE_USERS}.
      */
-    private boolean isAllowedToSetWorkMode(String callingPackage, int callingUid) {
+    private void ensureCanModifyQuietMode(String callingPackage, int callingUid,
+            boolean startIntent) {
         if (hasManageUsersPermission()) {
-            return true;
+            return;
         }
-
+        if (startIntent) {
+            throw new SecurityException("MANAGE_USERS permission is required to start intent "
+                    + "after disabling quiet mode.");
+        }
         final boolean hasModifyQuietModePermission = ActivityManager.checkComponentPermission(
                 Manifest.permission.MODIFY_QUIET_MODE,
                 callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
         if (hasModifyQuietModePermission) {
-            return true;
+            return;
         }
 
+        verifyCallingPackage(callingPackage, callingUid);
         final ShortcutServiceInternal shortcutInternal =
                 LocalServices.getService(ShortcutServiceInternal.class);
         if (shortcutInternal != null) {
             boolean isForegroundLauncher =
                     shortcutInternal.isForegroundDefaultLauncher(callingPackage, callingUid);
             if (isForegroundLauncher) {
-                return true;
+                return;
             }
         }
-        return false;
+        throw new SecurityException("Can't modify quiet mode, caller is neither foreground "
+                + "default launcher nor has MANAGE_USERS/MODIFY_QUIET_MODE permission");
     }
 
     private void setQuietModeEnabled(
@@ -1054,6 +1095,29 @@
         return mLocalService.isUserRunning(userId);
     }
 
+    @Override
+    public long getUserStartRealtime() {
+        final int userId = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized (mUsersLock) {
+            final UserData user = getUserDataLU(userId);
+            if (user != null) {
+                return user.startRealtime;
+            }
+            return 0;
+        }
+    }
+
+    @Override
+    public long getUserUnlockRealtime() {
+        synchronized (mUsersLock) {
+            final UserData user = getUserDataLU(UserHandle.getUserId(Binder.getCallingUid()));
+            if (user != null) {
+                return user.unlockRealtime;
+            }
+            return 0;
+        }
+    }
+
     private void checkManageOrInteractPermIfCallerInOtherProfileGroup(int userId, String name) {
         int callingUserId = UserHandle.getCallingUserId();
         if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) ||
@@ -3481,6 +3545,7 @@
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
 
         long now = System.currentTimeMillis();
+        final long nowRealtime = SystemClock.elapsedRealtime();
         StringBuilder sb = new StringBuilder();
         synchronized (mPackagesLock) {
             synchronized (mUsersLock) {
@@ -3508,25 +3573,20 @@
                     }
                     pw.println(UserState.stateToString(state));
                     pw.print("    Created: ");
-                    if (userInfo.creationTime == 0) {
-                        pw.println("<unknown>");
-                    } else {
-                        sb.setLength(0);
-                        TimeUtils.formatDuration(now - userInfo.creationTime, sb);
-                        sb.append(" ago");
-                        pw.println(sb);
-                    }
+                    dumpTimeAgo(pw, sb, now, userInfo.creationTime);
+
                     pw.print("    Last logged in: ");
-                    if (userInfo.lastLoggedInTime == 0) {
-                        pw.println("<unknown>");
-                    } else {
-                        sb.setLength(0);
-                        TimeUtils.formatDuration(now - userInfo.lastLoggedInTime, sb);
-                        sb.append(" ago");
-                        pw.println(sb);
-                    }
+                    dumpTimeAgo(pw, sb, now, userInfo.lastLoggedInTime);
+
                     pw.print("    Last logged in fingerprint: ");
                     pw.println(userInfo.lastLoggedInFingerprint);
+
+                    pw.print("    Start time: ");
+                    dumpTimeAgo(pw, sb, nowRealtime, userData.startRealtime);
+
+                    pw.print("    Unlock time: ");
+                    dumpTimeAgo(pw, sb, nowRealtime, userData.unlockRealtime);
+
                     pw.print("    Has profile owner: ");
                     pw.println(mIsUserManaged.get(userId));
                     pw.println("    Restrictions:");
@@ -3590,6 +3650,17 @@
         }
     }
 
+    private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) {
+        if (time == 0) {
+            pw.println("<unknown>");
+        } else {
+            sb.setLength(0);
+            TimeUtils.formatDuration(nowTime - time, sb);
+            sb.append(" ago");
+            pw.println(sb);
+        }
+    }
+
     final class MainHandler extends Handler {
 
         @Override
@@ -3932,4 +4003,16 @@
             return false;
         }
     }
+
+    /**
+     * Check if the calling package name matches with the calling UID, throw
+     * {@link SecurityException} if not.
+     */
+    private void verifyCallingPackage(String callingPackage, int callingUid) {
+        int packageUid = mPm.getPackageUid(callingPackage, 0,  UserHandle.getUserId(callingUid));
+        if (packageUid != callingUid) {
+            throw new SecurityException("Specified package " + callingPackage
+                    + " does not match the calling uid " + callingUid);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 4564988..bfba700 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -115,6 +115,8 @@
             UserManager.DISALLOW_AUTOFILL,
             UserManager.DISALLOW_USER_SWITCH,
             UserManager.DISALLOW_UNIFIED_PASSWORD,
+            UserManager.DISALLOW_CONFIG_LOCATION_MODE,
+            UserManager.DISALLOW_AIRPLANE_MODE
     });
 
     /**
@@ -142,7 +144,8 @@
             UserManager.DISALLOW_FUN,
             UserManager.DISALLOW_SAFE_BOOT,
             UserManager.DISALLOW_CREATE_WINDOWS,
-            UserManager.DISALLOW_DATA_ROAMING
+            UserManager.DISALLOW_DATA_ROAMING,
+            UserManager.DISALLOW_AIRPLANE_MODE
     );
 
     /**
@@ -197,7 +200,8 @@
      * Special user restrictions that are always applied to all users no matter who sets them.
      */
     private static final Set<String> PROFILE_GLOBAL_RESTRICTIONS = Sets.newArraySet(
-            UserManager.ENSURE_VERIFY_APPS
+            UserManager.ENSURE_VERIFY_APPS,
+            UserManager.DISALLOW_AIRPLANE_MODE
     );
 
     /**
@@ -456,7 +460,8 @@
                         // DISALLOW_DATA_ROAMING user restriction is set.
 
                         // Multi sim device.
-                        SubscriptionManager subscriptionManager = new SubscriptionManager(context);
+                        SubscriptionManager subscriptionManager = context
+                                .getSystemService(SubscriptionManager.class);
                         final List<SubscriptionInfo> subscriptionInfoList =
                             subscriptionManager.getActiveSubscriptionInfoList();
                         if (subscriptionInfoList != null) {
diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
index 854b704..d6281c5 100644
--- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -74,8 +75,6 @@
     public void startActivityAsUser(
             String callingPackage,
             ComponentName component,
-            Rect sourceBounds,
-            Bundle startActivityOptions,
             UserHandle user) throws RemoteException {
         Preconditions.checkNotNull(callingPackage);
         Preconditions.checkNotNull(component);
@@ -103,7 +102,6 @@
         // CATEGORY_LAUNCHER as calling startActivityAsUser ignore them if component is present.
         final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
         launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        launchIntent.setSourceBounds(sourceBounds);
         launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
         // Only package name is set here, as opposed to component name, because intent action and
@@ -114,7 +112,8 @@
         final long ident = mInjector.clearCallingIdentity();
         try {
             launchIntent.setComponent(component);
-            mContext.startActivityAsUser(launchIntent, startActivityOptions, user);
+            mContext.startActivityAsUser(launchIntent,
+                    ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(), user);
         } finally {
             mInjector.restoreCallingIdentity(ident);
         }
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 01f3c57..34c3ce3 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -30,6 +30,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageList;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
@@ -134,6 +135,11 @@
         LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
     }
 
+    private static final Set<String> COARSE_LOCATION_PERMISSIONS = new ArraySet<>();
+    static {
+        COARSE_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);
@@ -182,6 +188,7 @@
     private PackagesProvider mSmsAppPackagesProvider;
     private PackagesProvider mDialerAppPackagesProvider;
     private PackagesProvider mSimCallManagerPackagesProvider;
+    private PackagesProvider mUseOpenWifiAppPackagesProvider;
     private SyncAdapterPackagesProvider mSyncAdapterPackagesProvider;
 
     private ArrayMap<String, List<DefaultPermissionGrant>> mGrantExceptions;
@@ -246,17 +253,23 @@
         }
     }
 
+    public void setUseOpenWifiAppPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mUseOpenWifiAppPackagesProvider = provider;
+        }
+    }
+
     public void setSyncAdapterPackagesProvider(SyncAdapterPackagesProvider provider) {
         synchronized (mLock) {
             mSyncAdapterPackagesProvider = provider;
         }
     }
 
-    public void grantDefaultPermissions(Collection<PackageParser.Package> packages, int userId) {
+    public void grantDefaultPermissions(int userId) {
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
-            grantAllRuntimePermissions(packages, userId);
+            grantAllRuntimePermissions(userId);
         } else {
-            grantPermissionsToSysComponentsAndPrivApps(packages, userId);
+            grantPermissionsToSysComponentsAndPrivApps(userId);
             grantDefaultSystemHandlerPermissions(userId);
             grantDefaultPermissionExceptions(userId);
         }
@@ -278,10 +291,14 @@
         }
     }
 
-    private void grantAllRuntimePermissions(
-            Collection<PackageParser.Package> packages, int userId) {
+    private void grantAllRuntimePermissions(int userId) {
         Log.i(TAG, "Granting all runtime permissions for user " + userId);
-        for (PackageParser.Package pkg : packages) {
+        final PackageList packageList = mServiceInternal.getPackageList();
+        for (String packageName : packageList.getPackageNames()) {
+            final PackageParser.Package pkg = mServiceInternal.getPackage(packageName);
+            if (pkg == null) {
+                continue;
+            }
             grantRuntimePermissionsForPackage(userId, pkg);
         }
     }
@@ -290,10 +307,14 @@
         mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
     }
 
-    private void grantPermissionsToSysComponentsAndPrivApps(
-            Collection<PackageParser.Package> packages, int userId) {
+    private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
         Log.i(TAG, "Granting permissions to platform components for user " + userId);
-        for (PackageParser.Package pkg : packages) {
+        final PackageList packageList = mServiceInternal.getPackageList();
+        for (String packageName : packageList.getPackageNames()) {
+            final PackageParser.Package pkg = mServiceInternal.getPackage(packageName);
+            if (pkg == null) {
+                continue;
+            }
             if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg)
                     || !doesPackageSupportRuntimePermissions(pkg)
                     || pkg.requestedPermissions.isEmpty()) {
@@ -311,6 +332,7 @@
         final PackagesProvider smsAppPackagesProvider;
         final PackagesProvider dialerAppPackagesProvider;
         final PackagesProvider simCallManagerPackagesProvider;
+        final PackagesProvider useOpenWifiAppPackagesProvider;
         final SyncAdapterPackagesProvider syncAdapterPackagesProvider;
 
         synchronized (mLock) {
@@ -319,6 +341,7 @@
             smsAppPackagesProvider = mSmsAppPackagesProvider;
             dialerAppPackagesProvider = mDialerAppPackagesProvider;
             simCallManagerPackagesProvider = mSimCallManagerPackagesProvider;
+            useOpenWifiAppPackagesProvider = mUseOpenWifiAppPackagesProvider;
             syncAdapterPackagesProvider = mSyncAdapterPackagesProvider;
         }
 
@@ -332,6 +355,8 @@
                 ? dialerAppPackagesProvider.getPackages(userId) : null;
         String[] simCallManagerPackageNames = (simCallManagerPackagesProvider != null)
                 ? simCallManagerPackagesProvider.getPackages(userId) : null;
+        String[] useOpenWifiAppPackageNames = (useOpenWifiAppPackagesProvider != null)
+                ? useOpenWifiAppPackagesProvider.getPackages(userId) : null;
         String[] contactsSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
                 syncAdapterPackagesProvider.getPackages(ContactsContract.AUTHORITY, userId) : null;
         String[] calendarSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
@@ -449,6 +474,18 @@
             }
         }
 
+        // Use Open Wifi
+        if (useOpenWifiAppPackageNames != null) {
+            for (String useOpenWifiPackageName : useOpenWifiAppPackageNames) {
+                PackageParser.Package useOpenWifiPackage =
+                        getSystemPackage(useOpenWifiPackageName);
+                if (useOpenWifiPackage != null) {
+                    grantDefaultPermissionsToDefaultSystemUseOpenWifiApp(useOpenWifiPackage,
+                            userId);
+                }
+            }
+        }
+
         // SMS
         if (smsAppPackageNames == null) {
             Intent smsIntent = new Intent(Intent.ACTION_MAIN);
@@ -818,6 +855,13 @@
         }
     }
 
+    private void grantDefaultPermissionsToDefaultSystemUseOpenWifiApp(
+            PackageParser.Package useOpenWifiPackage, int userId) {
+        if (doesPackageSupportRuntimePermissions(useOpenWifiPackage)) {
+            grantRuntimePermissions(useOpenWifiPackage, COARSE_LOCATION_PERMISSIONS, userId);
+        }
+    }
+
     public void grantDefaultPermissionsToDefaultSmsApp(String packageName, int userId) {
         Log.i(TAG, "Granting permissions to default sms app for user:" + userId);
         if (packageName == null) {
@@ -850,6 +894,19 @@
         }
     }
 
+    public void grantDefaultPermissionsToDefaultUseOpenWifiApp(String packageName, int userId) {
+        Log.i(TAG, "Granting permissions to default Use Open WiFi app for user:" + userId);
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package useOpenWifiPackage = getPackage(packageName);
+        if (useOpenWifiPackage != null
+                && doesPackageSupportRuntimePermissions(useOpenWifiPackage)) {
+            grantRuntimePermissions(
+                    useOpenWifiPackage, COARSE_LOCATION_PERMISSIONS, false, true, userId);
+        }
+    }
+
     private void grantDefaultPermissionsToDefaultSimCallManager(
             PackageParser.Package simCallManagerPackage, int userId) {
         Log.i(TAG, "Granting permissions to sim call manager for user:" + userId);
@@ -1005,7 +1062,7 @@
     }
 
     private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
-            boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
+            boolean systemFixed, boolean ignoreSystemPackage, int userId) {
         if (pkg.requestedPermissions.isEmpty()) {
             return;
         }
@@ -1013,13 +1070,13 @@
         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
+        // In some cases, like for the Phone or SMS app, we grant permissions regardless
+        // of if the version on the system image declares the permission as used since
+        // selecting the app as the default for that function 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()) {
+        if (!ignoreSystemPackage && pkg.isUpdatedSystemApp()) {
             final PackageParser.Package disabledPkg =
                     mServiceInternal.getDisabledPackage(pkg.packageName);
             if (disabledPkg != null) {
@@ -1053,7 +1110,7 @@
                 // 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) {
+                if (flags == 0 || ignoreSystemPackage) {
                     // Never clobber policy or system.
                     final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
                             | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index 6c8c9b2..ffc4731 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -5,3 +5,4 @@
 per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com
 per-file DefaultPermissionGrantPolicy.java = toddke@google.com
 per-file DefaultPermissionGrantPolicy.java = yamasani@google.com
+per-file DefaultPermissionGrantPolicy.java = patb@google.com
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9752a57..076c0e4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2126,14 +2126,14 @@
         mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
             @Override
             public int onAppTransitionStartingLocked(int transit, IBinder openToken,
-                    IBinder closeToken,
-                    Animation openAnimation, Animation closeAnimation) {
-                return handleStartTransitionForKeyguardLw(transit, openAnimation);
+                    IBinder closeToken, long duration, long statusBarAnimationStartTime,
+                    long statusBarAnimationDuration) {
+                return handleStartTransitionForKeyguardLw(transit, duration);
             }
 
             @Override
             public void onAppTransitionCancelledLocked(int transit) {
-                handleStartTransitionForKeyguardLw(transit, null /* transit */);
+                handleStartTransitionForKeyguardLw(transit, 0 /* duration */);
             }
         });
         mKeyguardDelegate = new KeyguardServiceDelegate(mContext,
@@ -3989,7 +3989,7 @@
         }
     }
 
-    private int handleStartTransitionForKeyguardLw(int transit, @Nullable Animation anim) {
+    private int handleStartTransitionForKeyguardLw(int transit, long duration) {
         if (mKeyguardOccludedChanged) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
                     + mPendingKeyguardOccluded);
@@ -4000,13 +4000,7 @@
         }
         if (AppTransition.isKeyguardGoingAwayTransit(transit)) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
-            final long startTime = anim != null
-                    ? SystemClock.uptimeMillis() + anim.getStartOffset()
-                    : SystemClock.uptimeMillis();
-            final long duration = anim != null
-                    ? anim.getDuration()
-                    : 0;
-            startKeyguardExitAnimation(startTime, duration);
+            startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
         }
         return 0;
     }
diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java
index af7e91c..e6e4d7f 100644
--- a/services/core/java/com/android/server/policy/StatusBarController.java
+++ b/services/core/java/com/android/server/policy/StatusBarController.java
@@ -37,8 +37,6 @@
  */
 public class StatusBarController extends BarController {
 
-    private static final long TRANSITION_DURATION = 120L;
-
     private final AppTransitionListener mAppTransitionListener
             = new AppTransitionListener() {
 
@@ -57,17 +55,15 @@
 
         @Override
         public int onAppTransitionStartingLocked(int transit, IBinder openToken,
-                IBinder closeToken, final Animation openAnimation, final Animation closeAnimation) {
+                IBinder closeToken, long duration, long statusBarAnimationStartTime,
+                long statusBarAnimationDuration) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     StatusBarManagerInternal statusbar = getStatusBarInternal();
                     if (statusbar != null) {
-                        long startTime = calculateStatusBarTransitionStartTime(openAnimation,
-                                closeAnimation);
-                        long duration = closeAnimation != null || openAnimation != null
-                                ? TRANSITION_DURATION : 0;
-                        statusbar.appTransitionStarting(startTime, duration);
+                        statusbar.appTransitionStarting(statusBarAnimationStartTime,
+                                statusBarAnimationDuration);
                     }
                 }
             });
@@ -128,72 +124,4 @@
     public AppTransitionListener getAppTransitionListener() {
         return mAppTransitionListener;
     }
-
-    /**
-     * For a given app transition with {@code openAnimation} and {@code closeAnimation}, this
-     * calculates the timings for the corresponding status bar transition.
-     *
-     * @return the desired start time of the status bar transition, in uptime millis
-     */
-    private static long calculateStatusBarTransitionStartTime(Animation openAnimation,
-            Animation closeAnimation) {
-        if (openAnimation != null && closeAnimation != null) {
-            TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation);
-            TranslateAnimation closeTranslateAnimation = findTranslateAnimation(closeAnimation);
-            if (openTranslateAnimation != null) {
-
-                // Some interpolators are extremely quickly mostly finished, but not completely. For
-                // our purposes, we need to find the fraction for which ther interpolator is mostly
-                // there, and use that value for the calculation.
-                float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
-                return SystemClock.uptimeMillis()
-                        + openTranslateAnimation.getStartOffset()
-                        + (long)(openTranslateAnimation.getDuration()*t) - TRANSITION_DURATION;
-            } else if (closeTranslateAnimation != null) {
-                return SystemClock.uptimeMillis();
-            } else {
-                return SystemClock.uptimeMillis();
-            }
-        } else {
-            return SystemClock.uptimeMillis();
-        }
-    }
-
-    /**
-     * Tries to find a {@link TranslateAnimation} inside the {@code animation}.
-     *
-     * @return the found animation, {@code null} otherwise
-     */
-    private static TranslateAnimation findTranslateAnimation(Animation animation) {
-        if (animation instanceof TranslateAnimation) {
-            return (TranslateAnimation) animation;
-        } else if (animation instanceof AnimationSet) {
-            AnimationSet set = (AnimationSet) animation;
-            for (int i = 0; i < set.getAnimations().size(); i++) {
-                Animation a = set.getAnimations().get(i);
-                if (a instanceof TranslateAnimation) {
-                    return (TranslateAnimation) a;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
-     * {@code interpolator(t + eps) > 0.99}.
-     */
-    private static float findAlmostThereFraction(Interpolator interpolator) {
-        float val = 0.5f;
-        float adj = 0.25f;
-        while (adj >= 0.01f) {
-            if (interpolator.getInterpolation(val) < 0.99f) {
-                val += adj;
-            } else {
-                val -= adj;
-            }
-            adj /= 2;
-        }
-        return val;
-    }
 }
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index e5a23ea..2a153ec 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -209,10 +209,7 @@
 
         try {
             if (workSource != null) {
-                final int N = workSource.size();
-                for (int i=0; i<N; i++) {
-                    mBatteryStats.noteLongPartialWakelockStart(tag, historyTag, workSource.get(i));
-                }
+                mBatteryStats.noteLongPartialWakelockStartFromSource(tag, historyTag, workSource);
             } else {
                 mBatteryStats.noteLongPartialWakelockStart(tag, historyTag, ownerUid);
             }
@@ -230,10 +227,7 @@
 
         try {
             if (workSource != null) {
-                final int N = workSource.size();
-                for (int i=0; i<N; i++) {
-                    mBatteryStats.noteLongPartialWakelockFinish(tag, historyTag, workSource.get(i));
-                }
+                mBatteryStats.noteLongPartialWakelockFinishFromSource(tag, historyTag, workSource);
             } else {
                 mBatteryStats.noteLongPartialWakelockFinish(tag, historyTag, ownerUid);
             }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 0b590bc..86b22bb 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -57,6 +57,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.dreams.DreamManagerInternal;
@@ -1976,6 +1977,16 @@
                     return true;
                 }
             }
+
+            final ArrayList<WorkChain> workChains = wakeLock.mWorkSource.getWorkChains();
+            if (workChains != null) {
+                for (int k = 0; k < workChains.size(); k++) {
+                    final int uid = workChains.get(k).getAttributionUid();
+                    if (userId == UserHandle.getUserId(uid)) {
+                        return true;
+                    }
+                }
+            }
         }
         return userId == UserHandle.getUserId(wakeLock.mOwnerUid);
     }
@@ -2441,6 +2452,7 @@
             float screenAutoBrightnessAdjustment = 0.0f;
             boolean autoBrightness = (mScreenBrightnessModeSetting ==
                     Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+            boolean brightnessIsTemporary = false;
             if (!mBootCompleted) {
                 // Keep the brightness steady during boot. This requires the
                 // bootloader brightness and the default brightness to be identical.
@@ -2455,6 +2467,7 @@
                 brightnessSetByUser = false;
             } else if (isValidBrightness(mTemporaryScreenBrightnessSettingOverride)) {
                 screenBrightness = mTemporaryScreenBrightnessSettingOverride;
+                brightnessIsTemporary = true;
             } else if (isValidBrightness(mScreenBrightnessSetting)) {
                 screenBrightness = mScreenBrightnessSetting;
             }
@@ -2464,6 +2477,7 @@
                         mTemporaryScreenAutoBrightnessAdjustmentSettingOverride)) {
                     screenAutoBrightnessAdjustment =
                             mTemporaryScreenAutoBrightnessAdjustmentSettingOverride;
+                    brightnessIsTemporary = true;
                 } else if (isValidAutoBrightnessAdjustment(
                         mScreenAutoBrightnessAdjustmentSetting)) {
                     screenAutoBrightnessAdjustment = mScreenAutoBrightnessAdjustmentSetting;
@@ -2479,6 +2493,7 @@
             mDisplayPowerRequest.screenAutoBrightnessAdjustment =
                     screenAutoBrightnessAdjustment;
             mDisplayPowerRequest.brightnessSetByUser = brightnessSetByUser;
+            mDisplayPowerRequest.brightnessIsTemporary = brightnessIsTemporary;
             mDisplayPowerRequest.useAutoBrightness = autoBrightness;
             mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
             mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();
@@ -4324,7 +4339,7 @@
                 mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.DEVICE_POWER, null);
             }
-            if (ws != null && ws.size() != 0) {
+            if (ws != null && !ws.isEmpty()) {
                 mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.UPDATE_DEVICE_STATS, null);
             } else {
@@ -4379,7 +4394,7 @@
             }
 
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
-            if (ws != null && ws.size() != 0) {
+            if (ws != null && !ws.isEmpty()) {
                 mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.UPDATE_DEVICE_STATS, null);
             } else {
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index a35383f..f7cc443 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -40,6 +40,7 @@
 import android.os.storage.VolumeInfo;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
+import android.util.DataUnit;
 import android.util.Slog;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -80,13 +81,13 @@
 
     private static final int MSG_CHECK = 1;
 
-    private static final long DEFAULT_LOG_DELTA_BYTES = 64 * TrafficStats.MB_IN_BYTES;
+    private static final long DEFAULT_LOG_DELTA_BYTES = DataUnit.MEBIBYTES.toBytes(64);
     private static final long DEFAULT_CHECK_INTERVAL = DateUtils.MINUTE_IN_MILLIS;
 
     // com.android.internal.R.string.low_internal_storage_view_text_no_boot
     // hard codes 250MB in the message as the storage space required for the
     // boot image.
-    private static final long BOOT_IMAGE_STORAGE_REQUIREMENT = 250 * TrafficStats.MB_IN_BYTES;
+    private static final long BOOT_IMAGE_STORAGE_REQUIREMENT = DataUnit.MEBIBYTES.toBytes(250);
 
     private NotificationManager mNotifManager;
 
diff --git a/services/core/java/com/android/server/updates/CarrierIdInstallReceiver.java b/services/core/java/com/android/server/updates/CarrierIdInstallReceiver.java
new file mode 100644
index 0000000..116fe7f
--- /dev/null
+++ b/services/core/java/com/android/server/updates/CarrierIdInstallReceiver.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 com.android.server.updates;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.Telephony;
+import android.util.Log;
+
+public class CarrierIdInstallReceiver extends ConfigUpdateInstallReceiver {
+
+    public CarrierIdInstallReceiver() {
+        super("/data/misc/carrierid", "carrier_list.pb", "metadata/", "version");
+    }
+
+    @Override
+    protected void postInstall(Context context, Intent intent) {
+        ContentResolver resolver = context.getContentResolver();
+        resolver.update(Uri.withAppendedPath(Telephony.CarrierIdentification.CONTENT_URI,
+                "update_db"), new ContentValues(), null, null);
+    }
+}
diff --git a/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java b/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java
new file mode 100644
index 0000000..3b7ddc2
--- /dev/null
+++ b/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java
@@ -0,0 +1,40 @@
+/*
+ * 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.updates;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkWatchlistManager;
+import android.os.RemoteException;
+import android.util.Slog;
+
+public class NetworkWatchlistInstallReceiver extends ConfigUpdateInstallReceiver {
+
+    public NetworkWatchlistInstallReceiver() {
+        super("/data/misc/network_watchlist/", "network_watchlist.xml", "metadata/", "version");
+    }
+
+    @Override
+    protected void postInstall(Context context, Intent intent) {
+        try {
+            context.getSystemService(NetworkWatchlistManager.class).reloadWatchlist();
+        } catch (Exception e) {
+            // Network Watchlist is not available
+            Slog.wtf("NetworkWatchlistInstallReceiver", "Unable to reload watchlist");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 8e916ad..844aafb 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -956,7 +956,7 @@
                 }
                 if (mInfo != null && mInfo.getSupportsAmbientMode()) {
                     try {
-                        mEngine.setInAmbientMode(mInAmbientMode);
+                        mEngine.setInAmbientMode(mInAmbientMode, false /* animated */);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to set ambient mode state", e);
                     }
@@ -1751,7 +1751,7 @@
         }
     }
 
-    public void setInAmbientMode(boolean inAmbienMode) {
+    public void setInAmbientMode(boolean inAmbienMode, boolean animated) {
         final IWallpaperEngine engine;
         synchronized (mLock) {
             mInAmbientMode = inAmbienMode;
@@ -1766,7 +1766,7 @@
 
         if (engine != null) {
             try {
-                engine.setInAmbientMode(inAmbienMode);
+                engine.setInAmbientMode(inAmbienMode, animated);
             } catch (RemoteException e) {
                 // Cannot talk to wallpaper engine.
             }
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 84d47b4..ed4543e 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -30,6 +30,8 @@
  */
 interface AnimationAdapter {
 
+    long STATUS_BAR_TRANSITION_DURATION = 120L;
+
     /**
      * @return Whether we should detach the wallpaper during the animation.
      * @see Animation#setDetachWallpaper
@@ -66,4 +68,13 @@
      * @return The approximate duration of the animation, in milliseconds.
      */
     long getDurationHint();
+
+    /**
+     * If this animation is run as an app opening animation, this calculates the start time for all
+     * status bar transitions that happen as part of the app opening animation, which will be
+     * forwarded to SystemUI.
+     *
+     * @return the desired start time of the status bar transition, in uptime millis
+     */
+    long getStatusBarTransitionsStartTime();
 }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index c2cbced..d4b437a 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -38,38 +38,45 @@
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.proto.AppTransitionProto.APP_TRANSITION_STATE;
 import static com.android.server.wm.proto.AppTransitionProto.LAST_USED_APP_TRANSITION;
 
+import android.annotation.DrawableRes;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.GraphicBuffer;
 import android.graphics.Path;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Binder;
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.AppTransitionAnimationSpec;
+import android.view.DisplayListCanvas;
 import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
 import android.view.WindowManager;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -199,6 +206,13 @@
     private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN = 6;
     private static final int NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE = 7;
     private static final int NEXT_TRANSIT_TYPE_CLIP_REVEAL = 8;
+
+    /**
+     * Refers to the transition to activity started by using {@link
+     * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle)
+     * }.
+     */
+    private static final int NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS = 9;
     private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
 
     // These are the possible states for the enter/exit activities during a thumbnail transition
@@ -383,6 +397,11 @@
                 mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
     }
 
+
+    boolean isNextAppTransitionOpenCrossProfileApps() {
+        return mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
+    }
+
     /**
      * @return true if and only if we are currently fetching app transition specs from the future
      *         passed into {@link #overridePendingAppTransitionMultiThumbFuture}
@@ -407,17 +426,23 @@
      * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
      *         layout pass needs to be done
      */
-    int goodToGo(int transit, AppWindowAnimator topOpeningAppAnimator,
-            AppWindowAnimator topClosingAppAnimator, ArraySet<AppWindowToken> openingApps,
+    int goodToGo(int transit, AppWindowToken topOpeningApp,
+            AppWindowToken topClosingApp, ArraySet<AppWindowToken> openingApps,
             ArraySet<AppWindowToken> closingApps) {
         mNextAppTransition = TRANSIT_UNSET;
         mNextAppTransitionFlags = 0;
         setAppTransitionState(APP_STATE_RUNNING);
+        final AnimationAdapter topOpeningAnim = topOpeningApp != null
+                ? topOpeningApp.getAnimation()
+                : null;
         int redoLayout = notifyAppTransitionStartingLocked(transit,
-                topOpeningAppAnimator != null ? topOpeningAppAnimator.mAppToken.token : null,
-                topClosingAppAnimator != null ? topClosingAppAnimator.mAppToken.token : null,
-                topOpeningAppAnimator != null ? topOpeningAppAnimator.animation : null,
-                topClosingAppAnimator != null ? topClosingAppAnimator.animation : null);
+                topOpeningApp != null ? topOpeningApp.token : null,
+                topClosingApp != null ? topClosingApp.token : null,
+                topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
+                topOpeningAnim != null
+                        ? topOpeningAnim.getStatusBarTransitionsStartTime()
+                        : SystemClock.uptimeMillis(),
+                AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
         mService.getDefaultDisplayContentLocked().getDockedDividerController()
                 .notifyAppTransitionStarting(openingApps, transit);
 
@@ -425,8 +450,8 @@
         // ended it already then we don't need to wait.
         if (transit == TRANSIT_DOCK_TASK_FROM_RECENTS && !mProlongedAnimationsEnded) {
             for (int i = openingApps.size() - 1; i >= 0; i--) {
-                final AppWindowAnimator appAnimator = openingApps.valueAt(i).mAppAnimator;
-                appAnimator.startProlongAnimation(PROLONG_ANIMATION_AT_START);
+                final AppWindowToken app = openingApps.valueAt(i);
+                app.startDelayingAnimationStart();
             }
         }
         return redoLayout;
@@ -496,11 +521,12 @@
     }
 
     private int notifyAppTransitionStartingLocked(int transit, IBinder openToken,
-            IBinder closeToken, Animation openAnimation, Animation closeAnimation) {
+            IBinder closeToken, long duration, long statusBarAnimationStartTime,
+            long statusBarAnimationDuration) {
         int redoLayout = 0;
         for (int i = 0; i < mListeners.size(); i++) {
             redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken,
-                    closeToken, openAnimation, closeAnimation);
+                    closeToken, duration, statusBarAnimationStartTime, statusBarAnimationDuration);
         }
         return redoLayout;
     }
@@ -963,6 +989,43 @@
     }
 
     /**
+     * Creates an overlay with a background color and a thumbnail for the cross profile apps
+     * animation.
+     */
+    GraphicBuffer createCrossProfileAppsThumbnail(
+            @DrawableRes int thumbnailDrawableRes, Rect frame) {
+        final int width = frame.width();
+        final int height = frame.height();
+
+        final RenderNode node = RenderNode.create("CrossProfileAppsThumbnail", null);
+        node.setLeftTopRightBottom(0, 0, width, height);
+        node.setClipToBounds(false);
+
+        final DisplayListCanvas canvas = node.start(width, height);
+        canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
+        final int thumbnailSize = mService.mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
+        final Drawable drawable = mService.mContext.getDrawable(thumbnailDrawableRes);
+        drawable.setBounds(
+                (width - thumbnailSize) / 2,
+                (height - thumbnailSize) / 2,
+                (width + thumbnailSize) / 2,
+                (height + thumbnailSize) / 2);
+        drawable.draw(canvas);
+        node.end(canvas);
+
+        return ThreadedRenderer.createHardwareBitmap(node, width, height)
+                .createGraphicBufferHandle();
+    }
+
+    Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
+        final Animation animation = loadAnimationRes(
+                "android", com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
+        return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
+                appRect.height(), 0, null);
+    }
+
+    /**
      * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
      * when a thumbnail is specified with the pending animation override.
      */
@@ -1605,6 +1668,18 @@
                         + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                         + " Callers=" + Debug.getCallers(3));
             }
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS
+                && (transit == TRANSIT_ACTIVITY_OPEN
+                        || transit == TRANSIT_TASK_OPEN
+                        || transit == TRANSIT_TASK_TO_FRONT)) {
+
+            a = loadAnimationRes("android", enter
+                    ? com.android.internal.R.anim.task_open_enter_cross_profile_apps
+                    : com.android.internal.R.anim.task_open_exit);
+            Slog.v(TAG,
+                    "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:"
+                            + " anim=" + a + " transit=" + appTransitionToString(transit)
+                            + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
         } else {
             int animAttr = 0;
             switch (transit) {
@@ -1833,6 +1908,17 @@
     }
 
     /**
+     * @see {@link #NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS}
+     */
+    void overridePendingAppTransitionStartCrossProfileApps() {
+        if (isTransitionSet()) {
+            clear();
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
+            postAnimationCallback();
+        }
+    }
+
+    /**
      * If a future is set for the app transition specs, fetch it in another thread.
      */
     private void fetchAppTransitionSpecsFromFuture() {
@@ -1855,9 +1941,6 @@
                             mNextAppTransitionFutureCallback, null /* finishedCallback */,
                             mNextAppTransitionScaleUp);
                     mNextAppTransitionFutureCallback = null;
-                    if (specs != null) {
-                        mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
-                    }
                 }
                 mService.requestTraversal();
             });
@@ -1973,6 +2056,8 @@
                 return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP";
             case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN:
                 return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN";
+            case NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:
+                return "NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS";
             default:
                 return "unknown type=" + mNextAppTransitionType;
         }
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
deleted file mode 100644
index 5c1d5b2..0000000
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ /dev/null
@@ -1,495 +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.server.wm;
-
-import static com.android.server.policy.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;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
-
-import android.graphics.Matrix;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-public class AppWindowAnimator {
-    static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowAnimator" : TAG_WM;
-
-    private static final int PROLONG_ANIMATION_DISABLED = 0;
-    static final int PROLONG_ANIMATION_AT_END = 1;
-    static final int PROLONG_ANIMATION_AT_START = 2;
-
-    final AppWindowToken mAppToken;
-    final WindowManagerService mService;
-    final WindowAnimator mAnimator;
-
-    boolean animating;
-    boolean wasAnimating;
-    Animation animation;
-    boolean hasTransformation;
-    final Transformation transformation = new Transformation();
-
-    // Have we been asked to have this token keep the screen frozen?
-    // Protect with mAnimator.
-    boolean freezingScreen;
-
-    /**
-     * How long we last kept the screen frozen.
-     */
-    int lastFreezeDuration;
-
-    // Offset to the window of all layers in the token, for use by
-    // AppWindowToken animations.
-    int animLayerAdjustment;
-
-    // Propagated from AppWindowToken.allDrawn, to determine when
-    // the state changes.
-    boolean allDrawn;
-
-    // Special surface for thumbnail animation.  If deferThumbnailDestruction is enabled, then we
-    // will make sure that the thumbnail is destroyed after the other surface is completed.  This
-    // requires that the duration of the two animations are the same.
-    SurfaceControl thumbnail;
-    int thumbnailTransactionSeq;
-    private int mThumbnailLayer;
-
-    Animation thumbnailAnimation;
-    final Transformation thumbnailTransformation = new Transformation();
-    // This flag indicates that the destruction of the thumbnail surface is synchronized with
-    // another animation, so defer the destruction of this thumbnail surface for a single frame
-    // after the secondary animation completes.
-    boolean deferThumbnailDestruction;
-    // This flag is set if the animator has deferThumbnailDestruction set and has reached the final
-    // frame of animation.  It will extend the animation by one frame and then clean up afterwards.
-    boolean deferFinalFrameCleanup;
-    // If true when the animation hits the last frame, it will keep running on that last frame.
-    // This is used to synchronize animation with Recents and we wait for Recents to tell us to
-    // finish or for a new animation be set as fail-safe mechanism.
-    private int mProlongAnimation;
-    // Whether the prolong animation can be removed when animation is set. The purpose of this is
-    // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it
-    // when new animation is set.
-    private boolean mClearProlongedAnimation;
-    private int mTransit;
-    private int mTransitFlags;
-
-    /** WindowStateAnimator from mAppAnimator.allAppWindows as of last performLayout */
-    ArrayList<WindowStateAnimator> mAllAppWinAnimators = new ArrayList<>();
-
-    /** True if the current animation was transferred from another AppWindowAnimator.
-     *  See {@link #transferCurrentAnimation}*/
-    boolean usingTransferredAnimation = false;
-
-    private boolean mSkipFirstFrame = false;
-    private int mStackClip = STACK_CLIP_BEFORE_ANIM;
-
-    static final Animation sDummyAnimation = new DummyAnimation();
-
-    public AppWindowAnimator(final AppWindowToken atoken, WindowManagerService service) {
-        mAppToken = atoken;
-        mService = service;
-        mAnimator = mService.mAnimator;
-    }
-
-    public void setAnimation(Animation anim, int width, int height, int parentWidth,
-            int parentHeight, boolean skipFirstFrame, int stackClip, int transit,
-            int transitFlags) {
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting animation in " + mAppToken
-                + ": " + anim + " wxh=" + width + "x" + height
-                + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
-        animation = anim;
-        animating = false;
-        if (!anim.isInitialized()) {
-            anim.initialize(width, height, parentWidth, parentHeight);
-        }
-        anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
-        anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
-        int zorder = anim.getZAdjustment();
-        int adj = 0;
-        if (zorder == Animation.ZORDER_TOP) {
-            adj = TYPE_LAYER_OFFSET;
-        } else if (zorder == Animation.ZORDER_BOTTOM) {
-            adj = -TYPE_LAYER_OFFSET;
-        }
-
-        if (animLayerAdjustment != adj) {
-            animLayerAdjustment = adj;
-            updateLayers();
-        }
-        // Start out animation gone if window is gone, or visible if window is visible.
-        transformation.clear();
-        transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
-        hasTransformation = true;
-        mStackClip = stackClip;
-
-        mSkipFirstFrame = skipFirstFrame;
-        mTransit = transit;
-        mTransitFlags = transitFlags;
-
-        if (!mAppToken.fillsParent()) {
-            anim.setBackgroundColor(0);
-        }
-        if (mClearProlongedAnimation) {
-            mProlongAnimation = PROLONG_ANIMATION_DISABLED;
-        } else {
-            mClearProlongedAnimation = true;
-        }
-    }
-
-    public void setDummyAnimation() {
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken
-                + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
-        animation = sDummyAnimation;
-        hasTransformation = true;
-        transformation.clear();
-        transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
-    }
-
-    void setNullAnimation() {
-        animation = null;
-        usingTransferredAnimation = false;
-    }
-
-    public void clearAnimation() {
-        if (animation != null) {
-            animating = true;
-        }
-        clearThumbnail();
-        setNullAnimation();
-        if (mAppToken.deferClearAllDrawn) {
-            mAppToken.clearAllDrawn();
-        }
-        mStackClip = STACK_CLIP_BEFORE_ANIM;
-        mTransit = TRANSIT_UNSET;
-        mTransitFlags = 0;
-    }
-
-    public boolean isAnimating() {
-        return animation != null || mAppToken.inPendingTransaction;
-    }
-
-    /**
-     * @return whether an animation is about to start, i.e. the animation is set already but we
-     *         haven't processed the first frame yet.
-     */
-    boolean isAnimationStarting() {
-        return animation != null && !animating;
-    }
-
-    public int getTransit() {
-        return mTransit;
-    }
-
-    int getTransitFlags() {
-        return mTransitFlags;
-    }
-
-    public void clearThumbnail() {
-        if (thumbnail != null) {
-            thumbnail.hide();
-            mService.mWindowPlacerLocked.destroyAfterTransaction(thumbnail);
-            thumbnail = null;
-        }
-        deferThumbnailDestruction = false;
-    }
-
-    int getStackClip() {
-        return mStackClip;
-    }
-
-    void transferCurrentAnimation(
-            AppWindowAnimator toAppAnimator, WindowStateAnimator transferWinAnimator) {
-
-        if (animation != null) {
-            toAppAnimator.animation = animation;
-            toAppAnimator.animating = animating;
-            toAppAnimator.animLayerAdjustment = animLayerAdjustment;
-            setNullAnimation();
-            animLayerAdjustment = 0;
-            toAppAnimator.updateLayers();
-            updateLayers();
-            toAppAnimator.usingTransferredAnimation = true;
-            toAppAnimator.mTransit = mTransit;
-        }
-        if (transferWinAnimator != null) {
-            mAllAppWinAnimators.remove(transferWinAnimator);
-            toAppAnimator.mAllAppWinAnimators.add(transferWinAnimator);
-            toAppAnimator.hasTransformation = transferWinAnimator.mAppAnimator.hasTransformation;
-            if (toAppAnimator.hasTransformation) {
-                toAppAnimator.transformation.set(transferWinAnimator.mAppAnimator.transformation);
-            } else {
-                toAppAnimator.transformation.clear();
-            }
-            transferWinAnimator.mAppAnimator = toAppAnimator;
-        }
-    }
-
-    private void updateLayers() {
-        mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
-    }
-
-    private void stepThumbnailAnimation(long currentTime) {
-        thumbnailTransformation.clear();
-        final long animationFrameTime = getAnimationFrameTime(thumbnailAnimation, currentTime);
-        thumbnailAnimation.getTransformation(animationFrameTime, thumbnailTransformation);
-
-        ScreenRotationAnimation screenRotationAnimation =
-                mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
-        final boolean screenAnimation = screenRotationAnimation != null
-                && screenRotationAnimation.isAnimating();
-        if (screenAnimation) {
-            thumbnailTransformation.postCompose(screenRotationAnimation.getEnterTransformation());
-        }
-        // cache often used attributes locally
-        final float tmpFloats[] = mService.mTmpFloats;
-        thumbnailTransformation.getMatrix().getValues(tmpFloats);
-        if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
-                "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X]
-                + ", " + tmpFloats[Matrix.MTRANS_Y]);
-        thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
-        if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
-                "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
-                + " layer=" + mThumbnailLayer
-                + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
-                + "," + tmpFloats[Matrix.MSKEW_Y]
-                + "][" + tmpFloats[Matrix.MSKEW_X]
-                + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
-        thumbnail.setAlpha(thumbnailTransformation.getAlpha());
-        thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
-                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
-        thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
-    }
-
-    /**
-     * 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.
-     */
-    private long getAnimationFrameTime(Animation animation, long currentTime) {
-        if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
-            animation.setStartTime(currentTime);
-            return currentTime + 1;
-        }
-        return currentTime;
-    }
-
-    private boolean stepAnimation(long currentTime) {
-        if (animation == null) {
-            return false;
-        }
-        transformation.clear();
-        final long animationFrameTime = getAnimationFrameTime(animation, currentTime);
-        boolean hasMoreFrames = animation.getTransformation(animationFrameTime, transformation);
-        if (!hasMoreFrames) {
-            if (deferThumbnailDestruction && !deferFinalFrameCleanup) {
-                // We are deferring the thumbnail destruction, so extend the animation for one more
-                // (dummy) frame before we clean up
-                deferFinalFrameCleanup = true;
-                hasMoreFrames = true;
-            } else {
-                if (false && DEBUG_ANIM) Slog.v(TAG,
-                        "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames +
-                        ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation);
-                deferFinalFrameCleanup = false;
-                if (mProlongAnimation == PROLONG_ANIMATION_AT_END) {
-                    hasMoreFrames = true;
-                } else {
-                    setNullAnimation();
-                    clearThumbnail();
-                    if (DEBUG_ANIM) Slog.v(TAG, "Finished animation in " + mAppToken + " @ "
-                            + currentTime);
-                }
-            }
-        }
-        hasTransformation = hasMoreFrames;
-        return hasMoreFrames;
-    }
-
-    private long getStartTimeCorrection() {
-        if (mSkipFirstFrame) {
-
-            // If the transition is an animation in which the first frame doesn't change the screen
-            // contents at all, we can just skip it and start at the second frame. So we shift the
-            // start time of the animation forward by minus the frame duration.
-            return -Choreographer.getInstance().getFrameIntervalNanos() / TimeUtils.NANOS_PER_MS;
-        } else {
-            return 0;
-        }
-    }
-
-    // This must be called while inside a transaction.
-    boolean stepAnimationLocked(long currentTime) {
-        if (mAppToken.okToAnimate()) {
-            // We will run animations as long as the display isn't frozen.
-
-            if (animation == sDummyAnimation) {
-                // This guy is going to animate, but not yet.  For now count
-                // it as not animating for purposes of scheduling transactions;
-                // when it is really time to animate, this will be set to
-                // a real animation and the next call will execute normally.
-                return false;
-            }
-
-            if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
-                    && animation != null) {
-                if (!animating) {
-                    if (DEBUG_ANIM) Slog.v(TAG,
-                        "Starting animation in " + mAppToken +
-                        " @ " + currentTime + " scale="
-                        + mService.getTransitionAnimationScaleLocked()
-                        + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating);
-                    long correction = getStartTimeCorrection();
-                    animation.setStartTime(currentTime + correction);
-                    animating = true;
-                    if (thumbnail != null) {
-                        thumbnail.show();
-                        thumbnailAnimation.setStartTime(currentTime + correction);
-                    }
-                    mSkipFirstFrame = false;
-                }
-                if (stepAnimation(currentTime)) {
-                    // animation isn't over, step any thumbnail and that's
-                    // it for now.
-                    if (thumbnail != null) {
-                        stepThumbnailAnimation(currentTime);
-                    }
-                    return true;
-                }
-            }
-        } else if (animation != null) {
-            // If the display is frozen, and there is a pending animation,
-            // clear it and make sure we run the cleanup code.
-            animating = true;
-            animation = null;
-        }
-
-        hasTransformation = false;
-
-        if (!animating && animation == null) {
-            return false;
-        }
-
-        mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "AppWindowToken");
-
-        clearAnimation();
-        animating = false;
-        if (animLayerAdjustment != 0) {
-            animLayerAdjustment = 0;
-            updateLayers();
-        }
-        if (mService.mInputMethodTarget != null
-                && mService.mInputMethodTarget.mAppToken == mAppToken) {
-            mAppToken.getDisplayContent().computeImeTarget(true /* updateImeTarget */);
-        }
-
-        if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken
-                + ": reportedVisible=" + mAppToken.reportedVisible
-                + " okToDisplay=" + mAppToken.okToDisplay()
-                + " okToAnimate=" + mAppToken.okToAnimate()
-                + " startingDisplayed=" + mAppToken.startingDisplayed);
-
-        transformation.clear();
-
-        final int numAllAppWinAnimators = mAllAppWinAnimators.size();
-        for (int i = 0; i < numAllAppWinAnimators; i++) {
-            mAllAppWinAnimators.get(i).mWin.onExitAnimationDone();
-        }
-        mService.mAppTransition.notifyAppTransitionFinishedLocked(mAppToken.token);
-        return false;
-    }
-
-    // This must be called while inside a transaction.
-    boolean showAllWindowsLocked() {
-        boolean isAnimating = false;
-        final int NW = mAllAppWinAnimators.size();
-        for (int i=0; i<NW; i++) {
-            WindowStateAnimator winAnimator = mAllAppWinAnimators.get(i);
-            if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + winAnimator);
-            winAnimator.mWin.performShowLocked();
-            isAnimating |= winAnimator.isAnimationSet();
-        }
-        return isAnimating;
-    }
-
-    void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
-        pw.print(prefix); pw.print("mAnimator="); pw.println(mAnimator);
-        pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen);
-                pw.print(" allDrawn="); pw.print(allDrawn);
-                pw.print(" animLayerAdjustment="); pw.println(animLayerAdjustment);
-        if (lastFreezeDuration != 0) {
-            pw.print(prefix); pw.print("lastFreezeDuration=");
-                    TimeUtils.formatDuration(lastFreezeDuration, pw); pw.println();
-        }
-        if (animating || animation != null) {
-            pw.print(prefix); pw.print("animating="); pw.println(animating);
-            pw.print(prefix); pw.print("animation="); pw.println(animation);
-            pw.print(prefix); pw.print("mTransit="); pw.println(mTransit);
-            pw.print(prefix); pw.print("mTransitFlags="); pw.println(mTransitFlags);
-        }
-        if (hasTransformation) {
-            pw.print(prefix); pw.print("XForm: ");
-                    transformation.printShortString(pw);
-                    pw.println();
-        }
-        if (thumbnail != null) {
-            pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
-                    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());
-        }
-        for (int i=0; i<mAllAppWinAnimators.size(); i++) {
-            WindowStateAnimator wanim = mAllAppWinAnimators.get(i);
-            pw.print(prefix); pw.print("App Win Anim #"); pw.print(i);
-                    pw.print(": "); pw.println(wanim);
-        }
-    }
-
-    void startProlongAnimation(int prolongType) {
-        mProlongAnimation = prolongType;
-        mClearProlongedAnimation = false;
-    }
-
-    void endProlongedAnimation() {
-        mProlongAnimation = PROLONG_ANIMATION_DISABLED;
-    }
-
-    // This is an animation that does nothing: it just immediately finishes
-    // itself every time it is called.  It is used as a stub animation in cases
-    // where we want to synchronize multiple things that may be animating.
-    static final class DummyAnimation extends Animation {
-        @Override
-        public boolean getTransformation(long currentTime, Transformation outTransformation) {
-            return false;
-        }
-    }
-
-}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 00a0d3d..ae9f28b 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 
 import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
@@ -25,7 +24,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
@@ -34,16 +32,13 @@
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Trace;
 import android.util.Slog;
-import android.view.DisplayInfo;
 import android.view.IApplicationToken;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -339,7 +334,7 @@
 
             if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG_WM, "setAppVisibility("
                     + mToken + ", visible=" + visible + "): " + mService.mAppTransition
-                    + " hidden=" + wtoken.hidden + " hiddenRequested="
+                    + " hidden=" + wtoken.isHidden() + " hiddenRequested="
                     + wtoken.hiddenRequested + " Callers=" + Debug.getCallers(6));
 
             mService.mOpeningApps.remove(wtoken);
@@ -364,11 +359,11 @@
                 wtoken.startingMoved = false;
                 // If the token is currently hidden (should be the common case), or has been
                 // stopped, then we need to set up to wait for its windows to be ready.
-                if (wtoken.hidden || wtoken.mAppStopped) {
+                if (wtoken.isHidden() || wtoken.mAppStopped) {
                     wtoken.clearAllDrawn();
 
                     // If the app was already visible, don't reset the waitingToShow state.
-                    if (wtoken.hidden) {
+                    if (wtoken.isHidden()) {
                         wtoken.waitingToShow = true;
                     }
 
@@ -389,21 +384,6 @@
             // If we are preparing an app transition, then delay changing
             // the visibility of this token until we execute that transition.
             if (wtoken.okToAnimate() && mService.mAppTransition.isTransitionSet()) {
-                // A dummy animation is a placeholder animation which informs others that an
-                // animation is going on (in this case an application transition). If the animation
-                // was transferred from another application/animator, no dummy animator should be
-                // created since an animation is already in progress.
-                if (wtoken.mAppAnimator.usingTransferredAnimation
-                        && wtoken.mAppAnimator.animation == null) {
-                    Slog.wtf(TAG_WM, "Will NOT set dummy animation on: " + wtoken
-                            + ", using null transferred animation!");
-                }
-                if (!wtoken.mAppAnimator.usingTransferredAnimation &&
-                        (!wtoken.startingDisplayed || mService.mSkipAppTransitionAnimation)) {
-                    if (DEBUG_APP_TRANSITIONS) Slog.v(
-                            TAG_WM, "Setting dummy animation on: " + wtoken);
-                    wtoken.mAppAnimator.setDummyAnimation();
-                }
                 wtoken.inPendingTransaction = true;
                 if (visible) {
                     mService.mOpeningApps.add(wtoken);
@@ -423,7 +403,7 @@
                             if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "TRANSIT_TASK_OPEN_BEHIND, "
                                     + " adding " + focusedToken + " to mOpeningApps");
                             // Force animation to be loaded.
-                            focusedToken.hidden = true;
+                            focusedToken.setHidden(true);
                             mService.mOpeningApps.add(focusedToken);
                         }
                     }
@@ -710,7 +690,7 @@
                 return;
             }
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + mToken + ": hidden="
-                    + mContainer.hidden + " freezing=" + mContainer.mAppAnimator.freezingScreen);
+                    + mContainer.isHidden() + " freezing=" + mContainer.isFreezingScreen());
             mContainer.stopFreezingScreen(true, force);
         }
     }
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
new file mode 100644
index 0000000..487b52c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
+
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Binder;
+import android.util.Slog;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Builder;
+import android.view.SurfaceControl.Transaction;
+import android.view.animation.Animation;
+
+import com.android.server.wm.SurfaceAnimator.Animatable;
+
+/**
+ * Represents a surface that is displayed over an {@link AppWindowToken}
+ */
+class AppWindowThumbnail implements Animatable {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowThumbnail" : TAG_WM;
+
+    private final AppWindowToken mAppToken;
+    private final SurfaceControl mSurfaceControl;
+    private final SurfaceAnimator mSurfaceAnimator;
+    private final int mWidth;
+    private final int mHeight;
+
+    AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) {
+        mAppToken = appToken;
+        mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished,
+                appToken.mService.mAnimator::addAfterPrepareSurfacesRunnable, appToken.mService);
+        mWidth = thumbnailHeader.getWidth();
+        mHeight = thumbnailHeader.getHeight();
+
+        // Create a new surface for the thumbnail
+        WindowState window = appToken.findMainWindow();
+
+        // TODO: This should be attached as a child to the app token, once the thumbnail animations
+        // use relative coordinates. Once we start animating task we can also consider attaching
+        // this to the task.
+        mSurfaceControl = appToken.makeSurface()
+                .setName("thumbnail anim: " + appToken.toString())
+                .setSize(mWidth, mHeight)
+                .setFormat(PixelFormat.TRANSLUCENT)
+                .setMetadata(appToken.windowType,
+                        window != null ? window.mOwnerUid : Binder.getCallingUid())
+                .build();
+
+        if (SHOW_TRANSACTIONS) {
+            Slog.i(TAG, "  THUMBNAIL " + mSurfaceControl + ": CREATE");
+        }
+
+        // Transfer the thumbnail to the surface
+        Surface drawSurface = new Surface();
+        drawSurface.copyFrom(mSurfaceControl);
+        drawSurface.attachAndQueueBuffer(thumbnailHeader);
+        drawSurface.release();
+        t.show(mSurfaceControl);
+
+        // We parent the thumbnail to the task, and just place it on top of anything else in the
+        // task.
+        t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
+    }
+
+    void startAnimation(Transaction t, Animation anim) {
+        startAnimation(t, anim, null /* position */);
+    }
+
+    void startAnimation(Transaction t, Animation anim, Point position) {
+        anim.restrictDuration(MAX_ANIMATION_DURATION);
+        anim.scaleCurrentDuration(mAppToken.mService.getTransitionAnimationScaleLocked());
+        mSurfaceAnimator.startAnimation(t, new LocalAnimationAdapter(
+                new WindowAnimationSpec(anim, position,
+                        mAppToken.mService.mAppTransition.canSkipFirstFrame()),
+                mAppToken.mService.mSurfaceAnimationRunner), false /* hidden */);
+    }
+
+    private void onAnimationFinished() {
+    }
+
+    void setShowing(Transaction pendingTransaction, boolean show) {
+        // TODO: Not needed anymore once thumbnail is attached to the app.
+        if (show) {
+            pendingTransaction.show(mSurfaceControl);
+        } else {
+            pendingTransaction.hide(mSurfaceControl);
+        }
+    }
+
+    void destroy() {
+        mSurfaceAnimator.cancelAnimation();
+        mSurfaceControl.destroy();
+    }
+
+    @Override
+    public Transaction getPendingTransaction() {
+        return mAppToken.getPendingTransaction();
+    }
+
+    @Override
+    public void commitPendingTransaction() {
+        mAppToken.commitPendingTransaction();
+    }
+
+    @Override
+    public void destroyAfterPendingTransaction(SurfaceControl surface) {
+        mAppToken.destroyAfterPendingTransaction(surface);
+    }
+
+    @Override
+    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+        t.setLayer(leash, Integer.MAX_VALUE);
+    }
+
+    @Override
+    public void onAnimationLeashDestroyed(Transaction t) {
+
+        // TODO: Once attached to app token, we don't need to hide it immediately if thumbnail
+        // became visible.
+        t.hide(mSurfaceControl);
+    }
+
+    @Override
+    public Builder makeAnimationLeash() {
+        return mAppToken.makeSurface();
+    }
+
+    @Override
+    public SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
+    @Override
+    public SurfaceControl getAnimationLeashParent() {
+        return mAppToken.getAppAnimationLayer();
+    }
+
+    @Override
+    public SurfaceControl getParentSurfaceControl() {
+        return mAppToken.getParentSurfaceControl();
+    }
+
+    @Override
+    public int getSurfaceWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getSurfaceHeight() {
+        return mHeight;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 94a0cb7..fc0564d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -16,10 +16,13 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 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;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -27,6 +30,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
@@ -51,23 +55,27 @@
 import static com.android.server.wm.proto.AppWindowTokenProto.WINDOW_TOKEN;
 
 import android.annotation.CallSuper;
-import android.annotation.NonNull;
 import android.app.Activity;
 import android.content.res.Configuration;
+import android.graphics.GraphicBuffer;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.SystemClock;
+import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
 import android.view.IApplicationToken;
 import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
-import android.view.animation.Transformation;
+import android.view.animation.Animation;
 
+import com.android.internal.R;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.policy.WindowManagerPolicy.StartingSurface;
@@ -88,11 +96,14 @@
 class AppWindowToken extends WindowToken implements WindowManagerService.AppFreezeListener {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowToken" : TAG_WM;
 
+    /**
+     * Value to increment the z-layer when boosting a layer during animations. BOOST in l33tsp34k.
+     */
+    private static final int Z_BOOST_BASE = 800570000;
+
     // Non-null only for application tokens.
     final IApplicationToken appToken;
 
-    @NonNull final AppWindowAnimator mAppAnimator;
-
     final boolean mVoiceInteraction;
 
     /** @see WindowContainer#fillsParent() */
@@ -120,6 +131,8 @@
     private int mNumDrawnWindows;
     boolean inPendingTransaction;
     boolean allDrawn;
+    private boolean mLastAllDrawn;
+
     // Set to true when this app creates a surface while in the middle of an animation. In that
     // case do not clear allDrawn until the animation completes.
     boolean deferClearAllDrawn;
@@ -189,6 +202,32 @@
      */
     private boolean mCanTurnScreenOn = true;
 
+    /**
+     * If we are running an animation, this determines the transition type. Must be one of
+     * AppTransition.TRANSIT_* constants.
+     */
+    private int mTransit;
+
+    /**
+     * If we are running an animation, this determines the flags during this animation. Must be a
+     * bitwise combination of AppTransition.TRANSIT_FLAG_* constants.
+     */
+    private int mTransitFlags;
+
+    /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
+    private boolean mLastSurfaceShowing = true;
+
+    private AppWindowThumbnail mThumbnail;
+
+    /** Have we been asked to have this token keep the screen frozen? */
+    private boolean mFreezingScreen;
+
+    /** Whether this token should be boosted at the top of all app window tokens. */
+    private boolean mNeedsZBoost;
+
+    private final Point mTmpPoint = new Point();
+    private final Rect mTmpRect = new Rect();
+
     AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
             DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
             boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
@@ -206,7 +245,7 @@
         mRotationAnimationHint = rotationAnimationHint;
 
         // Application tokens start out hidden.
-        hidden = true;
+        setHidden(true);
         hiddenRequested = true;
     }
 
@@ -218,7 +257,6 @@
         mVoiceInteraction = voiceInteraction;
         mFillsParent = fillsParent;
         mInputApplicationHandle = new InputApplicationHandle(this);
-        mAppAnimator = new AppWindowAnimator(this, service);
     }
 
     void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
@@ -262,7 +300,7 @@
         boolean nowGone = mReportedVisibilityResults.nowGone;
 
         boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting;
-        boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !hidden;
+        boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !isHidden();
         if (!nowGone) {
             // If the app is not yet gone, then it can only become visible/drawn.
             if (!nowDrawn) {
@@ -325,19 +363,16 @@
         // transition animation
         // * or this is an opening app and windows are being replaced.
         boolean visibilityChanged = false;
-        if (hidden == visible || (hidden && mIsExiting) || (visible && waitingForReplacement())) {
+        if (isHidden() == visible || (isHidden() && mIsExiting) || (visible && waitingForReplacement())) {
             final AccessibilityController accessibilityController = mService.mAccessibilityController;
             boolean changed = false;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
-                    "Changing app " + this + " hidden=" + hidden + " performLayout=" + performLayout);
+                    "Changing app " + this + " hidden=" + isHidden() + " performLayout=" + performLayout);
 
             boolean runningAppAnimation = false;
 
-            if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
-                mAppAnimator.setNullAnimation();
-            }
             if (transit != AppTransition.TRANSIT_UNSET) {
-                if (mService.applyAnimationLocked(this, lp, transit, visible, isVoiceInteraction)) {
+                if (applyAnimationLocked(lp, transit, visible, isVoiceInteraction)) {
                     delayed = runningAppAnimation = true;
                 }
                 final WindowState window = findMainWindow();
@@ -355,7 +390,8 @@
                 changed |= win.onAppVisibilityChanged(visible, runningAppAnimation);
             }
 
-            hidden = hiddenRequested = !visible;
+            setHidden(!visible);
+            hiddenRequested = !visible;
             visibilityChanged = true;
             if (!visible) {
                 stopFreezingScreen(true, true);
@@ -373,7 +409,7 @@
             }
 
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "setVisibility: " + this
-                    + ": hidden=" + hidden + " hiddenRequested=" + hiddenRequested);
+                    + ": hidden=" + isHidden() + " hiddenRequested=" + hiddenRequested);
 
             if (changed) {
                 mService.mInputMonitor.setUpdateInputWindowsNeededLw();
@@ -386,7 +422,7 @@
             }
         }
 
-        if (mAppAnimator.animation != null) {
+        if (isReallyAnimating()) {
             delayed = true;
         }
 
@@ -414,7 +450,7 @@
             // no animation but there will still be a transition set.
             // We still need to delay hiding the surface such that it
             // can be synchronized with showing the next surface in the transition.
-            if (hidden && !delayed && !mService.mAppTransition.isTransitionSet()) {
+            if (isHidden() && !delayed && !mService.mAppTransition.isTransitionSet()) {
                 SurfaceControl.openTransaction();
                 for (int i = mChildren.size() - 1; i >= 0; i--) {
                     mChildren.get(i).mWinAnimator.hide("immediately hidden");
@@ -487,7 +523,7 @@
     boolean isVisible() {
         // If the app token isn't hidden then it is considered visible and there is no need to check
         // its children windows to see if they are visible.
-        return !hidden;
+        return !isHidden();
     }
 
     @Override
@@ -533,7 +569,7 @@
         }
 
         if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app " + this + " delayed=" + delayed
-                + " animation=" + mAppAnimator.animation + " animating=" + mAppAnimator.animating);
+                + " animation=" + getAnimation() + " animating=" + isSelfAnimating());
 
         if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: "
                 + this + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
@@ -545,7 +581,7 @@
         // If this window was animating, then we need to ensure that the app transition notifies
         // that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so
         // add to that list now
-        if (mAppAnimator.animating) {
+        if (isSelfAnimating()) {
             mService.mNoAnimationNotifyOnTransitionFinished.add(token);
         }
 
@@ -561,8 +597,7 @@
         } else {
             // Make sure there is no animation running on this token, so any windows associated
             // with it will be removed as soon as their animations are complete
-            mAppAnimator.clearAnimation();
-            mAppAnimator.animating = false;
+            cancelAnimation();
             if (stack != null) {
                 stack.mExitingAppTokens.remove(this);
             }
@@ -614,8 +649,7 @@
         boolean destroyedSomething = false;
 
         // Copying to a different list as multiple children can be removed.
-        // TODO: Not sure why this is needed.
-        final LinkedList<WindowState> children = new LinkedList<>(mChildren);
+        final ArrayList<WindowState> children = new ArrayList<>(mChildren);
         for (int i = children.size() - 1; i >= 0; i--) {
             final WindowState win = children.get(i);
             destroyedSomething |= win.destroySurface(cleanupOnResume, mAppStopped);
@@ -710,7 +744,7 @@
                 // We set the hidden state to false for the token from a transferred starting window.
                 // We now reset it back to true since the starting window was the last window in the
                 // token.
-                hidden = true;
+                setHidden(true);
             }
         } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) {
             // If this is the last window except for a starting transition window,
@@ -756,14 +790,6 @@
             final WindowState w = mChildren.get(i);
             w.setWillReplaceWindow(animate);
         }
-        if (animate) {
-            // Set-up dummy animation so we can start treating windows associated with this
-            // token like they are in transition before the new app window is ready for us to
-            // run the real transition animation.
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
-                    "setWillReplaceWindow() Setting dummy animation on: " + this);
-            mAppAnimator.setDummyAnimation();
-        }
     }
 
     void setWillReplaceChildWindows() {
@@ -1007,13 +1033,12 @@
 
     void startFreezingScreen() {
         if (DEBUG_ORIENTATION) logWithStack(TAG, "Set freezing of " + appToken + ": hidden="
-                + hidden + " freezing=" + mAppAnimator.freezingScreen + " hiddenRequested="
+                + isHidden() + " freezing=" + mFreezingScreen + " hiddenRequested="
                 + hiddenRequested);
         if (!hiddenRequested) {
-            if (!mAppAnimator.freezingScreen) {
-                mAppAnimator.freezingScreen = true;
+            if (!mFreezingScreen) {
+                mFreezingScreen = true;
                 mService.registerAppFreezeListener(this);
-                mAppAnimator.lastFreezeDuration = 0;
                 mService.mAppsFreezingScreen++;
                 if (mService.mAppsFreezingScreen == 1) {
                     mService.startFreezingDisplayLocked(false, 0, 0, getDisplayContent());
@@ -1030,7 +1055,7 @@
     }
 
     void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
-        if (!mAppAnimator.freezingScreen) {
+        if (!mFreezingScreen) {
             return;
         }
         if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + this + " force=" + force);
@@ -1042,10 +1067,8 @@
         }
         if (force || unfrozeWindows) {
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "No longer freezing: " + this);
-            mAppAnimator.freezingScreen = false;
+            mFreezingScreen = false;
             mService.unregisterAppFreezeListener(this);
-            mAppAnimator.lastFreezeDuration =
-                    (int)(SystemClock.elapsedRealtime() - mService.mDisplayFreezeTime);
             mService.mAppsFreezingScreen--;
             mService.mLastFinishedFreezeSource = this;
         }
@@ -1111,14 +1134,19 @@
                 if (fromToken.firstWindowDrawn) {
                     firstWindowDrawn = true;
                 }
-                if (!fromToken.hidden) {
-                    hidden = false;
+                if (!fromToken.isHidden()) {
+                    setHidden(false);
                     hiddenRequested = false;
                     mHiddenSetFromTransferredStartingWindow = true;
                 }
                 setClientHidden(fromToken.mClientHidden);
-                fromToken.mAppAnimator.transferCurrentAnimation(
-                        mAppAnimator, tStartingWindow.mWinAnimator);
+
+                transferAnimation(fromToken);
+
+                // When transferring an animation, we no longer need to apply an animation to the
+                // the token we transfer the animation over. Thus, remove the animation from
+                // pending opening apps.
+                mService.mOpeningApps.remove(this);
 
                 mService.updateFocusedWindowLocked(
                         UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
@@ -1142,17 +1170,8 @@
             return true;
         }
 
-        final AppWindowAnimator tAppAnimator = fromToken.mAppAnimator;
-        final AppWindowAnimator wAppAnimator = mAppAnimator;
-        if (tAppAnimator.thumbnail != null) {
-            // The old token is animating with a thumbnail, transfer that to the new token.
-            if (wAppAnimator.thumbnail != null) {
-                wAppAnimator.thumbnail.destroy();
-            }
-            wAppAnimator.thumbnail = tAppAnimator.thumbnail;
-            wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
-            tAppAnimator.thumbnail = null;
-        }
+        // TODO: Transfer thumbnail
+
         return false;
     }
 
@@ -1160,16 +1179,6 @@
         return mChildren.size() == 1 && mChildren.get(0) == win;
     }
 
-    void setAllAppWinAnimators() {
-        final ArrayList<WindowStateAnimator> allAppWinAnimators = mAppAnimator.mAllAppWinAnimators;
-        allAppWinAnimators.clear();
-
-        final int windowsCount = mChildren.size();
-        for (int j = 0; j < windowsCount; j++) {
-            (mChildren.get(j)).addWinAnimatorToList(allAppWinAnimators);
-        }
-    }
-
     @Override
     void onAppTransitionDone() {
         sendingToBottom = false;
@@ -1205,19 +1214,43 @@
     }
 
     @Override
-    void checkAppWindowsReadyToShow() {
-        if (allDrawn == mAppAnimator.allDrawn) {
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        final int prevWinMode = getWindowingMode();
+        super.onConfigurationChanged(newParentConfig);
+        final int winMode = getWindowingMode();
+
+        if (prevWinMode == winMode) {
             return;
         }
 
-        mAppAnimator.allDrawn = allDrawn;
+        if (prevWinMode != WINDOWING_MODE_UNDEFINED && winMode == WINDOWING_MODE_PINNED) {
+            // Entering PiP from fullscreen, reset the snap fraction
+            mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
+        } else if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED) {
+            // Leaving PiP to fullscreen, save the snap fraction based on the pre-animation bounds
+            // for the next re-entry into PiP (assuming the activity is not hidden or destroyed)
+            final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
+            if (pinnedStack != null) {
+                mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this,
+                        pinnedStack.mPreAnimationBounds);
+            }
+        }
+    }
+
+    @Override
+    void checkAppWindowsReadyToShow() {
+        if (allDrawn == mLastAllDrawn) {
+            return;
+        }
+
+        mLastAllDrawn = allDrawn;
         if (!allDrawn) {
             return;
         }
 
         // The token has now changed state to having all windows shown...  what to do, what to do?
-        if (mAppAnimator.freezingScreen) {
-            mAppAnimator.showAllWindowsLocked();
+        if (mFreezingScreen) {
+            showAllWindowsLocked();
             stopFreezingScreen(false, true);
             if (DEBUG_ORIENTATION) Slog.i(TAG,
                     "Setting mOrientationChangeComplete=true because wtoken " + this
@@ -1230,7 +1263,7 @@
 
             // We can now show all of the drawn windows!
             if (!mService.mOpeningApps.contains(this)) {
-                mService.mAnimator.orAnimating(mAppAnimator.showAllWindowsLocked());
+                showAllWindowsLocked();
             }
         }
     }
@@ -1300,10 +1333,10 @@
 
         if (DEBUG_STARTING_WINDOW_VERBOSE && w == startingWindow) {
             Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
-                    + " allDrawn=" + allDrawn + " freezingScreen=" + mAppAnimator.freezingScreen);
+                    + " allDrawn=" + allDrawn + " freezingScreen=" + mFreezingScreen);
         }
 
-        if (allDrawn && !mAppAnimator.freezingScreen) {
+        if (allDrawn && !mFreezingScreen) {
             return false;
         }
 
@@ -1320,13 +1353,13 @@
         if (!allDrawn && w.mightAffectAllDrawn()) {
             if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
                 Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
-                        + ", isAnimationSet=" + winAnimator.isAnimationSet());
+                        + ", isAnimationSet=" + isSelfAnimating());
                 if (!w.isDrawnLw()) {
                     Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
                             + " pv=" + w.mPolicyVisibility
                             + " mDrawState=" + winAnimator.drawStateToString()
                             + " ph=" + w.isParentWindowHidden() + " th=" + hiddenRequested
-                            + " a=" + winAnimator.isAnimationSet());
+                            + " a=" + isSelfAnimating());
                 }
             }
 
@@ -1338,7 +1371,7 @@
 
                         if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, "tokenMayBeDrawn: "
                                 + this + " w=" + w + " numInteresting=" + mNumInterestingWindows
-                                + " freezingScreen=" + mAppAnimator.freezingScreen
+                                + " freezingScreen=" + mFreezingScreen
                                 + " mAppFreezing=" + w.mAppFreezing);
 
                         isInterestingAndDrawn = true;
@@ -1356,21 +1389,6 @@
     }
 
     @Override
-    void stepAppWindowsAnimation(long currentTime) {
-        mAppAnimator.wasAnimating = mAppAnimator.animating;
-        if (mAppAnimator.stepAnimationLocked(currentTime)) {
-            mAppAnimator.animating = true;
-            mService.mAnimator.setAnimating(true);
-            mService.mAnimator.mAppWindowAnimating = true;
-        } else if (mAppAnimator.wasAnimating) {
-            // stopped animating, do one more pass through the layout
-            setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
-                    DEBUG_LAYOUT_REPEATS ? "appToken " + this + " done" : null);
-            if (DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating " + this);
-        }
-    }
-
-    @Override
     boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
         // For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
         // before the non-exiting app tokens. So, we skip the exiting app tokens here.
@@ -1521,18 +1539,300 @@
     }
 
     @Override
-    int getAnimLayerAdjustment() {
-        return mAppAnimator.animLayerAdjustment;
+    public SurfaceControl getAnimationLeashParent() {
+        return getAppAnimationLayer();
+    }
+
+    boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
+            boolean isVoiceInteraction) {
+
+        if (mService.mDisableTransitionAnimation) {
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+                Slog.v(TAG_WM, "applyAnimation: transition animation is disabled. atoken=" + this);
+            }
+            cancelAnimation();
+            return false;
+        }
+
+        // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
+        // to animate and it can cause strange artifacts when we unfreeze the display if some
+        // different animation is running.
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
+        if (okToAnimate()) {
+            final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
+            if (a != null) {
+                final TaskStack stack = getStack();
+                mTmpPoint.set(0, 0);
+                mTmpRect.setEmpty();
+                if (stack != null) {
+                    stack.getRelativePosition(mTmpPoint);
+                    stack.getBounds(mTmpRect);
+                    mTmpRect.offsetTo(0, 0);
+                }
+                final AnimationAdapter adapter = new LocalAnimationAdapter(
+                        new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
+                                mService.mAppTransition.canSkipFirstFrame(),
+                                mService.mAppTransition.getAppStackClipMode()),
+                        mService.mSurfaceAnimationRunner);
+                if (a.getZAdjustment() == Animation.ZORDER_TOP) {
+                    mNeedsZBoost = true;
+                }
+                startAnimation(getPendingTransaction(), adapter, !isVisible());
+                mTransit = transit;
+                mTransitFlags = mService.mAppTransition.getTransitFlags();
+            }
+        } else {
+            cancelAnimation();
+        }
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+        return isReallyAnimating();
+    }
+
+    private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+            boolean isVoiceInteraction) {
+        final DisplayContent displayContent = getTask().getDisplayContent();
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final int width = displayInfo.appWidth;
+        final int height = displayInfo.appHeight;
+        if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
+                "applyAnimation: atoken=" + this);
+
+        // Determine the visible rect to calculate the thumbnail clip
+        final WindowState win = findMainWindow();
+        final Rect frame = new Rect(0, 0, width, height);
+        final Rect displayFrame = new Rect(0, 0,
+                displayInfo.logicalWidth, displayInfo.logicalHeight);
+        final Rect insets = new Rect();
+        final Rect stableInsets = new Rect();
+        Rect surfaceInsets = null;
+        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
+            // won't exactly match the final freeform window frame (e.g. when overlapping with
+            // the status bar). In that case we need to use the final frame.
+            if (freeform) {
+                frame.set(win.mFrame);
+            } else {
+                frame.set(win.mContainingFrame);
+            }
+            surfaceInsets = win.getAttrs().surfaceInsets;
+            insets.set(win.mContentInsets);
+            stableInsets.set(win.mStableInsets);
+        }
+
+        if (mLaunchTaskBehind) {
+            // Differentiate the two animations. This one which is briefly on the screen
+            // gets the !enter animation, and the other activity which remains on the
+            // screen gets the enter animation. Both appear in the mOpeningApps set.
+            enter = false;
+        }
+        if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
+                + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+                + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
+        final Configuration displayConfig = displayContent.getConfiguration();
+        final Animation a = mService.mAppTransition.loadAnimation(lp, transit, enter,
+                displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
+                surfaceInsets, stableInsets, isVoiceInteraction, freeform, getTask().mTaskId);
+        if (a != null) {
+            if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
+            final int containingWidth = frame.width();
+            final int containingHeight = frame.height();
+            a.initialize(containingWidth, containingHeight, width, height);
+            a.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
+        }
+        return a;
+    }
+
+    @Override
+    protected void setLayer(Transaction t, int layer) {
+        if (!mSurfaceAnimator.hasLeash()) {
+            t.setLayer(mSurfaceControl, layer);
+        }
+    }
+
+    @Override
+    protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
+        if (!mSurfaceAnimator.hasLeash()) {
+            t.setRelativeLayer(mSurfaceControl, relativeTo, layer);
+        }
+    }
+
+    @Override
+    protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
+        if (!mSurfaceAnimator.hasLeash()) {
+            t.reparent(mSurfaceControl, newParent.getHandle());
+        }
+    }
+
+    @Override
+    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+
+        // The leash is parented to the animation layer. We need to preserve the z-order by using
+        // the prefix order index, but we boost if necessary.
+        int layer = getPrefixOrderIndex();
+        if (mNeedsZBoost) {
+            layer += Z_BOOST_BASE;
+        }
+        leash.setLayer(layer);
+    }
+
+    /**
+     * This must be called while inside a transaction.
+     */
+    void showAllWindowsLocked() {
+        forAllWindows(windowState -> {
+            if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + windowState);
+            windowState.performShowLocked();
+        }, false /* traverseTopToBottom */);
+    }
+
+    @Override
+    protected void onAnimationFinished() {
+        super.onAnimationFinished();
+
+        mTransit = TRANSIT_UNSET;
+        mTransitFlags = 0;
+        mNeedsZBoost = false;
+
+        setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,
+                "AppWindowToken");
+
+        clearThumbnail();
+
+        if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) {
+            getDisplayContent().computeImeTarget(true /* updateImeTarget */);
+        }
+
+        if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + this
+                + ": reportedVisible=" + reportedVisible
+                + " okToDisplay=" + okToDisplay()
+                + " okToAnimate=" + okToAnimate()
+                + " startingDisplayed=" + startingDisplayed);
+
+        // WindowState.onExitAnimationDone might modify the children list, so make a copy and then
+        // traverse the copy.
+        final ArrayList<WindowState> children = new ArrayList<>(mChildren);
+        children.forEach(WindowState::onExitAnimationDone);
+
+        mService.mAppTransition.notifyAppTransitionFinishedLocked(token);
+        scheduleAnimation();
+    }
+
+    @Override
+    boolean isAppAnimating() {
+        return isSelfAnimating();
     }
 
     @Override
     boolean isSelfAnimating() {
-        return mAppAnimator.isAnimating();
+        // If we are about to start a transition, we also need to be considered animating.
+        return isWaitingForTransitionStart() || isReallyAnimating();
+    }
+
+    /**
+     * @return True if and only if we are actually running an animation. Note that
+     *         {@link #isSelfAnimating} also returns true if we are waiting for an animation to
+     *         start.
+     */
+    private boolean isReallyAnimating() {
+        return super.isSelfAnimating();
     }
 
     @Override
-    void dump(PrintWriter pw, String prefix) {
-        super.dump(pw, prefix);
+    void cancelAnimation() {
+        super.cancelAnimation();
+        clearThumbnail();
+    }
+
+    boolean isWaitingForTransitionStart() {
+        return mService.mAppTransition.isTransitionSet()
+                && (mService.mOpeningApps.contains(this) || mService.mClosingApps.contains(this));
+    }
+
+    public int getTransit() {
+        return mTransit;
+    }
+
+    int getTransitFlags() {
+        return mTransitFlags;
+    }
+
+    void attachThumbnailAnimation() {
+        if (!isReallyAnimating()) {
+            return;
+        }
+        final int taskId = getTask().mTaskId;
+        final GraphicBuffer thumbnailHeader =
+                mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
+        if (thumbnailHeader == null) {
+            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
+            return;
+        }
+        clearThumbnail();
+        mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnailHeader);
+        mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader));
+    }
+
+    /**
+     * Attaches a surface with a thumbnail for the
+     * {@link android.app.ActivityOptions#ANIM_OPEN_CROSS_PROFILE_APPS} animation.
+     */
+    void attachCrossProfileAppsThumbnailAnimation() {
+        if (!isReallyAnimating()) {
+            return;
+        }
+        clearThumbnail();
+
+        final WindowState win = findMainWindow();
+        if (win == null) {
+            return;
+        }
+        final Rect frame = win.mFrame;
+        final int thumbnailDrawableRes = getTask().mUserId == mService.mCurrentUserId
+                ? R.drawable.ic_account_circle
+                : R.drawable.ic_corp_badge_no_background;
+        final GraphicBuffer thumbnail =
+                mService.mAppTransition
+                        .createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
+        if (thumbnail == null) {
+            return;
+        }
+        mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnail);
+        final Animation animation =
+                mService.mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(win.mFrame);
+        mThumbnail.startAnimation(getPendingTransaction(), animation, new Point(frame.left,
+                frame.top));
+    }
+
+    private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) {
+        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+
+        // If this is a multi-window scenario, we use the windows frame as
+        // destination of the thumbnail header animation. If this is a full screen
+        // window scenario, we use the whole display as the target.
+        WindowState win = findMainWindow();
+        Rect appRect = win != null ? win.getContentFrameLw() :
+                new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
+        Rect insets = win != null ? win.mContentInsets : null;
+        final Configuration displayConfig = mDisplayContent.getConfiguration();
+        return mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(
+                appRect, insets, thumbnailHeader, getTask().mTaskId, displayConfig.uiMode,
+                displayConfig.orientation);
+    }
+
+    private void clearThumbnail() {
+        if (mThumbnail == null) {
+            return;
+        }
+        mThumbnail.destroy();
+        mThumbnail = null;
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         if (appToken != null) {
             pw.println(prefix + "app=true mVoiceInteraction=" + mVoiceInteraction);
         }
@@ -1549,13 +1849,13 @@
             pw.print(prefix); pw.print("mAppStopped="); pw.println(mAppStopped);
         }
         if (mNumInterestingWindows != 0 || mNumDrawnWindows != 0
-                || allDrawn || mAppAnimator.allDrawn) {
+                || allDrawn || mLastAllDrawn) {
             pw.print(prefix); pw.print("mNumInterestingWindows=");
                     pw.print(mNumInterestingWindows);
                     pw.print(" mNumDrawnWindows="); pw.print(mNumDrawnWindows);
                     pw.print(" inPendingTransaction="); pw.print(inPendingTransaction);
                     pw.print(" allDrawn="); pw.print(allDrawn);
-                    pw.print(" (animator="); pw.print(mAppAnimator.allDrawn);
+                    pw.print(" lastAllDrawn="); pw.print(mLastAllDrawn);
                     pw.println(")");
         }
         if (inPendingTransaction) {
@@ -1590,9 +1890,44 @@
         if (mRemovingFromDisplay) {
             pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay);
         }
-        if (mAppAnimator.isAnimating()) {
-            mAppAnimator.dump(pw, prefix + "  ");
+    }
+
+    @Override
+    void setHidden(boolean hidden) {
+        super.setHidden(hidden);
+
+        if (hidden) {
+            // Once the app window is hidden, reset the last saved PiP snap fraction
+            mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
         }
+        scheduleAnimation();
+    }
+
+    @Override
+    void prepareSurfaces() {
+        // isSelfAnimating also returns true when we are about to start a transition, so we need
+        // to check super here.
+        final boolean reallyAnimating = super.isSelfAnimating();
+        final boolean show = !isHidden() || reallyAnimating;
+        if (show && !mLastSurfaceShowing) {
+            mPendingTransaction.show(mSurfaceControl);
+        } else if (!show && mLastSurfaceShowing) {
+            mPendingTransaction.hide(mSurfaceControl);
+        }
+        if (mThumbnail != null) {
+            mThumbnail.setShowing(mPendingTransaction, show);
+        }
+        mLastSurfaceShowing = show;
+        super.prepareSurfaces();
+    }
+
+    boolean isFreezingScreen() {
+        return mFreezingScreen;
+    }
+
+    @Override
+    boolean needsZBoost() {
+        return mNeedsZBoost || super.needsZBoost();
     }
 
     @CallSuper
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f458457..2cc2a0e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -69,7 +69,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -91,8 +90,6 @@
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
 import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
 import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
@@ -123,7 +120,6 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -191,9 +187,10 @@
             new NonAppWindowContainers("mBelowAppWindowsContainers", mService);
     // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
     // on the IME target. We mainly have this container grouping so we can keep track of all the IME
-    // window containers together and move them in-sync if/when needed.
-    private final NonAppWindowContainers mImeWindowsContainers =
-            new NonAppWindowContainers("mImeWindowsContainers", mService);
+    // window containers together and move them in-sync if/when needed. We use a subclass of
+    // WindowContainer which is omitted from screen magnification, as the IME is never magnified.
+    private final NonMagnifiableWindowContainers mImeWindowsContainers =
+            new NonMagnifiableWindowContainers("mImeWindowsContainers", mService);
 
     private WindowState mTmpWindow;
     private WindowState mTmpWindow2;
@@ -338,9 +335,6 @@
             new TaskForResizePointSearchResult();
     private final ApplySurfaceChangesTransactionState mTmpApplySurfaceChangesTransactionState =
             new ApplySurfaceChangesTransactionState();
-    private final ScreenshotApplicationState mScreenshotApplicationState =
-            new ScreenshotApplicationState();
-    private final Transaction mTmpTransaction = new Transaction();
 
     // True if this display is in the process of being removed. Used to determine if the removal of
     // the display's direct children should be allowed.
@@ -350,7 +344,6 @@
     private boolean mDisplayReady = false;
 
     WallpaperController mWallpaperController;
-    int mInputMethodAnimLayerAdjustment;
 
     private final SurfaceSession mSession = new SurfaceSession();
 
@@ -398,14 +391,6 @@
                 }
             }
         }
-        final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
-        if (appAnimator != null && appAnimator.thumbnail != null) {
-            if (appAnimator.thumbnailTransactionSeq
-                    != mTmpWindowAnimator.mAnimTransactionSequence) {
-                appAnimator.thumbnailTransactionSeq =
-                        mTmpWindowAnimator.mAnimTransactionSequence;
-            }
-        }
     };
 
     private final Consumer<WindowState> mUpdateWallpaperForAnimator = w -> {
@@ -436,15 +421,14 @@
 
         // If this window's app token is running a detached wallpaper animation, make a note so
         // we can ensure the wallpaper is displayed behind it.
-        final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
-        if (appAnimator != null && appAnimator.animation != null
-                && appAnimator.animating) {
-            if ((flags & FLAG_SHOW_WALLPAPER) != 0
-                    && appAnimator.animation.getDetachWallpaper()) {
+        final AppWindowToken atoken = winAnimator.mWin.mAppToken;
+        final AnimationAdapter animation = atoken != null ? atoken.getAnimation() : null;
+        if (animation != null) {
+            if ((flags & FLAG_SHOW_WALLPAPER) != 0 && animation.getDetachWallpaper()) {
                 mTmpWindow = w;
             }
 
-            final int color = appAnimator.animation.getBackgroundColor();
+            final int color = animation.getBackgroundColor();
             if (color != 0) {
                 final TaskStack stack = w.getStack();
                 if (stack != null) {
@@ -527,11 +511,11 @@
                     + " screen changed=" + w.isConfigChanged());
             final AppWindowToken atoken = w.mAppToken;
             if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
-                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
                     + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
                     + " parentHidden=" + w.isParentWindowHidden());
             else Slog.v(TAG, "  VIS: mViewVisibility=" + w.mViewVisibility
-                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
                     + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
                     + " parentHidden=" + w.isParentWindowHidden());
         }
@@ -667,10 +651,7 @@
             mWallpaperController.updateWallpaperVisibility();
         }
 
-        // Use mTmpTransaction instead of mPendingTransaction because we don't want to commit
-        // other changes in mPendingTransaction at this point.
-        w.handleWindowMovedIfNeeded(mTmpTransaction);
-        SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
+        w.handleWindowMovedIfNeeded(mPendingTransaction);
 
         final WindowStateAnimator winAnimator = w.mWinAnimator;
 
@@ -705,29 +686,6 @@
                     }
                 }
             }
-            final TaskStack stack = w.getStack();
-            if ((!winAnimator.isWaitingForOpening())
-                    || (stack != null && stack.isAnimatingBounds())) {
-                // Updates the shown frame before we set up the surface. This is needed
-                // because the resizing could change the top-left position (in addition to
-                // size) of the window. setSurfaceBoundariesLocked uses mShownPosition to
-                // position the surface.
-                //
-                // If an animation is being started, we can't call this method because the
-                // animation hasn't processed its initial transformation yet, but in general
-                // we do want to update the position if the window is animating. We make an exception
-                // for the bounds animating state, where an application may have been waiting
-                // for an exit animation to start, but instead enters PiP. We need to ensure
-                // we always recompute the top-left in this case.
-                winAnimator.computeShownFrameLocked();
-            }
-            winAnimator.setSurfaceBoundariesLocked(mTmpRecoveringMemory /* recoveringMemory */);
-
-            // Since setSurfaceBoundariesLocked applies the clipping, we need to apply the position
-            // to the surface of the window container as well. Use mTmpTransaction instead of
-            // mPendingTransaction to avoid committing any existing changes in there.
-            w.updateSurfacePosition(mTmpTransaction);
-            SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
         }
 
         final AppWindowToken atoken = w.mAppToken;
@@ -1933,6 +1891,8 @@
                     mService.unregisterPointerEventListener(mService.mMousePositionTracker);
                 }
             }
+            mService.mAnimator.removeDisplayLocked(mDisplayId);
+
             // The pending transaction won't be applied so we should
             // just clean up any surfaces pending destruction.
             onPendingTransactionApplied();
@@ -2056,12 +2016,6 @@
         mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight);
     }
 
-    void setInputMethodAnimLayerAdjustment(int adj) {
-        if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj);
-        mInputMethodAnimLayerAdjustment = adj;
-        assignWindowLayers(false /* relayoutNeeded */);
-    }
-
     /**
      * If a window that has an animation specifying a colored background and the current wallpaper
      * is visible, then the color goes *below* the wallpaper so we don't cause the wallpaper to
@@ -2168,7 +2122,9 @@
         proto.end(token);
     }
 
-    public void dump(String prefix, PrintWriter pw) {
+    @Override
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
         final String subPrefix = "  " + prefix;
         pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
@@ -2202,7 +2158,7 @@
         pw.println(prefix + "Application tokens in top down Z order:");
         for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
             final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
-            stack.dump(prefix + "  ", pw);
+            stack.dump(pw, prefix + "  ", dumpAll);
         }
 
         pw.println();
@@ -2214,7 +2170,7 @@
                 pw.print("  Exiting #"); pw.print(i);
                 pw.print(' '); pw.print(token);
                 pw.println(':');
-                token.dump(pw, "    ");
+                token.dump(pw, "    ", dumpAll);
             }
         }
 
@@ -2239,11 +2195,6 @@
         pw.println();
         mPinnedStackControllerLocked.dump(prefix, pw);
 
-        if (mInputMethodAnimLayerAdjustment != 0) {
-            pw.println(subPrefix
-                    + "mInputMethodAnimLayerAdjustment=" + mInputMethodAnimLayerAdjustment);
-        }
-
         pw.println();
         mDisplayFrames.dump(prefix, pw);
     }
@@ -2403,7 +2354,7 @@
             if (updateImeTarget) {
                 if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from "
                         + mService.mInputMethodTarget + " to null since mInputMethodWindow is null");
-                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0);
+                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
             }
             return null;
         }
@@ -2452,7 +2403,7 @@
                 if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
                         + " to null." + (SHOW_STACK_CRAWLS ? " Callers="
                         + Debug.getCallers(4) : ""));
-                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0);
+                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
             }
 
             return null;
@@ -2466,7 +2417,7 @@
                 // to look at all windows below the current target that are in this app, finding the
                 // highest visible one in layering.
                 WindowState highestTarget = null;
-                if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) {
+                if (token.isSelfAnimating()) {
                     highestTarget = token.getHighestAnimLayerWindow(curTarget);
                 }
 
@@ -2480,14 +2431,14 @@
                     if (appTransition.isTransitionSet()) {
                         // If we are currently setting up for an animation, hold everything until we
                         // can find out what will happen.
-                        setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+                        setInputMethodTarget(highestTarget, true);
                         return highestTarget;
                     } else if (highestTarget.mWinAnimator.isAnimationSet() &&
                             highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) {
                         // If the window we are currently targeting is involved with an animation,
                         // and it is on top of the next target we will be over, then hold off on
                         // moving until that is done.
-                        setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+                        setInputMethodTarget(highestTarget, true);
                         return highestTarget;
                     }
                 }
@@ -2495,23 +2446,20 @@
 
             if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
                     + target + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
-            setInputMethodTarget(target, false, target.mAppToken != null
-                    ? target.mAppToken.getAnimLayerAdjustment() : 0);
+            setInputMethodTarget(target, false);
         }
 
         return target;
     }
 
-    private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim, int layerAdj) {
+    private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
         if (target == mService.mInputMethodTarget
-                && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim
-                && mInputMethodAnimLayerAdjustment == layerAdj) {
+                && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim) {
             return;
         }
 
         mService.mInputMethodTarget = target;
         mService.mInputMethodTargetWaitingAnim = targetWaitingAnim;
-        setInputMethodAnimLayerAdjustment(layerAdj);
         assignWindowLayers(false /* setLayoutNeeded */);
     }
 
@@ -2573,7 +2521,7 @@
             pw.print(token);
             if (dumpAll) {
                 pw.println(':');
-                token.dump(pw, "    ");
+                token.dump(pw, "    ", dumpAll);
             } else {
                 pw.println();
             }
@@ -2840,6 +2788,7 @@
 
         mTmpRecoveringMemory = recoveringMemory;
         forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
+        prepareSurfaces();
 
         mService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
                 mTmpApplySurfaceChangesTransactionState.displayHasContent,
@@ -3197,7 +3146,7 @@
         /**
          * A control placed at the appropriate level for transitions to occur.
          */
-        SurfaceControl mAnimationLayer = null;
+        SurfaceControl mAppAnimationLayer = null;
 
         // 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.
@@ -3462,8 +3411,7 @@
                         // Make sure there is no animation running on this token, so any windows
                         // associated with it will be removed as soon as their animations are
                         // complete.
-                        token.mAppAnimator.clearAnimation();
-                        token.mAppAnimator.animating = false;
+                        cancelAnimation();
                         if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
                                 "performLayout: App token exiting now removed" + token);
                         token.removeIfPossible();
@@ -3544,19 +3492,28 @@
             // The appropriate place for App-Transitions to occur is right
             // above all other animations but still below things in the Picture-and-Picture
             // windowing mode.
-            if (mAnimationLayer != null) {
-                t.setLayer(mAnimationLayer, layer++);
+            if (mAppAnimationLayer != null) {
+                t.setLayer(mAppAnimationLayer, layer++);
             }
         }
 
         @Override
+        SurfaceControl getAppAnimationLayer() {
+            return mAppAnimationLayer;
+        }
+
+        @Override
         void onParentSet() {
             super.onParentSet();
             if (getParent() != null) {
-                mAnimationLayer = makeSurface().build();
+                mAppAnimationLayer = makeChildSurface(null)
+                        .setName("animationLayer")
+                        .build();
+                getPendingTransaction().show(mAppAnimationLayer);
+                scheduleAnimation();
             } else {
-                mAnimationLayer.destroy();
-                mAnimationLayer = null;
+                mAppAnimationLayer.destroy();
+                mAppAnimationLayer = null;
             }
         }
     }
@@ -3663,6 +3620,16 @@
         }
     }
 
+    private class NonMagnifiableWindowContainers extends NonAppWindowContainers {
+        NonMagnifiableWindowContainers(String name, WindowManagerService service) {
+            super(name, service);
+        }
+
+        @Override
+        void applyMagnificationSpec(Transaction t, MagnificationSpec spec) {
+        }
+    };
+
     SurfaceControl.Builder makeSurface(SurfaceSession s) {
         return mService.makeSurfaceBuilder(s)
                 .setParent(mWindowingLayer);
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 5fe4565..2173fa3 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -16,10 +16,9 @@
 
 package com.android.server.wm;
 
-import android.graphics.Point;
+import android.os.SystemClock;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
-import android.view.animation.Animation;
 
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
@@ -30,6 +29,7 @@
 class LocalAnimationAdapter implements AnimationAdapter {
 
     private final AnimationSpec mSpec;
+
     private final SurfaceAnimationRunner mAnimator;
 
     LocalAnimationAdapter(AnimationSpec spec, SurfaceAnimationRunner animator) {
@@ -64,6 +64,11 @@
         return mSpec.getDuration();
     }
 
+    @Override
+    public long getStatusBarTransitionsStartTime() {
+        return mSpec.calculateStatusBarTransitionStartTime();
+    }
+
     /**
      * Describes how to apply an animation.
      */
@@ -84,6 +89,13 @@
         }
 
         /**
+         * @see AnimationAdapter#getStatusBarTransitionsStartTime
+         */
+        default long calculateStatusBarTransitionStartTime() {
+            return SystemClock.uptimeMillis();
+        }
+
+        /**
          * @return The duration of the animation.
          */
         long getDuration();
@@ -91,10 +103,17 @@
         /**
          * Called when the spec needs to apply the current animation state to the leash.
          *
-         * @param t The transaction to use to apply a transform.
-         * @param leash The leash to apply the state to.
+         * @param t               The transaction to use to apply a transform.
+         * @param leash           The leash to apply the state to.
          * @param currentPlayTime The current time of the animation.
          */
         void apply(Transaction t, SurfaceControl leash, long currentPlayTime);
+
+        /**
+         * @see AppTransition#canSkipFirstFrame
+         */
+        default boolean canSkipFirstFrame() {
+            return false;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index d8726bf..69cbe46 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -48,6 +48,7 @@
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -71,6 +72,7 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
 
+    public static final float INVALID_SNAP_FRACTION = -1f;
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final Handler mHandler = UiThread.getHandler();
@@ -101,6 +103,8 @@
     private float mDefaultAspectRatio;
     private Point mScreenEdgeInsets;
     private int mCurrentMinSize;
+    private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
+    private WeakReference<AppWindowToken> mLastPipActivity = null;
 
     // The aspect ratio bounds of the PIP.
     private float mMinAspectRatio;
@@ -113,6 +117,7 @@
     private final Rect mTmpAnimatingBoundsRect = new Rect();
     private final Point mTmpDisplaySize = new Point();
 
+
     /**
      * The callback object passed to listeners for them to notify the controller of state changes.
      */
@@ -250,9 +255,35 @@
     }
 
     /**
+     * Saves the current snap fraction for re-entry of the current activity into PiP.
+     */
+    void saveReentrySnapFraction(final AppWindowToken token, final Rect stackBounds) {
+        mReentrySnapFraction = getSnapFraction(stackBounds);
+        mLastPipActivity = new WeakReference<>(token);
+    }
+
+    /**
+     * Resets the last saved snap fraction so that the default bounds will be returned.
+     */
+    void resetReentrySnapFraction(AppWindowToken token) {
+        if (mLastPipActivity != null && mLastPipActivity.get() == token) {
+            mReentrySnapFraction = INVALID_SNAP_FRACTION;
+            mLastPipActivity = null;
+        }
+    }
+
+    /**
      * @return the default bounds to show the PIP when there is no active PIP.
      */
-    Rect getDefaultBounds() {
+    Rect getDefaultOrLastSavedBounds() {
+        return getDefaultBounds(mReentrySnapFraction);
+    }
+
+    /**
+     * @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it
+     * will apply the default bounds to the provided snap fraction.
+     */
+    Rect getDefaultBounds(float snapFraction) {
         synchronized (mService.mWindowMap) {
             final Rect insetBounds = new Rect();
             getInsetBounds(insetBounds);
@@ -260,8 +291,14 @@
             final Rect defaultBounds = new Rect();
             final Size size = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio,
                     mDefaultMinSize, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
-            Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds,
-                    0, mIsImeShowing ? mImeHeight : 0, defaultBounds);
+            if (snapFraction != INVALID_SNAP_FRACTION) {
+                defaultBounds.set(0, 0, size.getWidth(), size.getHeight());
+                final Rect movementBounds = getMovementBounds(defaultBounds);
+                mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction);
+            } else {
+                Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds,
+                        0, mIsImeShowing ? mImeHeight : 0, defaultBounds);
+            }
             return defaultBounds;
         }
     }
@@ -299,9 +336,7 @@
             final Rect postChangeStackBounds = mTmpRect;
 
             // Calculate the snap fraction of the current stack along the old movement bounds
-            final Rect preChangeMovementBounds = getMovementBounds(postChangeStackBounds);
-            final float snapFraction = mSnapAlgorithm.getSnapFraction(postChangeStackBounds,
-                    preChangeMovementBounds);
+            final float snapFraction = getSnapFraction(postChangeStackBounds);
             mDisplayInfo.copyFrom(displayInfo);
 
             // Calculate the stack bounds in the new orientation to the same same fraction along the
@@ -414,7 +449,7 @@
             try {
                 final Rect insetBounds = new Rect();
                 getInsetBounds(insetBounds);
-                final Rect normalBounds = getDefaultBounds();
+                final Rect normalBounds = getDefaultBounds(INVALID_SNAP_FRACTION);
                 if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
                     transformBoundsToAspectRatio(normalBounds, mAspectRatio,
                             false /* useCurrentMinEdgeSize */);
@@ -486,6 +521,14 @@
     }
 
     /**
+     * @return the default snap fraction to apply instead of the default gravity when calculating
+     *         the default stack bounds when first entering PiP.
+     */
+    private float getSnapFraction(Rect stackBounds) {
+        return mSnapAlgorithm.getSnapFraction(stackBounds, getMovementBounds(stackBounds));
+    }
+
+    /**
      * @return the pixels for a given dp value.
      */
     private int dpToPx(float dpValue, DisplayMetrics dm) {
@@ -494,7 +537,8 @@
 
     void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "PinnedStackController");
-        pw.print(prefix + "  defaultBounds="); getDefaultBounds().printShortString(pw);
+        pw.print(prefix + "  defaultBounds=");
+        getDefaultBounds(INVALID_SNAP_FRACTION).printShortString(pw);
         pw.println();
         mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
         pw.print(prefix + "  movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
@@ -516,7 +560,7 @@
 
     void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        getDefaultBounds().writeToProto(proto, DEFAULT_BOUNDS);
+        getDefaultBounds(INVALID_SNAP_FRACTION).writeToProto(proto, DEFAULT_BOUNDS);
         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 b021a72..02fbfba 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -61,7 +61,7 @@
                     displayContent.getPinnedStackController();
             if (stackBounds == null) {
                 // Calculate the aspect ratio bounds from the default bounds
-                stackBounds = pinnedStackController.getDefaultBounds();
+                stackBounds = pinnedStackController.getDefaultOrLastSavedBounds();
             }
 
             if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) {
@@ -173,7 +173,7 @@
      * from fullscreen to non-fullscreen bounds.
      */
     public boolean deferScheduleMultiWindowModeChanged() {
-        synchronized(mWindowMap) {
+        synchronized (mWindowMap) {
             return mContainer.deferScheduleMultiWindowModeChanged();
         }
     }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e653e7d..2a77c92 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -612,7 +612,7 @@
                         defaultDisplay.pendingLayoutChanges);
         }
 
-        if (!mService.mAnimator.mAppWindowAnimating && mService.mAppTransition.isRunning()) {
+        if (!isAppAnimating() && mService.mAppTransition.isRunning()) {
             // We have finished the animation of an app transition. To do this, we have delayed a
             // lot of operations like showing and hiding apps, moving apps in Z-order, etc. The app
             // token list reflects the correct Z-order, but the window list may now be out of sync
@@ -1035,7 +1035,7 @@
             final int count = mChildren.size();
             for (int i = 0; i < count; ++i) {
                 final DisplayContent displayContent = mChildren.get(i);
-                displayContent.dump("  ", pw);
+                displayContent.dump(pw, "  ", true /* dumpAll */);
             }
         } else {
             pw.println("  NO DISPLAY");
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 3ef9b3f..dc62cc8 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.util.TimeUtils.NANOS_PER_MS;
 import static android.view.Choreographer.CALLBACK_TRAVERSAL;
 import static android.view.Choreographer.getSfInstance;
 
@@ -66,6 +67,9 @@
     @VisibleForTesting
     final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    private boolean mAnimationStartDeferred;
+
     SurfaceAnimationRunner() {
         this(null /* callbackProvider */, null /* animatorFactory */, new Transaction());
     }
@@ -85,13 +89,41 @@
                 : SfValueAnimator::new;
     }
 
+    /**
+     * Defers starting of animations until {@link #continueStartingAnimations} is called. This
+     * method is NOT nestable.
+     *
+     * @see #continueStartingAnimations
+     */
+    void deferStartingAnimations() {
+        synchronized (mLock) {
+            mAnimationStartDeferred = true;
+        }
+    }
+
+    /**
+     * Continues starting of animations.
+     *
+     * @see #deferStartingAnimations
+     */
+    void continueStartingAnimations() {
+        synchronized (mLock) {
+            mAnimationStartDeferred = false;
+            if (!mPendingAnimations.isEmpty()) {
+                mChoreographer.postFrameCallback(this::startAnimations);
+            }
+        }
+    }
+
     void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
             Runnable finishCallback) {
         synchronized (mLock) {
             final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
                     finishCallback);
             mPendingAnimations.put(animationLeash, runningAnim);
-            mChoreographer.postFrameCallback(this::stepAnimation);
+            if (!mAnimationStartDeferred) {
+                mChoreographer.postFrameCallback(this::startAnimations);
+            }
 
             // Some animations (e.g. move animations) require the initial transform to be applied
             // immediately.
@@ -142,6 +174,7 @@
             // Transaction will be applied in the commit phase.
             scheduleApplyTransaction();
         });
+
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -158,6 +191,7 @@
                     mRunningAnimations.remove(a.mLeash);
                     synchronized (mCancelLock) {
                         if (!a.mCancelled) {
+
                             // Post on other thread that we can push final state without jank.
                             AnimationThread.getHandler().post(a.mFinishCallback);
                         }
@@ -166,6 +200,14 @@
             }
         });
         anim.start();
+        if (a.mAnimSpec.canSkipFirstFrame()) {
+            // If we can skip the first frame, we start one frame later.
+            anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
+        }
+
+        // Immediately start the animation by manually applying an animation frame. Otherwise, the
+        // start time would only be set in the next frame, leading to a delay.
+        anim.doAnimationFrame(mChoreographer.getFrameTime());
         a.mAnim = anim;
         mRunningAnimations.put(a.mLeash, a);
     }
@@ -174,7 +216,7 @@
         a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
     }
 
-    private void stepAnimation(long frameTimeNanos) {
+    private void startAnimations(long frameTimeNanos) {
         synchronized (mLock) {
             startPendingAnimationsLocked();
         }
@@ -189,6 +231,7 @@
     }
 
     private void applyTransaction() {
+        mFrameTransaction.setAnimationTransaction();
         mFrameTransaction.apply();
         mApplyScheduled = false;
     }
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index e165211..e67cdba 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -22,11 +22,15 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
+import java.util.function.Consumer;
 
 /**
  * A class that can run animations on objects that have a set of child surfaces. We do this by
@@ -41,7 +45,9 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
     private final WindowManagerService mService;
     private AnimationAdapter mAnimation;
-    private SurfaceControl mLeash;
+
+    @VisibleForTesting
+    SurfaceControl mLeash;
     private final Animatable mAnimatable;
     private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
     private final Runnable mAnimationFinishedCallback;
@@ -50,35 +56,50 @@
     /**
      * @param animatable The object to animate.
      * @param animationFinishedCallback Callback to invoke when an animation has finished running.
+     * @param addAfterPrepareSurfaces Consumer that takes a runnable and executes it after preparing
+     *                                surfaces in WM. Can be implemented differently during testing.
      */
     SurfaceAnimator(Animatable animatable, Runnable animationFinishedCallback,
-            WindowManagerService service) {
+            Consumer<Runnable> addAfterPrepareSurfaces, WindowManagerService service) {
         mAnimatable = animatable;
         mService = service;
         mAnimationFinishedCallback = animationFinishedCallback;
-        mInnerAnimationFinishedCallback = getFinishedCallback(animationFinishedCallback);
+        mInnerAnimationFinishedCallback = getFinishedCallback(animationFinishedCallback,
+                addAfterPrepareSurfaces);
     }
 
-    private OnAnimationFinishedCallback getFinishedCallback(Runnable animationFinishedCallback) {
+    private OnAnimationFinishedCallback getFinishedCallback(Runnable animationFinishedCallback,
+            Consumer<Runnable> addAfterPrepareSurfaces) {
         return anim -> {
             synchronized (mService.mWindowMap) {
-                if (anim != mAnimation) {
-                    // Callback was from another animation - ignore.
+                final SurfaceAnimator target = mService.mAnimationTransferMap.remove(anim);
+                if (target != null) {
+                    target.mInnerAnimationFinishedCallback.onAnimationFinished(anim);
                     return;
                 }
 
-                final Transaction t = new Transaction();
-                SurfaceControl.openTransaction();
-                try {
-                    reset(t);
-                    animationFinishedCallback.run();
-                } finally {
-                    // TODO: This should use pendingTransaction eventually, but right now things
-                    // happening on the animation finished callback are happening on the global
-                    // transaction.
-                    SurfaceControl.mergeToGlobalTransaction(t);
-                    SurfaceControl.closeTransaction();
-                }
+                // TODO: This should use pendingTransaction eventually, but right now things
+                // happening on the animation finished callback are happening on the global
+                // transaction.
+                // For now we need to run this after it's guaranteed that the transaction that
+                // reparents the surface onto the leash is executed already. Otherwise this may be
+                // executed first, leading to surface loss, as the reparent operations wouldn't
+                // be in order.
+                addAfterPrepareSurfaces.accept(() -> {
+                    if (anim != mAnimation) {
+                        // Callback was from another animation - ignore.
+                        return;
+                    }
+                    final Transaction t = new Transaction();
+                    SurfaceControl.openTransaction();
+                    try {
+                        reset(t, true /* destroyLeash */);
+                        animationFinishedCallback.run();
+                    } finally {
+                        SurfaceControl.mergeToGlobalTransaction(t);
+                        SurfaceControl.closeTransaction();
+                    }
+                });
             }
         };
     }
@@ -95,7 +116,7 @@
      *               handing it to the component that is responsible to run the animation.
      */
     void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
-        cancelAnimation(t, true /* restarting */);
+        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
         mAnimation = anim;
         final SurfaceControl surface = mAnimatable.getSurfaceControl();
         if (surface == null) {
@@ -158,7 +179,8 @@
      * Cancels any currently running animation.
      */
     void cancelAnimation() {
-        cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */);
+        cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */,
+                true /* forwardCancel */);
         mAnimatable.commitPendingTransaction();
     }
 
@@ -197,13 +219,47 @@
         return mLeash != null;
     }
 
-    private void cancelAnimation(Transaction t, boolean restarting) {
+    void transferAnimation(SurfaceAnimator from) {
+        if (from.mLeash == null) {
+            return;
+        }
+        final SurfaceControl surface = mAnimatable.getSurfaceControl();
+        final SurfaceControl parent = mAnimatable.getAnimationLeashParent();
+        if (surface == null || parent == null) {
+            Slog.w(TAG, "Unable to transfer animation, surface or parent is null");
+            cancelAnimation();
+            return;
+        }
+        endDelayingAnimationStart();
+        final Transaction t = mAnimatable.getPendingTransaction();
+        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
+        mLeash = from.mLeash;
+        mAnimation = from.mAnimation;
+
+        // Cancel source animation, but don't let animation runner cancel the animation.
+        from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
+        t.reparent(surface, mLeash.getHandle());
+        t.reparent(mLeash, parent.getHandle());
+        mAnimatable.onAnimationLeashCreated(t, mLeash);
+        mService.mAnimationTransferMap.put(mAnimation, this);
+    }
+
+    /**
+     * Cancels the animation, and resets the leash.
+     *
+     * @param t The transaction to use for all cancelling surface operations.
+     * @param restarting Whether we are restarting the animation.
+     * @param forwardCancel Whether to forward the cancel signal to the adapter executing the
+     *                      animation. This will be set to false when just transferring an animation
+     *                      to another animator.
+     */
+    private void cancelAnimation(Transaction t, boolean restarting, boolean forwardCancel) {
         if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting);
         final SurfaceControl leash = mLeash;
         final AnimationAdapter animation = mAnimation;
-        reset(t);
+        reset(t, forwardCancel);
         if (animation != null) {
-            if (!mAnimationStartDelayed) {
+            if (!mAnimationStartDelayed && forwardCancel) {
                 animation.onAnimationCancelled(leash);
             }
             if (!restarting) {
@@ -215,7 +271,7 @@
         }
     }
 
-    private void reset(Transaction t) {
+    private void reset(Transaction t, boolean destroyLeash) {
         final SurfaceControl surface = mAnimatable.getSurfaceControl();
         final SurfaceControl parent = mAnimatable.getParentSurfaceControl();
 
@@ -225,7 +281,8 @@
             if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent");
             t.reparent(surface, parent.getHandle());
         }
-        if (mLeash != null) {
+        mService.mAnimationTransferMap.remove(mAnimation);
+        if (mLeash != null && destroyLeash) {
             mAnimatable.destroyAfterPendingTransaction(mLeash);
         }
         mLeash = null;
@@ -241,6 +298,7 @@
             int height, boolean hidden) {
         if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash");
         final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash()
+                .setParent(mAnimatable.getAnimationLeashParent())
                 .setName(surface + " - animation-leash")
                 .setSize(width, height);
         final SurfaceControl leash = builder.build();
@@ -309,6 +367,11 @@
         SurfaceControl.Builder makeAnimationLeash();
 
         /**
+         * @return The parent that should be used for the animation leash.
+         */
+        @Nullable SurfaceControl getAnimationLeashParent();
+
+        /**
          * @return The surface of the object to be animated.
          */
         @Nullable SurfaceControl getSurfaceControl();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 244eb66..3c96ca1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -536,14 +536,7 @@
     /** Cancels any running app transitions associated with the task. */
     void cancelTaskWindowTransition() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
-            mChildren.get(i).mAppAnimator.clearAnimation();
-        }
-    }
-
-    /** Cancels any running thumbnail transitions associated with the task. */
-    void cancelTaskThumbnailTransition() {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            mChildren.get(i).mAppAnimator.clearThumbnail();
+            mChildren.get(i).cancelAnimation();
         }
     }
 
@@ -656,6 +649,9 @@
         mDimmer.resetDimStates();
         super.prepareSurfaces();
         getDimBounds(mTmpDimBoundsRect);
+
+        // Bounds need to be relative, as the dim layer is a child.
+        mTmpDimBoundsRect.offsetTo(0, 0);
         if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
             scheduleAnimation();
         }
@@ -677,7 +673,9 @@
         proto.end(token);
     }
 
-    public void dump(String prefix, PrintWriter pw) {
+    @Override
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         final String doublePrefix = prefix + "  ";
 
         pw.println(prefix + "taskId=" + mTaskId);
@@ -691,7 +689,7 @@
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final AppWindowToken wtoken = mChildren.get(i);
             pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
-            wtoken.dump(pw, triplePrefix);
+            wtoken.dump(pw, triplePrefix, dumpAll);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c091157..f79719c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -93,6 +93,8 @@
     private final ArraySet<Task> mTmpTasks = new ArraySet<>();
     private final Handler mHandler = new Handler();
 
+    private final Rect mTmpRect = new Rect();
+
     /**
      * Flag indicating whether we are running on an Android TV device.
      */
@@ -223,11 +225,11 @@
 
         final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
         final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
-        final Rect taskFrame = new Rect();
-        task.getBounds(taskFrame);
+        task.getBounds(mTmpRect);
+        mTmpRect.offsetTo(0, 0);
 
         final GraphicBuffer buffer = SurfaceControl.captureLayers(
-                task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
+                task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction);
 
         if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
             if (DEBUG_SCREENSHOT) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 9946c6a..7b4281c 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -31,9 +31,8 @@
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.proto.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING;
@@ -45,6 +44,7 @@
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.RemoteException;
@@ -146,6 +146,7 @@
      * For {@link #prepareSurfaces}.
      */
     final Rect mTmpDimBoundsRect = new Rect();
+    private final Point mLastSurfaceSize = new Point();
 
     TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
         super(service);
@@ -220,6 +221,8 @@
         }
         alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : getRawBounds(), insetBounds);
         mDisplayContent.setLayoutNeeded();
+
+        updateSurfaceBounds();
     }
 
     private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
@@ -241,10 +244,11 @@
             return;
         }
         getRawBounds(mTmpRect);
-        // TODO: Should be in relative coordinates.
-        getPendingTransaction().setSize(mAnimationBackgroundSurface, mTmpRect.width(),
-                mTmpRect.height()).setPosition(mAnimationBackgroundSurface, mTmpRect.left,
-                mTmpRect.top);
+        final Rect stackBounds = getBounds();
+        getPendingTransaction()
+                .setSize(mAnimationBackgroundSurface, mTmpRect.width(), mTmpRect.height())
+                .setPosition(mAnimationBackgroundSurface, mTmpRect.left - stackBounds.left,
+                        mTmpRect.top - stackBounds.top);
         scheduleAnimation();
     }
 
@@ -297,6 +301,7 @@
 
         updateAdjustedBounds();
 
+        updateSurfaceBounds();
         return result;
     }
 
@@ -317,7 +322,7 @@
         if (matchParentBounds()
                 || !inSplitScreenSecondaryWindowingMode()
                 || mDisplayContent == null
-                || mDisplayContent.getSplitScreenPrimaryStack() != null) {
+                || mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null) {
             return true;
         }
         return false;
@@ -712,7 +717,12 @@
     public void onConfigurationChanged(Configuration newParentConfig) {
         final int prevWindowingMode = getWindowingMode();
         super.onConfigurationChanged(newParentConfig);
+
+        // Only need to update surface size here since the super method will handle updating
+        // surface position.
+        updateSurfaceSize(getPendingTransaction());
         final int windowingMode = getWindowingMode();
+
         if (mDisplayContent == null || prevWindowingMode == windowingMode) {
             return;
         }
@@ -720,6 +730,31 @@
         updateBoundsForWindowModeChange();
     }
 
+    private void updateSurfaceBounds() {
+        updateSurfaceBounds(getPendingTransaction());
+        scheduleAnimation();
+    }
+
+    void updateSurfaceBounds(SurfaceControl.Transaction transaction) {
+        updateSurfaceSize(transaction);
+        updateSurfacePosition(transaction);
+    }
+
+    private void updateSurfaceSize(SurfaceControl.Transaction transaction) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+
+        final Rect stackBounds = getBounds();
+        final int width = stackBounds.width();
+        final int height = stackBounds.height();
+        if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
+            return;
+        }
+        transaction.setSize(mSurfaceControl, width, height);
+        mLastSurfaceSize.set(width, height);
+    }
+
     @Override
     void onDisplayChanged(DisplayContent dc) {
         if (mDisplayContent != null) {
@@ -1284,7 +1319,8 @@
         proto.end(token);
     }
 
-    public void dump(String prefix, PrintWriter pw) {
+    @Override
+     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         pw.println(prefix + "mStackId=" + mStackId);
         pw.println(prefix + "mDeferRemoval=" + mDeferRemoval);
         pw.println(prefix + "mBounds=" + getRawBounds().toShortString());
@@ -1300,7 +1336,7 @@
             pw.println(prefix + "mAdjustedBounds=" + mAdjustedBounds.toShortString());
         }
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
-            mChildren.get(taskNdx).dump(prefix + "  ", pw);
+            mChildren.get(taskNdx).dump(pw, prefix + "  ", dumpAll);
         }
         if (mAnimationBackgroundSurfaceIsShown) {
             pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
@@ -1313,7 +1349,7 @@
                 pw.print("  Exiting App #"); pw.print(i);
                 pw.print(' '); pw.print(token);
                 pw.println(':');
-                token.dump(pw, "    ");
+                token.dump(pw, "    ", dumpAll);
             }
         }
     }
@@ -1653,35 +1689,6 @@
         return super.checkCompleteDeferredRemoval();
     }
 
-    void stepAppWindowsAnimation(long currentTime) {
-        super.stepAppWindowsAnimation(currentTime);
-
-        // TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set
-        // below but is set in the loop above. See if it really matters...
-
-        // Clear before using.
-        mTmpAppTokens.clear();
-        // We copy the list as things can be removed from the exiting token list while we are
-        // processing.
-        mTmpAppTokens.addAll(mExitingAppTokens);
-        for (int i = 0; i < mTmpAppTokens.size(); i++) {
-            final AppWindowAnimator appAnimator = mTmpAppTokens.get(i).mAppAnimator;
-            appAnimator.wasAnimating = appAnimator.animating;
-            if (appAnimator.stepAnimationLocked(currentTime)) {
-                mService.mAnimator.setAnimating(true);
-                mService.mAnimator.mAppWindowAnimating = true;
-            } else if (appAnimator.wasAnimating) {
-                // stopped animating, do one more pass through the layout
-                appAnimator.mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
-                        "exiting appToken " + appAnimator.mAppToken + " done");
-                if (DEBUG_ANIM) Slog.v(TAG_WM,
-                        "updateWindowsApps...: done animating exiting " + appAnimator.mAppToken);
-            }
-        }
-        // Clear to avoid holding reference to tokens.
-        mTmpAppTokens.clear();
-    }
-
     @Override
     int getOrientation() {
         return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
@@ -1705,6 +1712,9 @@
         mDimmer.resetDimStates();
         super.prepareSurfaces();
         getDimBounds(mTmpDimBoundsRect);
+
+        // Bounds need to be relative, as the dim layer is a child.
+        mTmpDimBoundsRect.offsetTo(0, 0);
         if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
             scheduleAnimation();
         }
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 5caae32..d83f28c 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -199,16 +199,6 @@
         }
     }
 
-    public void cancelThumbnailTransition() {
-        synchronized (mWindowMap) {
-            if (mContainer == null) {
-                Slog.w(TAG_WM, "cancelThumbnailTransition: taskId " + mTaskId + " not found.");
-                return;
-            }
-            mContainer.cancelTaskThumbnailTransition();
-        }
-    }
-
     public void setTaskDescription(TaskDescription taskDescription) {
         synchronized (mWindowMap) {
             if (mContainer == null) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3ae4549..ac0919d 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -64,8 +64,6 @@
     // to another, and this is the previous wallpaper target.
     private WindowState mPrevWallpaperTarget = null;
 
-    private int mWallpaperAnimLayerAdjustment;
-
     private float mLastWallpaperX = -1;
     private float mLastWallpaperY = -1;
     private float mLastWallpaperXStep = -1;
@@ -112,7 +110,7 @@
         mFindResults.resetTopWallpaper = true;
         if (w != winAnimator.mWindowDetachedWallpaper && w.mAppToken != null) {
             // If this window's app token is hidden and not animating, it is of no interest to us.
-            if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) {
+            if (w.mAppToken.isHidden() && !w.mAppToken.isSelfAnimating()) {
                 if (DEBUG_WALLPAPER) Slog.v(TAG,
                         "Skipping hidden and not animating token: " + w);
                 return false;
@@ -130,10 +128,10 @@
         }
 
         final boolean keyguardGoingAwayWithWallpaper = (w.mAppToken != null
-                && AppTransition.isKeyguardGoingAwayTransit(
-                w.mAppToken.mAppAnimator.getTransit())
-                && (w.mAppToken.mAppAnimator.getTransitFlags()
-                & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
+                && w.mAppToken.isSelfAnimating()
+                && AppTransition.isKeyguardGoingAwayTransit(w.mAppToken.getTransit())
+                && (w.mAppToken.getTransitFlags()
+                        & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
 
         boolean needsShowWhenLockedWallpaper = false;
         if ((w.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
@@ -204,18 +202,19 @@
     private boolean isWallpaperVisible(WindowState wallpaperTarget) {
         if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
                 + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
-                + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
-                ? wallpaperTarget.mAppToken.mAppAnimator.animation : null)
+                + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
+                ? wallpaperTarget.mAppToken.isSelfAnimating() : null)
                 + " prev=" + mPrevWallpaperTarget);
         return (wallpaperTarget != null
                 && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null
-                && wallpaperTarget.mAppToken.mAppAnimator.animation != null)))
+                && wallpaperTarget.mAppToken.isSelfAnimating())))
                 || mPrevWallpaperTarget != null;
     }
 
     boolean isWallpaperTargetAnimating() {
         return mWallpaperTarget != null && mWallpaperTarget.mWinAnimator.isAnimationSet()
-                && !mWallpaperTarget.mWinAnimator.isDummyAnimation();
+                && (mWallpaperTarget.mAppToken == null
+                        || !mWallpaperTarget.mAppToken.isWaitingForTransitionStart());
     }
 
     void updateWallpaperVisibility() {
@@ -250,7 +249,7 @@
         for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(i);
             token.hideWallpaperToken(wasDeferred, "hideWallpapers");
-            if (DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG, "Hiding wallpaper " + token
+            if (DEBUG_WALLPAPER_LIGHT && !token.isHidden()) Slog.d(TAG, "Hiding wallpaper " + token
                     + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
                     + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, "  "));
         }
@@ -441,10 +440,6 @@
         }
     }
 
-    int getAnimLayerAdjustment() {
-        return mWallpaperAnimLayerAdjustment;
-    }
-
     private void findWallpaperTarget(DisplayContent dc) {
         mFindResults.reset();
         if (dc.isStackVisible(WINDOWING_MODE_FREEFORM)) {
@@ -546,7 +541,7 @@
     private void updateWallpaperTokens(boolean visible) {
         for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
-            token.updateWallpaperWindows(visible, mWallpaperAnimLayerAdjustment);
+            token.updateWallpaperWindows(visible);
             token.getDisplayContent().assignWindowLayers(false);
         }
     }
@@ -565,12 +560,6 @@
         if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper visibility: " + visible);
 
         if (visible) {
-            // If the wallpaper target is animating, we may need to copy its layer adjustment.
-            // Only do this if we are not transferring between two wallpaper targets.
-            mWallpaperAnimLayerAdjustment =
-                    (mPrevWallpaperTarget == null && mWallpaperTarget.mAppToken != null)
-                            ? mWallpaperTarget.mAppToken.getAnimLayerAdjustment() : 0;
-
             if (mWallpaperTarget.mWallpaperX >= 0) {
                 mLastWallpaperX = mWallpaperTarget.mWallpaperX;
                 mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep;
@@ -681,10 +670,6 @@
             pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX);
             pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY);
         }
-
-        if (mWallpaperAnimLayerAdjustment != 0) {
-            pw.println(prefix + "mWallpaperAnimLayerAdjustment=" + mWallpaperAnimLayerAdjustment);
-        }
     }
 
     /** Helper class for storing the results of a wallpaper target find operation. */
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 3389f71..2ae5c7b 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -16,12 +16,9 @@
 
 package com.android.server.wm;
 
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -56,7 +53,7 @@
             final WindowState wallpaper = mChildren.get(j);
             wallpaper.hideWallpaperWindow(wasDeferred, reason);
         }
-        hidden = true;
+        setHidden(true);
     }
 
     void sendWindowWallpaperCommand(
@@ -92,8 +89,9 @@
         final int dw = displayInfo.logicalWidth;
         final int dh = displayInfo.logicalHeight;
 
-        if (hidden == visible) {
-            hidden = !visible;
+        if (isHidden() == visible) {
+            setHidden(!visible);
+
             // Need to do a layout to ensure the wallpaper now has the correct size.
             mDisplayContent.setLayoutNeeded();
         }
@@ -119,12 +117,12 @@
         }
     }
 
-    void updateWallpaperWindows(boolean visible, int animLayerAdj) {
+    void updateWallpaperWindows(boolean visible) {
 
-        if (hidden == visible) {
+        if (isHidden() == visible) {
             if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
                     "Wallpaper token " + token + " hidden=" + !visible);
-            hidden = !visible;
+            setHidden(!visible);
             // Need to do a layout to ensure the wallpaper now has the correct size.
             mDisplayContent.setLayoutNeeded();
         }
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index bb25297..0863ee9 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -16,11 +16,20 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
+
 import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.SystemClock;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.Interpolator;
 import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
 
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 
@@ -32,10 +41,26 @@
     private Animation mAnimation;
     private final Point mPosition = new Point();
     private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
+    private final boolean mCanSkipFirstFrame;
+    private final Rect mStackBounds = new Rect();
+    private int mStackClipMode;
+    private final Rect mTmpRect = new Rect();
 
-    public WindowAnimationSpec(Animation animation, Point position)  {
+    public WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame)  {
+        this(animation, position, null /* stackBounds */, canSkipFirstFrame, STACK_CLIP_NONE);
+    }
+
+    public WindowAnimationSpec(Animation animation, Point position, Rect stackBounds,
+            boolean canSkipFirstFrame, int stackClipMode) {
         mAnimation = animation;
-        mPosition.set(position.x, position.y);
+        if (position != null) {
+            mPosition.set(position.x, position.y);
+        }
+        mCanSkipFirstFrame = canSkipFirstFrame;
+        mStackClipMode = stackClipMode;
+        if (stackBounds != null) {
+            mStackBounds.set(stackBounds);
+        }
     }
 
     @Override
@@ -61,6 +86,80 @@
         tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
         t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
         t.setAlpha(leash, tmp.transformation.getAlpha());
+        if (mStackClipMode == STACK_CLIP_NONE) {
+            t.setWindowCrop(leash, tmp.transformation.getClipRect());
+        } else if (mStackClipMode == STACK_CLIP_AFTER_ANIM) {
+            mTmpRect.set(mStackBounds);
+            // Offset stack bounds to stack position so the final crop is in screen space.
+            mTmpRect.offsetTo(mPosition.x, mPosition.y);
+            t.setFinalCrop(leash, mTmpRect);
+            t.setWindowCrop(leash, tmp.transformation.getClipRect());
+        } else {
+            mTmpRect.set(mStackBounds);
+            mTmpRect.intersect(tmp.transformation.getClipRect());
+            t.setWindowCrop(leash, mTmpRect);
+        }
+    }
+
+    @Override
+    public long calculateStatusBarTransitionStartTime() {
+        TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation);
+        if (openTranslateAnimation != null) {
+
+            // Some interpolators are extremely quickly mostly finished, but not completely. For
+            // our purposes, we need to find the fraction for which ther interpolator is mostly
+            // there, and use that value for the calculation.
+            float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
+            return SystemClock.uptimeMillis()
+                    + openTranslateAnimation.getStartOffset()
+                    + (long)(openTranslateAnimation.getDuration() * t)
+                    - STATUS_BAR_TRANSITION_DURATION;
+        } else {
+            return SystemClock.uptimeMillis();
+        }
+    }
+
+    @Override
+    public boolean canSkipFirstFrame() {
+        return mCanSkipFirstFrame;
+    }
+
+    /**
+     * Tries to find a {@link TranslateAnimation} inside the {@code animation}.
+     *
+     * @return the found animation, {@code null} otherwise
+     */
+    private static TranslateAnimation findTranslateAnimation(Animation animation) {
+        if (animation instanceof TranslateAnimation) {
+            return (TranslateAnimation) animation;
+        } else if (animation instanceof AnimationSet) {
+            AnimationSet set = (AnimationSet) animation;
+            for (int i = 0; i < set.getAnimations().size(); i++) {
+                Animation a = set.getAnimations().get(i);
+                if (a instanceof TranslateAnimation) {
+                    return (TranslateAnimation) a;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
+     * {@code interpolator(t + eps) > 0.99}.
+     */
+    private static float findAlmostThereFraction(Interpolator interpolator) {
+        float val = 0.5f;
+        float adj = 0.25f;
+        while (adj >= 0.01f) {
+            if (interpolator.getInterpolation(val) < 0.99f) {
+                val += adj;
+            } else {
+                val -= adj;
+            }
+            adj /= 2;
+        }
+        return val;
     }
 
     private static class TmpValues {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 7c56f00..3efd6ac 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -35,6 +35,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 
 /**
  * Singleton class that carries out the animations and Surface operations in a separate task
@@ -46,20 +47,17 @@
     final WindowManagerService mService;
     final Context mContext;
     final WindowManagerPolicy mPolicy;
-    private final WindowSurfacePlacer mWindowPlacerLocked;
 
     /** Is any window animating? */
     private boolean mAnimating;
-    private boolean mLastAnimating;
-
-    /** Is any app window animating? */
-    boolean mAppWindowAnimating;
+    private boolean mLastRootAnimating;
 
     final Choreographer.FrameCallback mAnimationFrameCallback;
 
     /** Time of current animation step. Reset on each iteration */
     long mCurrentTime;
 
+    boolean mAppWindowAnimating;
     /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
      * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
     int mAnimTransactionSequence;
@@ -75,7 +73,7 @@
 
     SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = new SparseArray<>(2);
 
-    boolean mInitialized = false;
+    private boolean mInitialized = false;
 
     // When set to true the animator will go over all windows after an animation frame is posted and
     // check if some got replaced and can be removed.
@@ -89,11 +87,16 @@
      */
     private boolean mAnimationFrameCallbackScheduled;
 
+    /**
+     * A list of runnable that need to be run after {@link WindowContainer#prepareSurfaces} is
+     * executed and the corresponding transaction is closed and applied.
+     */
+    private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
+
     WindowAnimator(final WindowManagerService service) {
         mService = service;
         mContext = service.mContext;
         mPolicy = service.mPolicy;
-        mWindowPlacerLocked = service.mWindowPlacerLocked;
         AnimationThread.getHandler().runWithScissors(
                 () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);
 
@@ -142,21 +145,10 @@
             scheduleAnimation();
         }
 
-        // Simulate back-pressure by opening and closing an empty animation transaction. This makes
-        // sure that an animation frame is at least presented once on the screen. We do this outside
-        // of the regular transaction such that we can avoid holding the window manager lock in case
-        // we receive back-pressure from SurfaceFlinger. Since closing an animation transaction
-        // without the window manager locks leads to ordering issues (as the transaction will be
-        // processed only at the beginning of the next frame which may result in another transaction
-        // that was executed later in WM side gets executed first on SF side), we don't update any
-        // Surface properties here such that reordering doesn't cause issues.
-        mService.executeEmptyAnimationTransaction();
-
         synchronized (mService.mWindowMap) {
             mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
             mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
             mAnimating = false;
-            mAppWindowAnimating = false;
             if (DEBUG_WINDOW_TRACE) {
                 Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
             }
@@ -170,7 +162,6 @@
                 for (int i = 0; i < numDisplays; i++) {
                     final int displayId = mDisplayContentsAnimators.keyAt(i);
                     final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
-                    dc.stepAppWindowsAnimation(mCurrentTime);
                     DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
 
                     final ScreenRotationAnimation screenRotationAnimation =
@@ -248,10 +239,11 @@
             }
 
             if (hasPendingLayoutChanges || doRequest) {
-                mWindowPlacerLocked.requestTraversal();
+                mService.mWindowPlacerLocked.requestTraversal();
             }
 
-            if (mAnimating && !mLastAnimating) {
+            final boolean rootAnimating = mService.mRoot.isSelfOrChildAnimating();
+            if (rootAnimating && !mLastRootAnimating) {
 
                 // Usually app transitions but quite a load onto the system already (with all the
                 // things happening in app), so pause task snapshot persisting to not increase the
@@ -259,13 +251,13 @@
                 mService.mTaskSnapshotController.setPersisterPaused(true);
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
             }
-            if (!mAnimating && mLastAnimating) {
-                mWindowPlacerLocked.requestTraversal();
+            if (!rootAnimating && mLastRootAnimating) {
+                mService.mWindowPlacerLocked.requestTraversal();
                 mService.mTaskSnapshotController.setPersisterPaused(false);
                 Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
             }
 
-            mLastAnimating = mAnimating;
+            mLastRootAnimating = rootAnimating;
 
             if (mRemoveReplacedWindows) {
                 mService.mRoot.removeReplacedWindows();
@@ -275,6 +267,7 @@
             mService.destroyPreservedSurfaceLocked();
             mService.mWindowPlacerLocked.destroyPendingSurfaces();
 
+            executeAfterPrepareSurfacesRunnables();
 
             if (DEBUG_WINDOW_TRACE) {
                 Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
@@ -438,4 +431,23 @@
     void orAnimating(boolean animating) {
         mAnimating |= animating;
     }
+
+    /**
+     * Adds a runnable to be executed after {@link WindowContainer#prepareSurfaces} is called and
+     * the corresponding transaction is closed and applied.
+     */
+    void addAfterPrepareSurfacesRunnable(Runnable r) {
+        mAfterPrepareSurfacesRunnables.add(r);
+        scheduleAnimation();
+    }
+
+    private void executeAfterPrepareSurfacesRunnables() {
+
+        // Traverse in order they were added.
+        final int size = mAfterPrepareSurfacesRunnables.size();
+        for (int i = 0; i < size; i++) {
+            mAfterPrepareSurfacesRunnables.get(i).run();
+        }
+        mAfterPrepareSurfacesRunnables.clear();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b2b6119..36e6418 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -29,7 +29,8 @@
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
-import android.graphics.PixelFormat.Opacity;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.util.Slog;
 import android.view.MagnificationSpec;
 import android.view.SurfaceControl;
@@ -93,10 +94,17 @@
     protected final SurfaceAnimator mSurfaceAnimator;
     protected final WindowManagerService mService;
 
+    private final Point mTmpPos = new Point();
+    protected final Point mLastSurfacePosition = new Point();
+
+    /** Total number of elements in this subtree, including our own hierarchy element. */
+    private int mTreeWeight = 1;
+
     WindowContainer(WindowManagerService service) {
         mService = service;
         mPendingTransaction = service.mTransactionFactory.make();
-        mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, service);
+        mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished,
+                service.mAnimator::addAfterPrepareSurfacesRunnable, service);
     }
 
     @Override
@@ -114,6 +122,13 @@
         return mChildren.get(index);
     }
 
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        super.onConfigurationChanged(newParentConfig);
+        updateSurfacePosition(getPendingTransaction());
+        scheduleAnimation();
+    }
+
     final protected void setParent(WindowContainer<WindowContainer> parent) {
         mParent = parent;
         // Removing parent usually means that we've detached this entity to destroy it or to attach
@@ -147,7 +162,7 @@
             // surface animator such that hierarchy is preserved when animating, i.e.
             // mSurfaceControl stays attached to the leash and we just reparent the leash to the
             // new parent.
-            mSurfaceAnimator.reparent(getPendingTransaction(), mParent.mSurfaceControl);
+            reparentSurfaceControl(getPendingTransaction(), mParent.mSurfaceControl);
         }
 
         // Either way we need to ask the parent to assign us a Z-order.
@@ -190,6 +205,8 @@
         } else {
             mChildren.add(positionToAdd, child);
         }
+        onChildAdded(child);
+
         // Set the parent after we've actually added a child in case a subclass depends on this.
         child.setParent(this);
     }
@@ -203,10 +220,21 @@
                     + " can't add to container=" + getName());
         }
         mChildren.add(index, child);
+        onChildAdded(child);
+
         // Set the parent after we've actually added a child in case a subclass depends on this.
         child.setParent(this);
     }
 
+    private void onChildAdded(WindowContainer child) {
+        mTreeWeight += child.mTreeWeight;
+        WindowContainer parent = getParent();
+        while (parent != null) {
+            parent.mTreeWeight += child.mTreeWeight;
+            parent = parent.getParent();
+        }
+    }
+
     /**
      * Removes the input child container from this container which is its parent.
      *
@@ -215,6 +243,7 @@
     @CallSuper
     void removeChild(E child) {
         if (mChildren.remove(child)) {
+            onChildRemoved(child);
             child.setParent(null);
         } else {
             throw new IllegalArgumentException("removeChild: container=" + child.getName()
@@ -222,6 +251,15 @@
         }
     }
 
+    private void onChildRemoved(WindowContainer child) {
+        mTreeWeight -= child.mTreeWeight;
+        WindowContainer parent = getParent();
+        while (parent != null) {
+            parent.mTreeWeight -= child.mTreeWeight;
+            parent = parent.getParent();
+        }
+    }
+
     /**
      * Removes this window container and its children with no regard for what else might be going on
      * in the system. For example, the container will be removed during animation if this method is
@@ -236,7 +274,9 @@
             // Need to do this after calling remove on the child because the child might try to
             // remove/detach itself from its parent which will cause an exception if we remove
             // it before calling remove on the child.
-            mChildren.remove(child);
+            if (mChildren.remove(child)) {
+                onChildRemoved(child);
+            }
         }
 
         if (mSurfaceControl != null) {
@@ -255,6 +295,34 @@
     }
 
     /**
+     * @return The index of this element in the hierarchy tree in prefix order.
+     */
+    int getPrefixOrderIndex() {
+        if (mParent == null) {
+            return 0;
+        }
+        return mParent.getPrefixOrderIndex(this);
+    }
+
+    private int getPrefixOrderIndex(WindowContainer child) {
+        int order = 0;
+        for (int i = 0; i < mChildren.size(); i++) {
+            final WindowContainer childI = mChildren.get(i);
+            if (child == childI) {
+                break;
+            }
+            order += childI.mTreeWeight;
+        }
+        if (mParent != null) {
+            order += mParent.getPrefixOrderIndex(this);
+        }
+
+        // We also need to count ourselves.
+        order++;
+        return order;
+    }
+
+    /**
      * Removes this window container and its children taking care not to remove them during a
      * critical stage in the system. For example, some containers will not be removed during
      * animation if this method is called.
@@ -446,6 +514,20 @@
     }
 
     /**
+     * @return {@code true} if in this subtree of the hierarchy we have an {@link AppWindowToken}
+     *         that is {@link #isSelfAnimating}; {@code false} otherwise.
+     */
+    boolean isAppAnimating() {
+        for (int j = mChildren.size() - 1; j >= 0; j--) {
+            final WindowContainer wc = mChildren.get(j);
+            if (wc.isAppAnimating()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * @return Whether our own container running an animation at the moment.
      */
     boolean isSelfAnimating() {
@@ -532,14 +614,6 @@
         }
     }
 
-    /** Step currently ongoing animation for App window containers. */
-    void stepAppWindowsAnimation(long currentTime) {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mChildren.get(i);
-            wc.stepAppWindowsAnimation(currentTime);
-        }
-    }
-
     void onAppTransitionDone() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
@@ -815,10 +889,7 @@
     void assignLayer(Transaction t, int layer) {
         final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
         if (mSurfaceControl != null && changed) {
-
-            // Route through surface animator to accommodate that our surface control might be
-            // attached to the leash, and leash is attached to parent container.
-            mSurfaceAnimator.setLayer(t, layer);
+            setLayer(t, layer);
             mLastLayer = layer;
             mLastRelativeToLayer = null;
         }
@@ -827,15 +898,30 @@
     void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
         final boolean changed = layer != mLastLayer || mLastRelativeToLayer != relativeTo;
         if (mSurfaceControl != null && changed) {
-
-            // Route through surface animator to accommodate that our surface control might be
-            // attached to the leash, and leash is attached to parent container.
-            mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
+            setRelativeLayer(t, relativeTo, layer);
             mLastLayer = layer;
             mLastRelativeToLayer = relativeTo;
         }
     }
 
+    protected void setLayer(Transaction t, int layer) {
+
+        // Route through surface animator to accommodate that our surface control might be
+        // attached to the leash, and leash is attached to parent container.
+        mSurfaceAnimator.setLayer(t, layer);
+    }
+
+    protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
+
+        // Route through surface animator to accommodate that our surface control might be
+        // attached to the leash, and leash is attached to parent container.
+        mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
+    }
+
+    protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
+        mSurfaceAnimator.reparent(t, newParent);
+    }
+
     void assignChildLayers(Transaction t) {
         int layer = 0;
 
@@ -991,6 +1077,10 @@
         mSurfaceAnimator.startAnimation(t, anim, hidden);
     }
 
+    void transferAnimation(WindowContainer from) {
+        mSurfaceAnimator.transferAnimation(from.mSurfaceAnimator);
+    }
+
     void cancelAnimation() {
         mSurfaceAnimator.cancelAnimation();
     }
@@ -1001,6 +1091,22 @@
     }
 
     @Override
+    public SurfaceControl getAnimationLeashParent() {
+        return getParentSurfaceControl();
+    }
+
+    /**
+     * @return The layer on which all app animations are happening.
+     */
+    SurfaceControl getAppAnimationLayer() {
+        final WindowContainer parent = getParent();
+        if (parent != null) {
+            return parent.getAppAnimationLayer();
+        }
+        return null;
+    }
+
+    @Override
     public void commitPendingTransaction() {
         scheduleAnimation();
     }
@@ -1059,10 +1165,39 @@
         return mSurfaceControl.getHeight();
     }
 
+    @CallSuper
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         if (mSurfaceAnimator.isAnimating()) {
             pw.print(prefix); pw.println("ContainerAnimator:");
             mSurfaceAnimator.dump(pw, prefix + "  ");
         }
     }
+
+    void updateSurfacePosition(SurfaceControl.Transaction transaction) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+
+        getRelativePosition(mTmpPos);
+        if (mTmpPos.equals(mLastSurfacePosition)) {
+            return;
+        }
+
+        transaction.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
+        mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);
+
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            mChildren.get(i).updateSurfacePosition(transaction);
+        }
+    }
+
+    void getRelativePosition(Point outPos) {
+        final Rect bounds = getBounds();
+        outPos.set(bounds.left, bounds.top);
+        final WindowContainer parent = getParent();
+        if (parent != null) {
+            final Rect parentBounds = parent.getBounds();
+            outPos.offset(-parentBounds.left, -parentBounds.top);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 62d2e7d..1935a44 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -118,8 +118,11 @@
          *                of AppTransition.TRANSIT_* values
          * @param openToken the token for the opening app
          * @param closeToken the token for the closing app
-         * @param openAnimation the animation for the opening app
-         * @param closeAnimation the animation for the closing app
+         * @param duration the total duration of the transition
+         * @param statusBarAnimationStartTime the desired start time for all visual animations in
+         *        the status bar caused by this app transition in uptime millis
+         * @param statusBarAnimationDuration the duration for all visual animations in the status
+         *        bar caused by this app transition in millis
          *
          * @return Return any bit set of {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_LAYOUT},
          * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_CONFIG},
@@ -127,7 +130,7 @@
          * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
          */
         public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
-                Animation openAnimation, Animation closeAnimation) {
+                long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) {
             return 0;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9fc9f3c..e91b16d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -71,13 +71,9 @@
 import static com.android.server.LockGuard.installLock;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
-import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
-import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
 import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
@@ -177,6 +173,7 @@
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
@@ -566,12 +563,10 @@
     int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     Rect mDockedStackCreateBounds;
 
-    private final SparseIntArray mTmpTaskIds = new SparseIntArray();
-
     boolean mForceResizableTasks = false;
     boolean mSupportsPictureInPicture = false;
 
-    private boolean mDisableTransitionAnimation = false;
+    boolean mDisableTransitionAnimation = false;
 
     int getDragLayerLocked() {
         return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
@@ -769,6 +764,11 @@
     final WindowAnimator mAnimator;
     final SurfaceAnimationRunner mSurfaceAnimationRunner;
 
+    /**
+     * Keeps track of which animations got transferred to which animators. Entries will get cleaned
+     * up when the animation finishes.
+     */
+    final ArrayMap<AnimationAdapter, SurfaceAnimator> mAnimationTransferMap = new ArrayMap<>();
     final BoundsAnimationController mBoundsAnimationController;
 
     private final PointerEventDispatcher mPointerEventDispatcher;
@@ -852,35 +852,6 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
-
-    /**
-     * Executes an empty animation transaction without holding the WM lock to simulate
-     * back-pressure. See {@link WindowAnimator#animate} why this is needed.
-     */
-    void executeEmptyAnimationTransaction() {
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
-            synchronized (mWindowMap) {
-                if (mRoot.mSurfaceTraceEnabled) {
-                    mRoot.mRemoteEventTrace.openSurfaceTransaction();
-                }
-                SurfaceControl.openTransaction();
-                SurfaceControl.setAnimationTransaction();
-                if (mRoot.mSurfaceTraceEnabled) {
-                    mRoot.mRemoteEventTrace.closeSurfaceTransaction();
-                }
-            }
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
-            SurfaceControl.closeTransaction();
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
-    }
-
     /** Listener to notify activity manager about app transitions. */
     final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
             = new WindowManagerInternal.AppTransitionListener() {
@@ -957,7 +928,6 @@
             boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
             WindowManagerPolicy policy) {
         installLock(this, INDEX_WINDOW);
-        mRoot = new RootWindowContainer(this);
         mContext = context;
         mHaveInputMethods = haveInputMethods;
         mAllowBootMessages = showBootMsgs;
@@ -981,8 +951,11 @@
         mDisplaySettings = new DisplaySettings();
         mDisplaySettings.readSettingsLocked();
 
-        mWindowPlacerLocked = new WindowSurfacePlacer(this);
         mPolicy = policy;
+        mAnimator = new WindowAnimator(this);
+        mRoot = new RootWindowContainer(this);
+
+        mWindowPlacerLocked = new WindowSurfacePlacer(this);
         mTaskSnapshotController = new TaskSnapshotController(this);
 
         mWindowTracing = WindowTracing.createDefaultAndStartLooper(context);
@@ -1080,7 +1053,6 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
         mHoldingScreenWakeLock.setReferenceCounted(false);
 
-        mAnimator = new WindowAnimator(this);
         mSurfaceAnimationRunner = new SurfaceAnimationRunner();
 
         mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
@@ -2279,83 +2251,6 @@
         }
     }
 
-    boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
-            int transit, boolean enter, boolean isVoiceInteraction) {
-        if (mDisableTransitionAnimation) {
-            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
-                Slog.v(TAG_WM,
-                        "applyAnimation: transition animation is disabled. atoken=" + atoken);
-            }
-            atoken.mAppAnimator.clearAnimation();
-            return false;
-        }
-        // Only apply an animation if the display isn't frozen.  If it is
-        // frozen, there is no reason to animate and it can cause strange
-        // artifacts when we unfreeze the display if some different animation
-        // is running.
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
-        if (atoken.okToAnimate()) {
-            final DisplayContent displayContent = atoken.getTask().getDisplayContent();
-            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-            final int width = displayInfo.appWidth;
-            final int height = displayInfo.appHeight;
-            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
-                    "applyAnimation: atoken=" + atoken);
-
-            // Determine the visible rect to calculate the thumbnail clip
-            final WindowState win = atoken.findMainWindow();
-            final Rect frame = new Rect(0, 0, width, height);
-            final Rect displayFrame = new Rect(0, 0,
-                    displayInfo.logicalWidth, displayInfo.logicalHeight);
-            final Rect insets = new Rect();
-            final Rect stableInsets = new Rect();
-            Rect surfaceInsets = null;
-            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
-                // won't exactly match the final freeform window frame (e.g. when overlapping with
-                // the status bar). In that case we need to use the final frame.
-                if (freeform) {
-                    frame.set(win.mFrame);
-                } else {
-                    frame.set(win.mContainingFrame);
-                }
-                surfaceInsets = win.getAttrs().surfaceInsets;
-                insets.set(win.mContentInsets);
-                stableInsets.set(win.mStableInsets);
-            }
-
-            if (atoken.mLaunchTaskBehind) {
-                // Differentiate the two animations. This one which is briefly on the screen
-                // gets the !enter animation, and the other activity which remains on the
-                // screen gets the enter animation. Both appear in the mOpeningApps set.
-                enter = false;
-            }
-            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
-                    + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
-                    + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
-            final Configuration displayConfig = displayContent.getConfiguration();
-            Animation a = mAppTransition.loadAnimation(lp, transit, enter, displayConfig.uiMode,
-                    displayConfig.orientation, frame, displayFrame, insets, surfaceInsets,
-                    stableInsets, isVoiceInteraction, freeform, atoken.getTask().mTaskId);
-            if (a != null) {
-                if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
-                final int containingWidth = frame.width();
-                final int containingHeight = frame.height();
-                atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight, width,
-                        height, mAppTransition.canSkipFirstFrame(),
-                        mAppTransition.getAppStackClipMode(),
-                        transit, mAppTransition.getTransitFlags());
-            }
-        } else {
-            atoken.mAppAnimator.clearAnimation();
-        }
-        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
-        return atoken.mAppAnimator.animation != null;
-    }
-
     boolean checkCallingPermission(String permission, String func) {
         // Quick check: if the calling permission is me, it's all okay.
         if (Binder.getCallingPid() == myPid()) {
@@ -2701,29 +2596,13 @@
         synchronized (mWindowMap) {
             mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback,
                     onAnimationFinishedCallback, scaleUp);
-            prolongAnimationsFromSpecs(specs, scaleUp);
 
         }
     }
 
-    void prolongAnimationsFromSpecs(@NonNull AppTransitionAnimationSpec[] specs, boolean scaleUp) {
-        // This is used by freeform <-> recents windows transition. We need to synchronize
-        // the animation with the appearance of the content of recents, so we will make
-        // animation stay on the first or last frame a little longer.
-        mTmpTaskIds.clear();
-        for (int i = specs.length - 1; i >= 0; i--) {
-            mTmpTaskIds.put(specs[i].taskId, 0);
-        }
-        for (final WindowState win : mWindowMap.values()) {
-            final Task task = win.getTask();
-            if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1
-                    && task.inFreeformWindowingMode()) {
-                final AppWindowToken appToken = win.mAppToken;
-                if (appToken != null && appToken.mAppAnimator != null) {
-                    appToken.mAppAnimator.startProlongAnimation(scaleUp ?
-                            PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END);
-                }
-            }
+    public void overridePendingAppTransitionStartCrossProfileApps() {
+        synchronized (mWindowMap) {
+            mAppTransition.overridePendingAppTransitionStartCrossProfileApps();
         }
     }
 
@@ -2749,8 +2628,8 @@
         synchronized (mWindowMap) {
             for (final WindowState win : mWindowMap.values()) {
                 final AppWindowToken appToken = win.mAppToken;
-                if (appToken != null && appToken.mAppAnimator != null) {
-                    appToken.mAppAnimator.endProlongedAnimation();
+                if (appToken != null) {
+                    appToken.endDelayingAnimationStart();
                 }
             }
             mAppTransition.notifyProlongedAnimationsEnded();
@@ -2805,15 +2684,6 @@
         }
     }
 
-    void updateTokenInPlaceLocked(AppWindowToken wtoken, int transit) {
-        if (transit != TRANSIT_UNSET) {
-            if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
-                wtoken.mAppAnimator.setNullAnimation();
-            }
-            applyAnimationLocked(wtoken, null, transit, false, false);
-        }
-    }
-
     public void setDockedStackCreateState(int mode, Rect bounds) {
         synchronized (mWindowMap) {
             setDockedStackCreateStateLocked(mode, bounds);
@@ -5608,7 +5478,9 @@
 
     /** Note that Locked in this case is on mLayoutToAnim */
     void scheduleAnimationLocked() {
-        mAnimator.scheduleAnimation();
+        if (mAnimator != null) {
+            mAnimator.scheduleAnimation();
+        }
     }
 
     // TODO: Move to DisplayContent
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
index 1b2eb46..dd89b3b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -36,6 +36,7 @@
     private final Object mLock = new Object();
 
     private final int mAnimationThreadId;
+    private final int mSurfaceAnimationThreadId;
 
     @GuardedBy("mLock")
     private boolean mAppTransitionRunning;
@@ -45,14 +46,16 @@
     WindowManagerThreadPriorityBooster() {
         super(THREAD_PRIORITY_DISPLAY, INDEX_WINDOW);
         mAnimationThreadId = AnimationThread.get().getThreadId();
+        mSurfaceAnimationThreadId = SurfaceAnimationThread.get().getThreadId();
     }
 
     @Override
     public void boost() {
 
-        // Do not boost the animation thread. As the animation thread is changing priorities,
+        // Do not boost the animation threads. As the animation threads are changing priorities,
         // boosting it might mess up the priority because we reset it the the previous priority.
-        if (myTid() == mAnimationThreadId) {
+        final int myTid = myTid();
+        if (myTid == mAnimationThreadId || myTid == mSurfaceAnimationThreadId) {
             return;
         }
         super.boost();
@@ -62,7 +65,8 @@
     public void reset() {
 
         // See comment in boost().
-        if (myTid() == mAnimationThreadId) {
+        final int myTid = myTid();
+        if (myTid == mAnimationThreadId || myTid == mSurfaceAnimationThreadId) {
             return;
         }
         super.reset();
@@ -92,5 +96,6 @@
                 ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
         setBoostToPriority(priority);
         setThreadPriority(mAnimationThreadId, priority);
+        setThreadPriority(mSurfaceAnimationThreadId, priority);
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ddc1eac..0ad60c9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -48,8 +48,8 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -112,15 +112,36 @@
 import static com.android.server.wm.proto.WindowStateProto.CONTAINING_FRAME;
 import static com.android.server.wm.proto.WindowStateProto.CONTENT_FRAME;
 import static com.android.server.wm.proto.WindowStateProto.CONTENT_INSETS;
+import static com.android.server.wm.proto.WindowStateProto.CUTOUT;
+import static com.android.server.wm.proto.WindowStateProto.DECOR_FRAME;
+import static com.android.server.wm.proto.WindowStateProto.DESTROYING;
+import static com.android.server.wm.proto.WindowStateProto.DISPLAY_FRAME;
 import static com.android.server.wm.proto.WindowStateProto.DISPLAY_ID;
 import static com.android.server.wm.proto.WindowStateProto.FRAME;
 import static com.android.server.wm.proto.WindowStateProto.GIVEN_CONTENT_INSETS;
+import static com.android.server.wm.proto.WindowStateProto.HAS_SURFACE;
 import static com.android.server.wm.proto.WindowStateProto.IDENTIFIER;
+import static com.android.server.wm.proto.WindowStateProto.IS_ON_SCREEN;
+import static com.android.server.wm.proto.WindowStateProto.IS_READY_FOR_DISPLAY;
+import static com.android.server.wm.proto.WindowStateProto.IS_VISIBLE;
+import static com.android.server.wm.proto.WindowStateProto.OUTSETS;
+import static com.android.server.wm.proto.WindowStateProto.OUTSET_FRAME;
+import static com.android.server.wm.proto.WindowStateProto.OVERSCAN_FRAME;
+import static com.android.server.wm.proto.WindowStateProto.OVERSCAN_INSETS;
 import static com.android.server.wm.proto.WindowStateProto.PARENT_FRAME;
-import static com.android.server.wm.proto.WindowStateProto.STACK_ID;
+import static com.android.server.wm.proto.WindowStateProto.REMOVED;
+import static com.android.server.wm.proto.WindowStateProto.REMOVE_ON_EXIT;
+import static com.android.server.wm.proto.WindowStateProto.REQUESTED_HEIGHT;
+import static com.android.server.wm.proto.WindowStateProto.REQUESTED_WIDTH;
 import static com.android.server.wm.proto.WindowStateProto.SHOWN_POSITION;
+import static com.android.server.wm.proto.WindowStateProto.STABLE_INSETS;
+import static com.android.server.wm.proto.WindowStateProto.STACK_ID;
 import static com.android.server.wm.proto.WindowStateProto.SURFACE_INSETS;
 import static com.android.server.wm.proto.WindowStateProto.SURFACE_POSITION;
+import static com.android.server.wm.proto.WindowStateProto.SYSTEM_UI_VISIBILITY;
+import static com.android.server.wm.proto.WindowStateProto.VIEW_VISIBILITY;
+import static com.android.server.wm.proto.WindowStateProto.VISIBLE_FRAME;
+import static com.android.server.wm.proto.WindowStateProto.VISIBLE_INSETS;
 import static com.android.server.wm.proto.WindowStateProto.WINDOW_CONTAINER;
 
 import android.annotation.CallSuper;
@@ -142,6 +163,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
 import android.util.Slog;
@@ -1432,7 +1454,7 @@
      */
     // TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
     boolean isWinVisibleLw() {
-        return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.mAppAnimator.animating)
+        return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.isSelfAnimating())
                 && isVisible();
     }
 
@@ -1441,7 +1463,7 @@
      * not the pending requested hidden state.
      */
     boolean isVisibleNow() {
-        return (!mToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING)
+        return (!mToken.isHidden() || mAttrs.type == TYPE_APPLICATION_STARTING)
                 && isVisible();
     }
 
@@ -1479,7 +1501,7 @@
         final AppWindowToken atoken = mAppToken;
         if (atoken != null) {
             return ((!isParentWindowHidden() && !atoken.hiddenRequested)
-                    || mWinAnimator.isAnimationSet() || atoken.mAppAnimator.animation != null);
+                    || mWinAnimator.isAnimationSet());
         }
         return !isParentWindowHidden() || mWinAnimator.isAnimationSet();
     }
@@ -1501,7 +1523,7 @@
      */
     boolean isInteresting() {
         return mAppToken != null && !mAppDied
-                && (!mAppToken.mAppAnimator.freezingScreen || !mAppFreezing);
+                && (!mAppToken.isFreezingScreen() || !mAppFreezing);
     }
 
     /**
@@ -1513,33 +1535,24 @@
             return false;
         }
         return mHasSurface && mPolicyVisibility && !mDestroying
-                && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden)
-                        || mWinAnimator.isAnimationSet()
-                        || ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null)));
+                && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.isHidden())
+                        || mWinAnimator.isAnimationSet());
     }
 
     // TODO: Another visibility method that was added late in the release to minimize risk.
     @Override
     public boolean canAffectSystemUiFlags() {
-        final boolean shown = mWinAnimator.getShown();
-
-        // We only consider the app to be exiting when the animation has started. After the app
-        // transition is executed the windows are marked exiting before the new windows have been
-        // shown. Thus, wait considering a window to be exiting after the animation has actually
-        // started.
-        final boolean appAnimationStarting = mAppToken != null
-                && mAppToken.mAppAnimator.isAnimationStarting();
-        final boolean exitingSelf = mAnimatingExit && !appAnimationStarting;
-        final boolean appExiting = mAppToken != null && mAppToken.hidden && !appAnimationStarting;
-
-        final boolean exiting = exitingSelf || mDestroying || appExiting;
         final boolean translucent = mAttrs.alpha == 0.0f;
-
-        // If we are entering with a dummy animation, avoid affecting SystemUI flags until the
-        // transition is starting.
-        final boolean enteringWithDummyAnimation =
-                mWinAnimator.isDummyAnimation() && mWinAnimator.mShownAlpha == 0f;
-        return shown && !exiting && !translucent && !enteringWithDummyAnimation;
+        if (translucent) {
+            return false;
+        }
+        if (mAppToken == null) {
+            final boolean shown = mWinAnimator.getShown();
+            final boolean exiting = mAnimatingExit || mDestroying;
+            return shown && !exiting;
+        } else {
+            return !mAppToken.isHidden();
+        }
     }
 
     /**
@@ -1550,10 +1563,8 @@
     public boolean isDisplayedLw() {
         final AppWindowToken atoken = mAppToken;
         return isDrawnLw() && mPolicyVisibility
-            && ((!isParentWindowHidden() &&
-                    (atoken == null || !atoken.hiddenRequested))
-                        || mWinAnimator.isAnimationSet()
-                        || (atoken != null && atoken.mAppAnimator.animation != null));
+                && ((!isParentWindowHidden() && (atoken == null || !atoken.hiddenRequested))
+                        || mWinAnimator.isAnimationSet());
     }
 
     /**
@@ -1561,8 +1572,7 @@
      */
     @Override
     public boolean isAnimatingLw() {
-        return mWinAnimator.isAnimationSet()
-                || (mAppToken != null && mAppToken.mAppAnimator.animation != null);
+        return isAnimating();
     }
 
     @Override
@@ -1570,7 +1580,7 @@
         final AppWindowToken atoken = mAppToken;
         return mViewVisibility == View.GONE
                 || !mRelayoutCalled
-                || (atoken == null && mToken.hidden)
+                || (atoken == null && mToken.isHidden())
                 || (atoken != null && atoken.hiddenRequested)
                 || isParentWindowHidden()
                 || (mAnimatingExit && !isAnimatingLw())
@@ -1608,8 +1618,7 @@
         // to determine if it's occluding apps.
         return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE)
                 || (mIsWallpaper && mWallpaperVisible))
-                && isDrawnLw() && !mWinAnimator.isAnimationSet()
-                && (mAppToken == null || mAppToken.mAppAnimator.animation == null);
+                && isDrawnLw() && !mWinAnimator.isAnimationSet();
     }
 
     @Override
@@ -1631,7 +1640,7 @@
             // Starting window that's exiting will be removed when the animation finishes.
             // Mark all relevant flags for that onExitAnimationDone will proceed all the way
             // to actually remove it.
-            if (!visible && isVisibleNow() && mAppToken.mAppAnimator.isAnimating()) {
+            if (!visible && isVisibleNow() && mAppToken.isSelfAnimating()) {
                 mAnimatingExit = true;
                 mRemoveOnExit = true;
                 mWindowRemovalAllowed = true;
@@ -1908,7 +1917,7 @@
                     + " surfaceShowing=" + mWinAnimator.getShown()
                     + " isAnimationSet=" + mWinAnimator.isAnimationSet()
                     + " app-animation="
-                    + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
+                    + (mAppToken != null ? mAppToken.isSelfAnimating() : "false")
                     + " mWillReplaceWindow=" + mWillReplaceWindow
                     + " inPendingTransaction="
                     + (mAppToken != null ? mAppToken.inPendingTransaction : false)
@@ -1968,8 +1977,8 @@
                         mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
                     }
                 }
-                final boolean isAnimating =
-                        mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
+                final boolean isAnimating = mWinAnimator.isAnimationSet()
+                        && (mAppToken == null || !mAppToken.isWaitingForTransitionStart());
                 final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
                         && mAppToken.isLastWindow(this);
                 // We delay the removal of a window if it has a showing surface that can be used to run
@@ -2019,28 +2028,6 @@
         mHasSurface = hasSurface;
     }
 
-    int getAnimLayerAdjustment() {
-        if (mIsImWindow && mService.mInputMethodTarget != null) {
-            final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken;
-            if (appToken != null) {
-                return appToken.getAnimLayerAdjustment();
-            }
-        }
-
-        return mToken.getAnimLayerAdjustment();
-    }
-
-    int getSpecialWindowAnimLayerAdjustment() {
-        int specialAdjustment = 0;
-        if (mIsImWindow) {
-            specialAdjustment = getDisplayContent().mInputMethodAnimLayerAdjustment;
-        } else if (mIsWallpaper) {
-            specialAdjustment = getDisplayContent().mWallpaperController.getAnimLayerAdjustment();
-        }
-
-        return mLayer + specialAdjustment;
-    }
-
     boolean canBeImeTarget() {
         if (mIsImWindow) {
             // IME windows can't be IME targets. IME targets are required to be below the IME
@@ -2260,12 +2247,13 @@
                         mWinAnimator + ": " + mPolicyVisibilityAfterAnim);
             }
             mPolicyVisibility = mPolicyVisibilityAfterAnim;
-            setDisplayLayoutNeeded();
             if (!mPolicyVisibility) {
+                mWinAnimator.hide("checkPolicyVisibilityChange");
                 if (mService.mCurrentFocus == this) {
                     if (DEBUG_FOCUS_LIGHT) Slog.i(TAG,
                             "setAnimationLocked: setting mFocusMayChange true");
                     mService.mFocusMayChange = true;
+                    setDisplayLayoutNeeded();
                 }
                 // Window is no longer visible -- make sure if we were waiting
                 // for it to be displayed before enabling the display, that
@@ -2679,8 +2667,11 @@
 
     boolean destroySurface(boolean cleanupOnResume, boolean appStopped) {
         boolean destroyedSomething = false;
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
+
+        // Copying to a different list as multiple children can be removed.
+        final ArrayList<WindowState> childWindows = new ArrayList<>(mChildren);
+        for (int i = childWindows.size() - 1; i >= 0; --i) {
+            final WindowState c = childWindows.get(i);
             destroyedSomething |= c.destroySurface(cleanupOnResume, appStopped);
         }
 
@@ -3121,6 +3112,27 @@
         for (int i = 0; i < mChildren.size(); i++) {
             mChildren.get(i).writeToProto(proto, CHILD_WINDOWS, trim);
         }
+        proto.write(REQUESTED_WIDTH, mRequestedWidth);
+        proto.write(REQUESTED_HEIGHT, mRequestedHeight);
+        proto.write(VIEW_VISIBILITY, mViewVisibility);
+        proto.write(SYSTEM_UI_VISIBILITY, mSystemUiVisibility);
+        proto.write(HAS_SURFACE, mHasSurface);
+        proto.write(IS_READY_FOR_DISPLAY, isReadyForDisplay());
+        mDisplayFrame.writeToProto(proto, DISPLAY_FRAME);
+        mOverscanFrame.writeToProto(proto, OVERSCAN_FRAME);
+        mVisibleFrame.writeToProto(proto, VISIBLE_FRAME);
+        mDecorFrame.writeToProto(proto, DECOR_FRAME);
+        mOutsetFrame.writeToProto(proto, OUTSET_FRAME);
+        mOverscanInsets.writeToProto(proto, OVERSCAN_INSETS);
+        mVisibleInsets.writeToProto(proto, VISIBLE_INSETS);
+        mStableInsets.writeToProto(proto, STABLE_INSETS);
+        mOutsets.writeToProto(proto, OUTSETS);
+        mDisplayCutout.writeToProto(proto, CUTOUT);
+        proto.write(REMOVE_ON_EXIT, mRemoveOnExit);
+        proto.write(DESTROYING, mDestroying);
+        proto.write(REMOVED, mRemoved);
+        proto.write(IS_ON_SCREEN, isOnScreen());
+        proto.write(IS_VISIBLE, isVisible());
         proto.end(token);
     }
 
@@ -3170,7 +3182,6 @@
             pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer);
                     pw.print(" mSubLayer="); pw.print(mSubLayer);
                     pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+");
-                    pw.print(getAnimLayerAdjustment());
                     pw.print("="); pw.print(mWinAnimator.mAnimLayer);
                     pw.print(" mLastLayer="); pw.println(mWinAnimator.mLastLayer);
         }
@@ -3697,10 +3708,10 @@
                     + " parentHidden=" + isParentWindowHidden()
                     + " tok.hiddenRequested="
                     + (mAppToken != null && mAppToken.hiddenRequested)
-                    + " tok.hidden=" + (mAppToken != null && mAppToken.hidden)
+                    + " tok.hidden=" + (mAppToken != null && mAppToken.isHidden())
                     + " animationSet=" + mWinAnimator.isAnimationSet()
                     + " tok animating="
-                    + (mWinAnimator.mAppAnimator != null && mWinAnimator.mAppAnimator.animating)
+                    + (mAppToken != null && mAppToken.isSelfAnimating())
                     + " Callers=" + Debug.getCallers(4));
         }
     }
@@ -3714,6 +3725,13 @@
             windowInfo.activityToken = mAppToken.appToken.asBinder();
         }
         windowInfo.title = mAttrs.accessibilityTitle;
+        // Panel windows have no public way to set the a11y title directly. Use the
+        // regular title as a fallback.
+        if (TextUtils.isEmpty(windowInfo.title)
+                && (mAttrs.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW)
+                && (mAttrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW)) {
+            windowInfo.title = mAttrs.getTitle();
+        }
         windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor;
         windowInfo.focused = isFocused();
         Task task = getTask();
@@ -3911,8 +3929,7 @@
 
         if (!mChildren.isEmpty()) {
             // Copying to a different list as multiple children can be removed.
-            // TODO: Not sure if we really need to copy this into a different list.
-            final LinkedList<WindowState> childWindows = new LinkedList(mChildren);
+            final ArrayList<WindowState> childWindows = new ArrayList<>(mChildren);
             for (int i = childWindows.size() - 1; i >= 0; i--) {
                 childWindows.get(i).onExitAnimationDone();
             }
@@ -4292,7 +4309,8 @@
         anim.restrictDuration(MAX_ANIMATION_DURATION);
         anim.scaleCurrentDuration(mService.getWindowAnimationScaleLocked());
         final AnimationAdapter adapter = new LocalAnimationAdapter(
-                new WindowAnimationSpec(anim, mSurfacePosition), mService.mSurfaceAnimationRunner);
+                new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */),
+                mService.mSurfaceAnimationRunner);
         startAnimation(mPendingTransaction, adapter);
         commitPendingTransaction();
     }
@@ -4329,8 +4347,22 @@
         float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx;
         float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy;
         float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy;
-        float9[Matrix.MTRANS_X] = mSurfacePosition.x + mShownPosition.x;
-        float9[Matrix.MTRANS_Y] = mSurfacePosition.y + mShownPosition.y;
+        int x = mSurfacePosition.x + mShownPosition.x;
+        int y = mSurfacePosition.y + mShownPosition.y;
+
+        // If changed, also adjust transformFrameToSurfacePosition
+        final WindowContainer parent = getParent();
+        if (isChildWindow()) {
+            final WindowState parentWindow = getParentWindow();
+            x += parentWindow.mFrame.left - parentWindow.mAttrs.surfaceInsets.left;
+            y += parentWindow.mFrame.top - parentWindow.mAttrs.surfaceInsets.top;
+        } else if (parent != null) {
+            final Rect parentBounds = parent.getBounds();
+            x += parentBounds.left;
+            y += parentBounds.top;
+        }
+        float9[Matrix.MTRANS_X] = x;
+        float9[Matrix.MTRANS_Y] = y;
         float9[Matrix.MPERSP_0] = 0;
         float9[Matrix.MPERSP_1] = 0;
         float9[Matrix.MPERSP_2] = 1;
@@ -4415,7 +4447,13 @@
 
     @Override
     boolean needsZBoost() {
-        return getAnimLayerAdjustment() > 0 || mWillReplaceWindow;
+        if (mIsImWindow && mService.mInputMethodTarget != null) {
+            final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken;
+            if (appToken != null) {
+                return appToken.needsZBoost();
+            }
+        }
+        return mWillReplaceWindow;
     }
 
     private void applyDims(Dimmer dimmer) {
@@ -4449,6 +4487,7 @@
 
         // Leash is now responsible for position, so set our position to 0.
         t.setPosition(mSurfaceControl, 0, 0);
+        mLastSurfacePosition.set(0, 0);
     }
 
     @Override
@@ -4457,24 +4496,36 @@
         updateSurfacePosition(t);
     }
 
+    @Override
     void updateSurfacePosition(Transaction t) {
         if (mSurfaceControl == null) {
             return;
         }
 
         transformFrameToSurfacePosition(mFrame.left, mFrame.top, mSurfacePosition);
-        if (!mSurfaceAnimator.hasLeash()) {
+        if (!mSurfaceAnimator.hasLeash() && !mLastSurfacePosition.equals(mSurfacePosition)) {
             t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+            mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
         }
     }
 
     private void transformFrameToSurfacePosition(int left, int top, Point outPoint) {
         outPoint.set(left, top);
+
+        // If changed, also adjust getTransformationMatrix
+        final WindowContainer parentWindowContainer = getParent();
         if (isChildWindow()) {
             // TODO: This probably falls apart at some point and we should
             // actually compute relative coordinates.
+
+            // Since the parent was outset by its surface insets, we need to undo the outsetting
+            // with insetting by the same amount.
             final WindowState parent = getParentWindow();
-            outPoint.offset(-parent.mFrame.left, -parent.mFrame.top);
+            outPoint.offset(-parent.mFrame.left + parent.mAttrs.surfaceInsets.left,
+                    -parent.mFrame.top + parent.mAttrs.surfaceInsets.top);
+        } else if (parentWindowContainer != null) {
+            final Rect parentBounds = parentWindowContainer.getBounds();
+            outPoint.offset(-parentBounds.left, -parentBounds.top);
         }
 
         // Expand for surface insets. See WindowState.expandForSurfaceInsets.
@@ -4532,7 +4583,8 @@
         private MoveAnimationSpec(int fromX, int fromY, int toX, int toY) {
             final Animation anim = AnimationUtils.loadAnimation(mContext,
                     com.android.internal.R.anim.window_move_from_decor);
-            mDuration = anim.computeDurationHint();
+            mDuration = (long)
+                    (anim.computeDurationHint() * mService.getWindowAnimationScaleLocked());
             mInterpolator = anim.getInterpolator();
             mFrom.set(fromX, fromY);
             mTo.set(toX, toY);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 0eabc89..96b0bd5 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -25,7 +25,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.AppWindowAnimator.sDummyAnimation;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -43,10 +42,11 @@
 import static com.android.server.wm.WindowManagerService.logWithStack;
 import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
 import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
+import static com.android.server.wm.proto.WindowStateAnimatorProto.DRAW_STATE;
 import static com.android.server.wm.proto.WindowStateAnimatorProto.LAST_CLIP_RECT;
 import static com.android.server.wm.proto.WindowStateAnimatorProto.SURFACE;
+import static com.android.server.wm.proto.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
 
-import android.app.WindowConfiguration;
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
@@ -65,7 +65,6 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
 
 import com.android.server.policy.WindowManagerPolicy;
 
@@ -103,7 +102,6 @@
     final WindowState mWin;
     private final WindowStateAnimator mParentWinAnimator;
     final WindowAnimator mAnimator;
-    AppWindowAnimator mAppAnimator;
     final Session mSession;
     final WindowManagerPolicy mPolicy;
     final Context mContext;
@@ -228,7 +226,6 @@
 
         mWin = win;
         mParentWinAnimator = !win.isChildWindow() ? null : win.getParentWindow().mWinAnimator;
-        mAppAnimator = win.mAppToken == null ? null : win.mAppToken.mAppAnimator;
         mSession = win.mSession;
         mAttrType = win.mAttrs.type;
         mIsWallpaper = win.mIsWallpaper;
@@ -242,17 +239,12 @@
         return mWin.isAnimating();
     }
 
-    /** Is the window animating the DummyAnimation? */
-    boolean isDummyAnimation() {
-        return mAppAnimator != null
-                && mAppAnimator.animation == sDummyAnimation;
-    }
-
     /**
      * Is this window currently waiting to run an opening animation?
      */
     boolean isWaitingForOpening() {
-        return mService.mAppTransition.isTransitionSet() && isDummyAnimation()
+        return mService.mAppTransition.isTransitionSet()
+                && (mWin.mAppToken != null && mWin.mAppToken.isHidden())
                 && mService.mOpeningApps.contains(mWin.mAppToken);
     }
 
@@ -423,7 +415,7 @@
             return;
         }
 
-        if (mWin.mAppToken.mAppAnimator.animation == null) {
+        if (!mWin.mAppToken.isSelfAnimating()) {
             mWin.mAppToken.clearAllDrawn();
         } else {
             // Currently animating, persist current state of allDrawn until animation
@@ -667,8 +659,6 @@
     }
 
     void computeShownFrameLocked() {
-        Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
-                ? mAppAnimator.transformation : null;
 
         final int displayId = mWin.getDisplayId();
         final ScreenRotationAnimation screenRotationAnimation =
@@ -677,14 +667,14 @@
                 screenRotationAnimation != null && screenRotationAnimation.isAnimating();
 
         mHasClipRect = false;
-        if (appTransformation != null || screenAnimation) {
+        if (screenAnimation) {
             // cache often used attributes locally
             final Rect frame = mWin.mFrame;
             final float tmpFloats[] = mService.mTmpFloats;
             final Matrix tmpMatrix = mWin.mTmpMatrix;
 
             // Compute the desired transformation.
-            if (screenAnimation && screenRotationAnimation.isRotating()) {
+            if (screenRotationAnimation.isRotating()) {
                 // If we are doing a screen animation, the global rotation
                 // applied to windows can result in windows that are carefully
                 // aligned with each other to slightly separate, allowing you
@@ -702,6 +692,7 @@
             } else {
                 tmpMatrix.reset();
             }
+
             tmpMatrix.postScale(mWin.mGlobalScale, mWin.mGlobalScale);
 
             // WindowState.prepareSurfaces expands for surface insets (in order they don't get
@@ -709,9 +700,6 @@
             tmpMatrix.postTranslate(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left,
                     mWin.mYOffset + mWin.mAttrs.surfaceInsets.top);
 
-            if (appTransformation != null) {
-                tmpMatrix.postConcat(appTransformation.getMatrix());
-            }
 
             // "convert" it into SurfaceFlinger's format
             // (a 2x2 matrix + an offset)
@@ -740,24 +728,6 @@
                     || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)
                             && x == frame.left && y == frame.top))) {
                 //Slog.i(TAG_WM, "Applying alpha transform");
-                if (appTransformation != null) {
-                    mShownAlpha *= appTransformation.getAlpha();
-                    if (appTransformation.hasClipRect()) {
-                        mClipRect.set(appTransformation.getClipRect());
-                        mHasClipRect = true;
-                        // The app transformation clip will be in the coordinate space of the main
-                        // activity window, which the animation correctly assumes will be placed at
-                        // (0,0)+(insets) relative to the containing frame. This isn't necessarily
-                        // true for child windows though which can have an arbitrary frame position
-                        // relative to their containing frame. We need to offset the difference
-                        // between the containing frame as used to calculate the crop and our
-                        // bounds to compensate for this.
-                        if (mWin.layoutInParentFrame()) {
-                            mClipRect.offset( (mWin.mContainingFrame.left - mWin.mFrame.left),
-                                    mWin.mContainingFrame.top - mWin.mFrame.top );
-                        }
-                    }
-                }
                 if (screenAnimation) {
                     mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
                 }
@@ -768,7 +738,6 @@
             if ((DEBUG_ANIM || WindowManagerService.localLOGV)
                     && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
                     TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
-                    + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha())
                     + " screen=" + (screenAnimation ?
                             screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
             return;
@@ -800,47 +769,6 @@
     }
 
     /**
-     * In some scenarios we use a screen space clip rect (so called, final clip rect)
-     * to crop to stack bounds. Generally because it's easier to deal with while
-     * animating.
-     *
-     * @return True in scenarios where we use the final clip rect for stack clipping.
-     */
-    private boolean useFinalClipRect() {
-        return (isAnimationSet() && resolveStackClip() == STACK_CLIP_AFTER_ANIM)
-                || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWindowingMode();
-    }
-
-    /**
-     * Calculate the screen-space crop rect and fill finalClipRect.
-     * @return true if finalClipRect has been filled, otherwise,
-     * no screen space crop should be applied.
-     */
-    private boolean calculateFinalCrop(Rect finalClipRect) {
-        final WindowState w = mWin;
-        final DisplayContent displayContent = w.getDisplayContent();
-        finalClipRect.setEmpty();
-
-        if (displayContent == null) {
-            return false;
-        }
-
-        if (!shouldCropToStackBounds() || !useFinalClipRect()) {
-            return false;
-        }
-
-        // Task is non-null per shouldCropToStackBounds
-        final TaskStack stack = w.getTask().mStack;
-        stack.getDimBounds(finalClipRect);
-
-        if (stack.getWindowConfiguration().tasksAreFloating()) {
-            w.expandForSurfaceInsets(finalClipRect);
-        }
-
-        return true;
-    }
-
-    /**
      * Calculate the window-space crop rect and fill clipRect.
      * @return true if clipRect has been filled otherwise, no window space crop should be applied.
      */
@@ -900,9 +828,6 @@
         // so we need to translate to match the actual surface coordinates.
         clipRect.offset(w.mAttrs.surfaceInsets.left, w.mAttrs.surfaceInsets.top);
 
-        if (!useFinalClipRect()) {
-            adjustCropToStackBounds(clipRect, isFreeformResizing);
-        }
         if (DEBUG_WINDOW_CROP) Slog.d(TAG,
                 "win=" + w + " Clip rect after stack adjustment=" + clipRect);
 
@@ -911,9 +836,9 @@
         return true;
     }
 
-    private void applyCrop(Rect clipRect, Rect finalClipRect, boolean recoveringMemory) {
+    private void applyCrop(Rect clipRect, boolean recoveringMemory) {
         if (DEBUG_WINDOW_CROP) Slog.d(TAG, "applyCrop: win=" + mWin
-                + " clipRect=" + clipRect + " finalClipRect=" + finalClipRect);
+                + " clipRect=" + clipRect);
         if (clipRect != null) {
             if (!clipRect.equals(mLastClipRect)) {
                 mLastClipRect.set(clipRect);
@@ -922,93 +847,6 @@
         } else {
             mSurfaceController.clearCropInTransaction(recoveringMemory);
         }
-
-        if (finalClipRect == null) {
-            finalClipRect = mService.mTmpRect;
-            finalClipRect.setEmpty();
-        }
-        if (!finalClipRect.equals(mLastFinalClipRect)) {
-            mLastFinalClipRect.set(finalClipRect);
-            mSurfaceController.setFinalCropInTransaction(finalClipRect);
-            if (mDestroyPreservedSurfaceUponRedraw && mPendingDestroySurface != null) {
-                mPendingDestroySurface.setFinalCropInTransaction(finalClipRect);
-            }
-        }
-    }
-
-    private int resolveStackClip() {
-        // App animation overrides window animation stack clip mode.
-        if (mAppAnimator != null && mAppAnimator.animation != null) {
-            return mAppAnimator.getStackClip();
-        } else {
-            return STACK_CLIP_AFTER_ANIM;
-        }
-    }
-
-    private boolean shouldCropToStackBounds() {
-        final WindowState w = mWin;
-        final DisplayContent displayContent = w.getDisplayContent();
-        if (displayContent != null && !displayContent.isDefaultDisplay) {
-            // There are some windows that live on other displays while their app and main window
-            // live on the default display (e.g. casting...). We don't want to crop this windows
-            // to the stack bounds which is only currently supported on the default display.
-            // TODO(multi-display): Need to support cropping to stack bounds on other displays
-            // when we have stacks on other displays.
-            return false;
-        }
-
-        final Task task = w.getTask();
-        if (task == null || !task.cropWindowsToStackBounds()) {
-            return false;
-        }
-
-        final int stackClip = resolveStackClip();
-
-        // It's animating and we don't want to clip it to stack bounds during animation - abort.
-        if (isAnimationSet() && stackClip == STACK_CLIP_NONE) {
-            return false;
-        }
-        return true;
-    }
-
-    private void adjustCropToStackBounds(Rect clipRect,
-            boolean isFreeformResizing) {
-        final WindowState w = mWin;
-
-        if (!shouldCropToStackBounds()) {
-            return;
-        }
-
-        final TaskStack stack = w.getTask().mStack;
-        stack.getDimBounds(mTmpStackBounds);
-        final Rect surfaceInsets = w.getAttrs().surfaceInsets;
-        // When we resize we use the big surface approach, which means we can't trust the
-        // window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid
-        // hardcoding it, we use surface coordinates.
-        final int frameX = isFreeformResizing ? (int) mSurfaceController.getX() :
-                w.mFrame.left + mWin.mXOffset - surfaceInsets.left;
-        final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
-                w.mFrame.top + mWin.mYOffset - surfaceInsets.top;
-
-        // We need to do some acrobatics with surface position, because their clip region is
-        // relative to the inside of the surface, but the stack bounds aren't.
-        final WindowConfiguration winConfig = w.getWindowConfiguration();
-        if (winConfig.hasWindowShadow() && !winConfig.canResizeTask()) {
-                // The windows in this stack display drop shadows and the fill the entire stack
-                // area. Adjust the stack bounds we will use to cropping take into account the
-                // offsets we use to display the drop shadow so it doesn't get cropped.
-                mTmpStackBounds.inset(-surfaceInsets.left, -surfaceInsets.top,
-                        -surfaceInsets.right, -surfaceInsets.bottom);
-        }
-
-        clipRect.left = Math.max(0,
-                Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
-        clipRect.top = Math.max(0,
-                Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
-        clipRect.right = Math.max(0,
-                Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
-        clipRect.bottom = Math.max(0,
-                Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
     }
 
     void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
@@ -1053,13 +891,10 @@
         // updates until a resize occurs.
         mService.markForSeamlessRotation(w, w.mSeamlesslyRotated && !mSurfaceResized);
 
-        Rect clipRect = null, finalClipRect = null;
+        Rect clipRect = null;
         if (calculateCrop(mTmpClipRect)) {
             clipRect = mTmpClipRect;
         }
-        if (calculateFinalCrop(mTmpFinalClipRect)) {
-            finalClipRect = mTmpFinalClipRect;
-        }
 
         float surfaceWidth = mSurfaceController.getWidth();
         float surfaceHeight = mSurfaceController.getHeight();
@@ -1124,7 +959,6 @@
                 // Always clip to the stack bounds since the surface can be larger with the current
                 // scale
                 clipRect = null;
-                finalClipRect = mTmpStackBounds;
             } else {
                 // We want to calculate the scaling based on the content area, not based on
                 // the entire surface, so that we scale in sync with windows that don't have insets.
@@ -1135,7 +969,6 @@
                 // expose the whole window in buffer space, and not risk extending
                 // past where the system would have cropped us
                 clipRect = null;
-                finalClipRect = null;
             }
 
             // In the case of ForceScaleToStack we scale entire tasks together,
@@ -1183,7 +1016,7 @@
         }
 
         if (!w.mSeamlesslyRotated) {
-            applyCrop(clipRect, finalClipRect, recoveringMemory);
+            applyCrop(clipRect, recoveringMemory);
             mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale,
                     mDtDx * w.mVScale * mExtraVScale,
                     mDtDy * w.mHScale * mExtraHScale,
@@ -1381,7 +1214,7 @@
             mService.openSurfaceTransaction();
             mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left,
                     mWin.mFrame.top + top, false);
-            applyCrop(null, null, false);
+            applyCrop(null, false);
         } catch (RuntimeException e) {
             Slog.w(TAG, "Error positioning surface of " + mWin
                     + " pos=(" + left + "," + top + ")", e);
@@ -1550,6 +1383,8 @@
         if (mSurfaceController != null) {
             mSurfaceController.writeToProto(proto, SURFACE);
         }
+        proto.write(DRAW_STATE, mDrawState);
+        mSystemDecorRect.writeToProto(proto, SYSTEM_DECOR_RECT);
         proto.end(token);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index d8e7457..a512fdf 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManagerInternal.APP_TRANSITION_SNAPSHOT;
 import static android.app.ActivityManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
 import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
@@ -43,28 +44,19 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 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_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
 import static com.android.server.wm.WindowManagerService.H.REPORT_WINDOWS_CHANGE;
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
-import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
 
-import android.content.res.Configuration;
-import android.graphics.GraphicBuffer;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Binder;
 import android.os.Debug;
 import android.os.Trace;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseIntArray;
 import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.Animation;
@@ -97,9 +89,6 @@
     static final int SET_TURN_ON_SCREEN                 = 1 << 4;
     static final int SET_WALLPAPER_ACTION_PENDING       = 1 << 5;
 
-    private final Rect mTmpStartRect = new Rect();
-    private final Rect mTmpContentRect = new Rect();
-
     private boolean mTraversalScheduled;
     private int mDeferDepth = 0;
 
@@ -352,26 +341,28 @@
             animLp = null;
         }
 
-        processApplicationsAnimatingInPlace(transit);
+        final int layoutRedo;
+        mService.mSurfaceAnimationRunner.deferStartingAnimations();
+        try {
+            processApplicationsAnimatingInPlace(transit);
 
-        mTmpLayerAndToken.token = null;
-        handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken);
-        final AppWindowToken topClosingApp = mTmpLayerAndToken.token;
-        final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, voiceInteraction);
+            mTmpLayerAndToken.token = null;
+            handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken);
+            final AppWindowToken topClosingApp = mTmpLayerAndToken.token;
+            final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp,
+                    voiceInteraction);
 
-        mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
+            mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
 
-        final AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ?  null :
-                topOpeningApp.mAppAnimator;
-        final AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null :
-                topClosingApp.mAppAnimator;
-
-        final int flags = mService.mAppTransition.getTransitFlags();
-        int layoutRedo = mService.mAppTransition.goodToGo(transit, openingAppAnimator,
-                closingAppAnimator, mService.mOpeningApps, mService.mClosingApps);
-        handleNonAppWindowsInTransition(transit, flags);
-        mService.mAppTransition.postAnimationCallback();
-        mService.mAppTransition.clear();
+            final int flags = mService.mAppTransition.getTransitFlags();
+            layoutRedo = mService.mAppTransition.goodToGo(transit, topOpeningApp,
+                    topClosingApp, mService.mOpeningApps, mService.mClosingApps);
+            handleNonAppWindowsInTransition(transit, flags);
+            mService.mAppTransition.postAnimationCallback();
+            mService.mAppTransition.clear();
+        } finally {
+            mService.mSurfaceAnimationRunner.continueStartingAnimations();
+        }
 
         mService.mTaskSnapshotController.onTransitionStarting();
 
@@ -405,14 +396,8 @@
         final int appsCount = mService.mOpeningApps.size();
         for (int i = 0; i < appsCount; i++) {
             AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
-            final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
 
-            if (!appAnimator.usingTransferredAnimation) {
-                appAnimator.clearThumbnail();
-                appAnimator.setNullAnimation();
-            }
-
             if (!wtoken.setVisibility(animLp, true, transit, false, voiceInteraction)){
                 // This token isn't going to be animating. Add it to the list of tokens to
                 // be notified of app transition complete since the notification will not be
@@ -421,19 +406,16 @@
             }
             wtoken.updateReportedVisibilityLocked();
             wtoken.waitingToShow = false;
-            wtoken.setAllAppWinAnimators();
-
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
             mService.openSurfaceTransaction();
             try {
-                mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
+                wtoken.showAllWindowsLocked();
             } finally {
                 mService.closeSurfaceTransaction("handleAppTransitionReadyLocked");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                         "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
             }
-            mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
             if (animLp != null) {
                 final int layer = wtoken.getHighestAnimLayer();
@@ -443,7 +425,9 @@
                 }
             }
             if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
-                createThumbnailAppAnimator(transit, wtoken);
+                wtoken.attachThumbnailAnimation();
+            } else if (mService.mAppTransition.isNextAppTransitionOpenCrossProfileApps()) {
+                wtoken.attachCrossProfileAppsThumbnailAnimation();
             }
         }
         return topOpeningApp;
@@ -456,18 +440,11 @@
         for (int i = 0; i < appsCount; i++) {
             AppWindowToken wtoken = mService.mClosingApps.valueAt(i);
 
-            final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
-            appAnimator.clearThumbnail();
-            appAnimator.setNullAnimation();
             // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not
             //       animating?
-            wtoken.setAllAppWinAnimators();
             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.
@@ -479,7 +456,6 @@
                     && wtoken.getController() != null) {
                 wtoken.getController().removeStartingWindow();
             }
-            mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
             if (animLp != null) {
                 int layer = wtoken.getHighestAnimLayer();
@@ -489,7 +465,7 @@
                 }
             }
             if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) {
-                createThumbnailAppAnimator(transit, wtoken);
+                wtoken.attachThumbnailAnimation();
             }
         }
     }
@@ -666,102 +642,16 @@
             final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow();
             if (win != null) {
                 final AppWindowToken wtoken = win.mAppToken;
-                final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
                 if (DEBUG_APP_TRANSITIONS)
                     Slog.v(TAG, "Now animating app in place " + wtoken);
-                appAnimator.clearThumbnail();
-                appAnimator.setNullAnimation();
-                mService.updateTokenInPlaceLocked(wtoken, transit);
+                wtoken.cancelAnimation();
+                wtoken.applyAnimationLocked(null, transit, false, false);
                 wtoken.updateReportedVisibilityLocked();
-                wtoken.setAllAppWinAnimators();
-                mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
-                mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
+                wtoken.showAllWindowsLocked();
             }
         }
     }
 
-    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken) {
-        AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
-        if (openingAppAnimator == null || openingAppAnimator.animation == null) {
-            return;
-        }
-        final int taskId = appToken.getTask().mTaskId;
-        final GraphicBuffer thumbnailHeader =
-                mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
-        if (thumbnailHeader == null) {
-            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
-            return;
-        }
-        // This thumbnail animation is very special, we need to have
-        // an extra surface with the thumbnail included with the animation.
-        Rect dirty = new Rect(0, 0, thumbnailHeader.getWidth(), thumbnailHeader.getHeight());
-        try {
-            // TODO(multi-display): support other displays
-            final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
-            final Display display = displayContent.getDisplay();
-            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-
-            // Create a new surface for the thumbnail
-            WindowState window = appToken.findMainWindow();
-            final SurfaceControl surfaceControl = appToken.makeSurface()
-                    .setName("thumbnail anim")
-                    .setSize(dirty.width(), dirty.height())
-                    .setFormat(PixelFormat.TRANSLUCENT)
-                    .setMetadata(appToken.windowType,
-                            window != null ? window.mOwnerUid : Binder.getCallingUid())
-                    .build();
-
-            if (SHOW_TRANSACTIONS) {
-                Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
-            }
-
-            // Transfer the thumbnail to the surface
-            Surface drawSurface = new Surface();
-            drawSurface.copyFrom(surfaceControl);
-            drawSurface.attachAndQueueBuffer(thumbnailHeader);
-            drawSurface.release();
-
-            // Get the thumbnail animation
-            Animation anim;
-            if (mService.mAppTransition.isNextThumbnailTransitionAspectScaled()) {
-                // If this is a multi-window scenario, we use the windows frame as
-                // destination of the thumbnail header animation. If this is a full screen
-                // window scenario, we use the whole display as the target.
-                WindowState win = appToken.findMainWindow();
-                Rect appRect = win != null ? win.getContentFrameLw() :
-                        new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
-                Rect insets = win != null ? win.mContentInsets : null;
-                final Configuration displayConfig = displayContent.getConfiguration();
-                // For the new aspect-scaled transition, we want it to always show
-                // above the animating opening/closing window, and we want to
-                // synchronize its thumbnail surface with the surface for the
-                // open/close animation (only on the way down)
-                anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
-                        insets, thumbnailHeader, taskId, displayConfig.uiMode,
-                        displayConfig.orientation);
-                openingAppAnimator.deferThumbnailDestruction =
-                        !mService.mAppTransition.isNextThumbnailTransitionScaleUp();
-            } else {
-                anim = mService.mAppTransition.createThumbnailScaleAnimationLocked(
-                        displayInfo.appWidth, displayInfo.appHeight, transit, thumbnailHeader);
-            }
-            anim.restrictDuration(MAX_ANIMATION_DURATION);
-            anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
-
-            openingAppAnimator.thumbnail = surfaceControl;
-            openingAppAnimator.thumbnailAnimation = anim;
-            mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
-
-            // We parent the thumbnail to the app token, and just place it
-            // on top of anything else in the app token.
-            surfaceControl.setLayer(Integer.MAX_VALUE);
-        } catch (Surface.OutOfResourcesException e) {
-            Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
-                    + dirty.width() + " h=" + dirty.height(), e);
-            openingAppAnimator.clearThumbnail();
-        }
-    }
-
     void requestTraversal() {
         if (!mTraversalScheduled) {
             mTraversalScheduled = true;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5bcf59c..bad9bf5 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -63,7 +63,7 @@
     boolean paused = false;
 
     // Should this token's windows be hidden?
-    boolean hidden;
+    private boolean mHidden;
 
     // Temporary for finding which tokens no longer have visible windows.
     boolean hasVisible;
@@ -112,6 +112,16 @@
         onDisplayChanged(dc);
     }
 
+    void setHidden(boolean hidden) {
+        if (hidden != mHidden) {
+            mHidden = hidden;
+        }
+    }
+
+    boolean isHidden() {
+        return mHidden;
+    }
+
     void removeAllWindowsIfPossible() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowState win = mChildren.get(i);
@@ -130,7 +140,7 @@
         // This token is exiting, so allow it to be removed when it no longer contains any windows.
         mPersistOnEmpty = false;
 
-        if (hidden) {
+        if (mHidden) {
             return;
         }
 
@@ -146,7 +156,7 @@
             changed |= win.onSetAppExiting();
         }
 
-        hidden = true;
+        setHidden(true);
 
         if (changed) {
             mService.mWindowPlacerLocked.performSurfacePlacement();
@@ -189,11 +199,6 @@
         return mChildren.isEmpty();
     }
 
-    // Used by AppWindowToken.
-    int getAnimLayerAdjustment() {
-        return 0;
-    }
-
     WindowState getReplacingWindow() {
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final WindowState win = mChildren.get(i);
@@ -274,10 +279,11 @@
         proto.end(token);
     }
 
-    void dump(PrintWriter pw, String prefix) {
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         pw.print(prefix); pw.print("windows="); pw.println(mChildren);
         pw.print(prefix); pw.print("windowType="); pw.print(windowType);
-                pw.print(" hidden="); pw.print(hidden);
+                pw.print(" hidden="); pw.print(mHidden);
                 pw.print(" hasVisible="); pw.println(hasVisible);
         if (waitingToShow || sendingToBottom) {
             pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow);
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
index 79f482c6..62bab07 100644
--- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -94,6 +94,9 @@
         j_str = env->NewString((jchar*)byteBuffer, numUSC2Bytes/2);
         free(byteBuffer);
     }
+
+    usb_device_close(device);
+
     return j_str;
 }
 
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 246bd42..67bad0f 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -48,6 +48,7 @@
 static jmethodID method_reportNmea;
 static jmethodID method_setEngineCapabilities;
 static jmethodID method_setGnssYearOfHardware;
+static jmethodID method_setGnssHardwareModelName;
 static jmethodID method_xtraDownloadRequest;
 static jmethodID method_reportNiNotification;
 static jmethodID method_requestRefLocation;
@@ -373,12 +374,11 @@
 Return<void> GnssCallback::gnssNameCb(const android::hardware::hidl_string& name) {
     ALOGD("%s: name=%s\n", __func__, name.c_str());
 
-    // TODO(b/38003769): build Java code to connect to below code
-    /*
     JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareName, name);
+    jstring jstringName = env->NewStringUTF(name.c_str());
+    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareModelName, jstringName);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    */
+
     return Void();
 }
 
@@ -1031,6 +1031,8 @@
     method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
     method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V");
     method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
+    method_setGnssHardwareModelName = env->GetMethodID(clazz, "setGnssHardwareModelName",
+            "(Ljava/lang/String;)V");
     method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");
     method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification",
             "(IIIIILjava/lang/String;Ljava/lang/String;II)V");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index e55d4ea..c1e95eb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -67,7 +67,8 @@
     public void transferOwner(ComponentName admin, ComponentName target, PersistableBundle bundle) {}
 
     public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm,
-            ParcelableKeyGenParameterSpec keySpec, KeymasterCertificateChain attestationChain) {
+            ParcelableKeyGenParameterSpec keySpec, int idAttestationFlags,
+            KeymasterCertificateChain attestationChain) {
         return false;
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e5351b4..f0681e9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -45,6 +45,10 @@
 import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES;
 import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
 import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
@@ -217,8 +221,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map.Entry;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -5052,17 +5058,82 @@
         return false;
     }
 
+    private void enforceIsDeviceOwnerOrCertInstallerOfDeviceOwner(
+            ComponentName who, String callerPackage, int callerUid) throws SecurityException {
+        if (who == null) {
+            if (!mOwners.hasDeviceOwner()) {
+                throw new SecurityException("Not in Device Owner mode.");
+            }
+            if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) {
+                throw new SecurityException("Caller not from device owner user");
+            }
+            if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) {
+                throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() +
+                        "has no permission to generate keys.");
+            }
+        } else {
+            // Caller provided - check it is the device owner.
+            synchronized (this) {
+                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public static int[] translateIdAttestationFlags(
+            int idAttestationFlags) {
+        Map<Integer, Integer> idTypeToAttestationFlag = new HashMap();
+        idTypeToAttestationFlag.put(ID_TYPE_SERIAL, AttestationUtils.ID_TYPE_SERIAL);
+        idTypeToAttestationFlag.put(ID_TYPE_IMEI, AttestationUtils.ID_TYPE_IMEI);
+        idTypeToAttestationFlag.put(ID_TYPE_MEID, AttestationUtils.ID_TYPE_MEID);
+
+        int numFlagsSet = Integer.bitCount(idAttestationFlags);
+        // No flags are set - return null to indicate no device ID attestation information should
+        // be included in the attestation record.
+        if (numFlagsSet == 0) {
+            return null;
+        }
+
+        // If the ID_TYPE_BASE_INFO is set, make sure that a non-null array is returned, even if
+        // no other flag is set. That will lead to inclusion of general device make data in the
+        // attestation record, but no specific device identifiers.
+        if ((idAttestationFlags & ID_TYPE_BASE_INFO) != 0) {
+            numFlagsSet -= 1;
+            idAttestationFlags = idAttestationFlags & (~ID_TYPE_BASE_INFO);
+        }
+
+        int[] attestationUtilsFlags = new int[numFlagsSet];
+        int i = 0;
+        for (Integer idType: idTypeToAttestationFlag.keySet()) {
+            if ((idType & idAttestationFlags) != 0) {
+                attestationUtilsFlags[i++] = idTypeToAttestationFlag.get(idType);
+            }
+        }
+
+        return attestationUtilsFlags;
+    }
+
     @Override
     public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm,
             ParcelableKeyGenParameterSpec parcelableKeySpec,
+            int idAttestationFlags,
             KeymasterCertificateChain attestationChain) {
-        enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
-                DELEGATION_CERT_INSTALL);
+        // Get attestation flags, if any.
+        final int[] attestationUtilsFlags = translateIdAttestationFlags(idAttestationFlags);
+        final boolean deviceIdAttestationRequired = attestationUtilsFlags != null;
+        final int callingUid = mInjector.binderGetCallingUid();
+
+        if (deviceIdAttestationRequired && attestationUtilsFlags.length > 0) {
+            enforceIsDeviceOwnerOrCertInstallerOfDeviceOwner(who, callerPackage, callingUid);
+        } else {
+            enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_CERT_INSTALL);
+        }
         final KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec();
-        if (TextUtils.isEmpty(keySpec.getKeystoreAlias())) {
+        final String alias = keySpec.getKeystoreAlias();
+        if (TextUtils.isEmpty(alias)) {
             throw new IllegalArgumentException("Empty alias provided.");
         }
-        final String alias = keySpec.getKeystoreAlias();
         // As the caller will be granted access to the key, ensure no UID was specified, as
         // it will not have the desired effect.
         if (keySpec.getUid() != KeyStore.UID_SELF) {
@@ -5070,7 +5141,10 @@
             return false;
         }
 
-        final int callingUid = mInjector.binderGetCallingUid();
+        if (deviceIdAttestationRequired && (keySpec.getAttestationChallenge() == null)) {
+            throw new IllegalArgumentException(
+                    "Requested Device ID attestation but challenge is empty.");
+        }
 
         final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
         final long id = mInjector.binderClearCallingIdentity();
@@ -5103,7 +5177,7 @@
                 final byte[] attestationChallenge = keySpec.getAttestationChallenge();
                 if (attestationChallenge != null) {
                     final boolean attestationResult = keyChain.attestKey(
-                            alias, attestationChallenge, attestationChain);
+                            alias, attestationChallenge, attestationUtilsFlags, attestationChain);
                     if (!attestationResult) {
                         Log.e(LOG_TAG, String.format(
                                 "Attestation for %s failed, deleting key.", alias));
@@ -7116,13 +7190,8 @@
             return;
         }
 
-        final int userId = mInjector.userHandleGetCallingUserId();
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            if (!isUserAffiliatedWithDeviceLocked(userId)) {
-                throw new SecurityException("Admin " + who +
-                        " is neither the device owner or affiliated user's profile owner.");
-            }
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
             long token = mInjector.binderClearCallingIdentity();
             try {
                 mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null);
@@ -9589,6 +9658,9 @@
                         " is neither the device owner or affiliated user's profile owner.");
             }
         }
+        if (isManagedProfile(userId)) {
+            throw new SecurityException("Managed profile cannot disable keyguard");
+        }
 
         long ident = mInjector.binderClearCallingIdentity();
         try {
@@ -9615,6 +9687,9 @@
                 throw new SecurityException("Admin " + who +
                         " is neither the device owner or affiliated user's profile owner.");
             }
+            if (isManagedProfile(userId)) {
+                throw new SecurityException("Managed profile cannot disable status bar");
+            }
             DevicePolicyData policy = getUserData(userId);
             if (policy.mStatusBarDisabled != disabled) {
                 boolean isLockTaskMode = false;
@@ -11802,10 +11877,14 @@
 
         final long id = mInjector.binderClearCallingIdentity();
         try {
-            //STOPSHIP add support for COMP, DO, edge cases when device is rebooted/work mode off,
+            //STOPSHIP add support for COMP, edge cases when device is rebooted/work mode off,
             //transfer callbacks and broadcast
-            if (isProfileOwner(admin, callingUserId)) {
-                transferProfileOwner(admin, target, callingUserId);
+            synchronized (this) {
+                if (isProfileOwner(admin, callingUserId)) {
+                    transferProfileOwnerLocked(admin, target, callingUserId);
+                } else if (isDeviceOwner(admin, callingUserId)) {
+                    transferDeviceOwnerLocked(admin, target, callingUserId);
+                }
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
@@ -11815,15 +11894,25 @@
     /**
      * Transfers the profile owner for user with id profileOwnerUserId from admin to target.
      */
-    private void transferProfileOwner(ComponentName admin, ComponentName target,
+    private void transferProfileOwnerLocked(ComponentName admin, ComponentName target,
             int profileOwnerUserId) {
-        synchronized (this) {
-            transferActiveAdminUncheckedLocked(target, admin, profileOwnerUserId);
-            mOwners.transferProfileOwner(target, profileOwnerUserId);
-            Slog.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId);
-            mOwners.writeProfileOwner(profileOwnerUserId);
-            mDeviceAdminServiceController.startServiceForOwner(
-                    target.getPackageName(), profileOwnerUserId, "transfer-profile-owner");
-        }
+        transferActiveAdminUncheckedLocked(target, admin, profileOwnerUserId);
+        mOwners.transferProfileOwner(target, profileOwnerUserId);
+        Slog.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId);
+        mOwners.writeProfileOwner(profileOwnerUserId);
+        mDeviceAdminServiceController.startServiceForOwner(
+                target.getPackageName(), profileOwnerUserId, "transfer-profile-owner");
+    }
+
+    /**
+     * Transfers the device owner for user with id userId from admin to target.
+     */
+    private void transferDeviceOwnerLocked(ComponentName admin, ComponentName target, int userId) {
+        transferActiveAdminUncheckedLocked(target, admin, userId);
+        mOwners.transferDeviceOwner(target);
+        Slog.i(LOG_TAG, "Device owner set: " + target + " on user " + userId);
+        mOwners.writeDeviceOwner();
+        mDeviceAdminServiceController.startServiceForOwner(
+                target.getPackageName(), userId, "transfer-device-owner");
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 9042a8d..2a23888 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -288,6 +288,17 @@
         }
     }
 
+    void transferDeviceOwner(ComponentName target) {
+        synchronized (mLock) {
+            // We don't set a name because it's not used anyway.
+            // See DevicePolicyManagerService#getDeviceOwnerName
+            mDeviceOwner = new OwnerInfo(null, target,
+                    mDeviceOwner.userRestrictionsMigrated, mDeviceOwner.remoteBugreportUri,
+                    mDeviceOwner.remoteBugreportHash);
+            pushToPackageManagerLocked();
+        }
+    }
+
     ComponentName getProfileOwnerComponent(int userId) {
         synchronized (mLock) {
             OwnerInfo profileOwner = mProfileOwners.get(userId);
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java
new file mode 100644
index 0000000..c397f23
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.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.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderClasses;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 26)
+@SystemLoaderClasses({BackupManagerConstants.class})
+@Presubmit
+public class BackupManagerConstantsTest {
+    private static final String PACKAGE_NAME = "some.package.name";
+    private static final String ANOTHER_PACKAGE_NAME = "another.package.name";
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testDefaultValues() throws Exception {
+        final Context context = RuntimeEnvironment.application.getApplicationContext();
+        final Handler handler = new Handler();
+
+        Settings.Secure.putString(
+                context.getContentResolver(), Settings.Secure.BACKUP_MANAGER_CONSTANTS, null);
+
+        final BackupManagerConstants constants =
+                new BackupManagerConstants(handler, context.getContentResolver());
+
+        assertThat(constants.getKeyValueBackupIntervalMilliseconds())
+                .isEqualTo(4 * AlarmManager.INTERVAL_HOUR);
+        assertThat(constants.getKeyValueBackupFuzzMilliseconds()).isEqualTo(10 * 60 * 1000);
+        assertThat(constants.getKeyValueBackupRequireCharging()).isEqualTo(true);
+        assertThat(constants.getKeyValueBackupRequiredNetworkType()).isEqualTo(1);
+
+        assertThat(constants.getFullBackupIntervalMilliseconds())
+                .isEqualTo(24 * AlarmManager.INTERVAL_HOUR);
+        assertThat(constants.getFullBackupRequireCharging()).isEqualTo(true);
+        assertThat(constants.getFullBackupRequiredNetworkType()).isEqualTo(2);
+        assertThat(constants.getBackupFinishedNotificationReceivers()).isEqualTo(new String[0]);
+    }
+
+    @Test
+    public void testParseNotificationReceivers() throws Exception {
+        final Context context = RuntimeEnvironment.application.getApplicationContext();
+        final Handler handler = new Handler();
+
+        final String recieversSetting =
+                "backup_finished_notification_receivers="
+                        + PACKAGE_NAME
+                        + ':'
+                        + ANOTHER_PACKAGE_NAME;
+        Settings.Secure.putString(
+                context.getContentResolver(),
+                Settings.Secure.BACKUP_MANAGER_CONSTANTS,
+                recieversSetting);
+
+        final BackupManagerConstants constants =
+                new BackupManagerConstants(handler, context.getContentResolver());
+
+        assertThat(constants.getBackupFinishedNotificationReceivers())
+                .isEqualTo(new String[] {PACKAGE_NAME, ANOTHER_PACKAGE_NAME});
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
new file mode 100644
index 0000000..f9ebd28
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+import static org.testng.Assert.expectThrows;
+
+import android.app.backup.BackupManager;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.HandlerThread;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+
+import com.android.server.backup.testing.ShadowAppBackupUtils;
+import com.android.server.backup.testing.TransportTestUtils;
+import com.android.server.backup.testing.TransportTestUtils.TransportData;
+import com.android.server.backup.transport.TransportNotRegisteredException;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderClasses;
+
+import org.junit.After;
+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 org.robolectric.shadows.ShadowContextWrapper;
+import org.robolectric.shadows.ShadowLog;
+import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowSettings;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(
+    manifest = Config.NONE,
+    sdk = 26,
+    shadows = {ShadowAppBackupUtils.class}
+)
+@SystemLoaderClasses({RefactoredBackupManagerService.class, TransportManager.class})
+@Presubmit
+public class BackupManagerServiceRoboTest {
+    private static final String TAG = "BMSTest";
+    private static final String TRANSPORT_NAME =
+            "com.google.android.gms/.backup.BackupTransportService";
+
+    @Mock private TransportManager mTransportManager;
+    private HandlerThread mBackupThread;
+    private ShadowLooper mShadowBackupLooper;
+    private File mBaseStateDir;
+    private File mDataDir;
+    private RefactoredBackupManagerService mBackupManagerService;
+    private ShadowContextWrapper mShadowContext;
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mBackupThread = new HandlerThread("backup-test");
+        mBackupThread.setUncaughtExceptionHandler(
+                (t, e) -> ShadowLog.e(TAG, "Uncaught exception in test thread " + t.getName(), e));
+        mBackupThread.start();
+        mShadowBackupLooper = shadowOf(mBackupThread.getLooper());
+
+        ContextWrapper context = RuntimeEnvironment.application;
+        mContext = context;
+        mShadowContext = shadowOf(context);
+
+        File cacheDir = mContext.getCacheDir();
+        mBaseStateDir = new File(cacheDir, "base_state_dir");
+        mDataDir = new File(cacheDir, "data_dir");
+
+        mBackupManagerService =
+                new RefactoredBackupManagerService(
+                        mContext,
+                        new Trampoline(mContext),
+                        mBackupThread,
+                        mBaseStateDir,
+                        mDataDir,
+                        mTransportManager);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mBackupThread.quit();
+        ShadowAppBackupUtils.reset();
+    }
+
+    /* Tests for destination string */
+
+    @Test
+    public void testDestinationString() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+                .thenReturn("destinationString");
+
+        String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME);
+
+        assertThat(destination).isEqualTo("destinationString");
+    }
+
+    @Test
+    public void testDestinationString_whenTransportNotRegistered() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+                .thenThrow(TransportNotRegisteredException.class);
+
+        String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME);
+
+        assertThat(destination).isNull();
+    }
+
+    @Test
+    public void testDestinationString_withoutPermission() throws Exception {
+        mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+                .thenThrow(TransportNotRegisteredException.class);
+
+        expectThrows(
+                SecurityException.class,
+                () -> mBackupManagerService.getDestinationString(TRANSPORT_NAME));
+    }
+
+    /* Tests for app eligibility */
+
+    @Test
+    public void testIsAppEligibleForBackup_whenAppEligible() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        TransportData transport =
+                TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> true;
+
+        boolean result = mBackupManagerService.isAppEligibleForBackup("app.package");
+
+        assertThat(result).isTrue();
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(transport.transportClientMock), any());
+    }
+
+    @Test
+    public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false;
+
+        boolean result = mBackupManagerService.isAppEligibleForBackup("app.package");
+
+        assertThat(result).isFalse();
+    }
+
+    @Test
+    public void testIsAppEligibleForBackup_withoutPermission() throws Exception {
+        mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+        TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+
+        expectThrows(
+                SecurityException.class,
+                () -> mBackupManagerService.isAppEligibleForBackup("app.package"));
+    }
+
+    @Test
+    public void testFilterAppsEligibleForBackup() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        TransportData transport =
+                TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        Map<String, Boolean> packagesMap = new HashMap<>();
+        packagesMap.put("package.a", true);
+        packagesMap.put("package.b", false);
+        ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = packagesMap::get;
+        String[] packages = packagesMap.keySet().toArray(new String[packagesMap.size()]);
+
+        String[] filtered = mBackupManagerService.filterAppsEligibleForBackup(packages);
+
+        assertThat(filtered).asList().containsExactly("package.a");
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(transport.transportClientMock), any());
+    }
+
+    @Test
+    public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false;
+
+        String[] filtered =
+                mBackupManagerService.filterAppsEligibleForBackup(
+                        new String[] {"package.a", "package.b"});
+
+        assertThat(filtered).isEmpty();
+    }
+
+    @Test
+    public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception {
+        mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+        TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+
+        expectThrows(
+                SecurityException.class,
+                () ->
+                        mBackupManagerService.filterAppsEligibleForBackup(
+                                new String[] {"package.a", "package.b"}));
+    }
+
+    /* Tests for select transport */
+
+    private TransportData mNewTransport;
+    private TransportData mOldTransport;
+    private ComponentName mNewTransportComponent;
+    private ISelectBackupTransportCallback mCallback;
+
+    private void setUpForSelectTransport() throws Exception {
+        List<TransportData> transports =
+                TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES);
+        mNewTransport = transports.get(0);
+        mNewTransportComponent = mNewTransport.transportClientMock.getTransportComponent();
+        mOldTransport = transports.get(1);
+        when(mTransportManager.selectTransport(eq(mNewTransport.transportName)))
+                .thenReturn(mOldTransport.transportName);
+    }
+
+    @Test
+    public void testSelectBackupTransport() throws Exception {
+        setUpForSelectTransport();
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+
+        String oldTransport =
+                mBackupManagerService.selectBackupTransport(mNewTransport.transportName);
+
+        assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
+        assertThat(oldTransport).isEqualTo(mOldTransport.transportName);
+    }
+
+    @Test
+    public void testSelectBackupTransport_withoutPermission() throws Exception {
+        setUpForSelectTransport();
+        mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+
+        expectThrows(
+                SecurityException.class,
+                () -> mBackupManagerService.selectBackupTransport(mNewTransport.transportName));
+    }
+
+    @Test
+    public void testSelectBackupTransportAsync() throws Exception {
+        setUpForSelectTransport();
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
+                .thenReturn(BackupManager.SUCCESS);
+        ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
+
+        mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+
+        mShadowBackupLooper.runToEndOfTasks();
+        assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
+        verify(callback).onSuccess(eq(mNewTransport.transportName));
+    }
+
+    @Test
+    public void testSelectBackupTransportAsync_whenRegistrationFails() throws Exception {
+        setUpForSelectTransport();
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
+                .thenReturn(BackupManager.ERROR_TRANSPORT_UNAVAILABLE);
+        ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
+
+        mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+
+        mShadowBackupLooper.runToEndOfTasks();
+        assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName);
+        verify(callback).onFailure(anyInt());
+    }
+
+    @Test
+    public void testSelectBackupTransportAsync_whenTransportGetsUnregistered() throws Exception {
+        TransportTestUtils.setUpTransports(
+                mTransportManager, new TransportData(TRANSPORT_NAME, null, null));
+        ComponentName newTransportComponent =
+                TransportTestUtils.transportComponentName(TRANSPORT_NAME);
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.registerAndSelectTransport(eq(newTransportComponent)))
+                .thenReturn(BackupManager.SUCCESS);
+        ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
+
+        mBackupManagerService.selectBackupTransportAsync(newTransportComponent, callback);
+
+        mShadowBackupLooper.runToEndOfTasks();
+        assertThat(getSettingsTransport()).isNotEqualTo(TRANSPORT_NAME);
+        verify(callback).onFailure(anyInt());
+    }
+
+    @Test
+    public void testSelectBackupTransportAsync_withoutPermission() throws Exception {
+        setUpForSelectTransport();
+        mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+        ComponentName newTransportComponent =
+                mNewTransport.transportClientMock.getTransportComponent();
+
+        expectThrows(
+                SecurityException.class,
+                () ->
+                        mBackupManagerService.selectBackupTransportAsync(
+                                newTransportComponent, mock(ISelectBackupTransportCallback.class)));
+    }
+
+    private String getSettingsTransport() {
+        return ShadowSettings.ShadowSecure.getString(
+                mContext.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index ced9b1e..acd670f 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -18,15 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static junit.framework.Assert.fail;
-
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.robolectric.shadow.api.Shadow.extract;
 import static org.testng.Assert.expectThrows;
 
 import android.annotation.Nullable;
-import android.app.backup.BackupManager;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -42,7 +39,6 @@
 import com.android.server.backup.testing.ShadowContextImplForBackup;
 import com.android.server.backup.testing.ShadowPackageManagerForBackup;
 import com.android.server.backup.testing.TransportBoundListenerStub;
-import com.android.server.backup.testing.TransportReadyCallbackStub;
 import com.android.server.backup.transport.TransportClient;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 import com.android.server.testing.FrameworkRobolectricTestRunner;
@@ -58,7 +54,6 @@
 import org.robolectric.shadows.ShadowLog;
 import org.robolectric.shadows.ShadowLooper;
 import org.robolectric.shadows.ShadowPackageManager;
-import org.testng.Assert.ThrowingRunnable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -90,9 +85,6 @@
     private final TransportBoundListenerStub mTransportBoundListenerStub =
             new TransportBoundListenerStub(true);
 
-    private final TransportReadyCallbackStub mTransportReadyCallbackStub =
-            new TransportReadyCallbackStub();
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -469,59 +461,6 @@
     }
 
     @Test
-    public void ensureTransportReady_transportNotYetBound_callsListenerOnFailure()
-            throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Arrays.asList(mTransport1.componentName, mTransport2.componentName)),
-                mTransport1.name,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-
-        transportManager.ensureTransportReady(mTransport1.componentName,
-                mTransportReadyCallbackStub);
-
-        assertThat(mTransportReadyCallbackStub.getSuccessCalls()).isEmpty();
-        assertThat(mTransportReadyCallbackStub.getFailureCalls()).containsExactlyElementsIn(
-                Collections.singleton(
-                        BackupManager.ERROR_TRANSPORT_UNAVAILABLE));
-    }
-
-    @Test
-    public void ensureTransportReady_transportCannotBeBound_callsListenerOnFailure()
-            throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
-                        Collections.singletonList(mTransport1), mTransport1.name);
-
-        transportManager.ensureTransportReady(mTransport1.componentName,
-                mTransportReadyCallbackStub);
-
-        assertThat(mTransportReadyCallbackStub.getSuccessCalls()).isEmpty();
-        assertThat(mTransportReadyCallbackStub.getFailureCalls()).containsExactlyElementsIn(
-                Collections.singleton(
-                        BackupManager.ERROR_TRANSPORT_UNAVAILABLE));
-    }
-
-    @Test
-    public void ensureTransportReady_transportsAlreadyBound_callsListenerOnSuccess()
-            throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
-                        Collections.singletonList(mTransport1), mTransport1.name);
-
-        transportManager.ensureTransportReady(mTransport2.componentName,
-                mTransportReadyCallbackStub);
-
-        assertThat(mTransportReadyCallbackStub.getSuccessCalls()).containsExactlyElementsIn(
-                Collections.singleton(mTransport2.name));
-        assertThat(mTransportReadyCallbackStub.getFailureCalls()).isEmpty();
-    }
-
-    @Test
     public void getTransportClient_forRegisteredTransport_returnCorrectly() throws Exception {
         TransportManager transportManager =
                 createTransportManagerAndSetUpTransports(
diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
new file mode 100644
index 0000000..dfca901
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.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.server.backup.internal;
+
+import static android.app.backup.BackupTransport.TRANSPORT_ERROR;
+import static android.app.backup.BackupTransport.TRANSPORT_OK;
+
+import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAME;
+import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+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.app.AlarmManager;
+import android.app.Application;
+import android.app.PendingIntent;
+import android.app.backup.IBackupObserver;
+import android.os.DeadObjectException;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.testing.TransportTestUtils;
+import com.android.server.backup.testing.TransportTestUtils.TransportData;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderClasses;
+
+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 java.io.File;
+import java.util.List;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 26)
+@SystemLoaderClasses({PerformInitializeTaskTest.class, TransportManager.class})
+@Presubmit
+public class PerformInitializeTaskTest {
+    @Mock private RefactoredBackupManagerService mBackupManagerService;
+    @Mock private TransportManager mTransportManager;
+    @Mock private OnTaskFinishedListener mListener;
+    @Mock private IBackupTransport mTransport;
+    @Mock private IBackupObserver mObserver;
+    @Mock private AlarmManager mAlarmManager;
+    @Mock private PendingIntent mRunInitIntent;
+    private File mBaseStateDir;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        Application context = RuntimeEnvironment.application;
+        mBaseStateDir = new File(context.getCacheDir(), "base_state_dir");
+        assertThat(mBaseStateDir.mkdir()).isTrue();
+
+        when(mBackupManagerService.getAlarmManager()).thenReturn(mAlarmManager);
+        when(mBackupManagerService.getRunInitIntent()).thenReturn(mRunInitIntent);
+    }
+
+    @Test
+    public void testRun_callsTransportCorrectly() throws Exception {
+        setUpTransport(TRANSPORT_NAME);
+        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mTransport).initializeDevice();
+        verify(mTransport).finishBackup();
+    }
+
+    @Test
+    public void testRun_callsBackupManagerCorrectly() throws Exception {
+        setUpTransport(TRANSPORT_NAME);
+        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mBackupManagerService)
+                .recordInitPending(
+                        false, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+        verify(mBackupManagerService)
+                .resetBackupState(
+                        eq(
+                                new File(
+                                        mBaseStateDir,
+                                        TransportTestUtils.transportDirName(TRANSPORT_NAME))));
+    }
+
+    @Test
+    public void testRun_callsObserverAndListenerCorrectly() throws Exception {
+        setUpTransport(TRANSPORT_NAME);
+        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK));
+        verify(mObserver).backupFinished(eq(TRANSPORT_OK));
+        verify(mListener).onFinished(any());
+    }
+
+    @Test
+    public void testRun_whenInitializeDeviceFails() throws Exception {
+        setUpTransport(TRANSPORT_NAME);
+        configureTransport(mTransport, TRANSPORT_ERROR, 0);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mTransport).initializeDevice();
+        verify(mTransport, never()).finishBackup();
+        verify(mBackupManagerService)
+                .recordInitPending(
+                        true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+    }
+
+    @Test
+    public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly()
+            throws Exception {
+        setUpTransport(TRANSPORT_NAME);
+        configureTransport(mTransport, TRANSPORT_ERROR, 0);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR));
+        verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
+        verify(mListener).onFinished(any());
+    }
+
+    @Test
+    public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception {
+        setUpTransport(TRANSPORT_NAME);
+        configureTransport(mTransport, TRANSPORT_ERROR, 0);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mAlarmManager).set(anyInt(), anyLong(), eq(mRunInitIntent));
+    }
+
+    @Test
+    public void testRun_whenFinishBackupFails() throws Exception {
+        setUpTransport(TRANSPORT_NAME);
+        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mTransport).initializeDevice();
+        verify(mTransport).finishBackup();
+        verify(mBackupManagerService)
+                .recordInitPending(
+                        true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+    }
+
+    @Test
+    public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception {
+        setUpTransport(TRANSPORT_NAME);
+        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR));
+        verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
+        verify(mListener).onFinished(any());
+    }
+
+    @Test
+    public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception {
+        setUpTransport(TRANSPORT_NAME);
+        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mAlarmManager).set(anyInt(), anyLong(), eq(mRunInitIntent));
+    }
+
+    @Test
+    public void testRun_whenOnlyOneTransportFails() throws Exception {
+        List<TransportData> transports =
+                TransportTestUtils.setUpTransports(
+                        mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+        configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
+        configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask =
+                createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+
+        performInitializeTask.run();
+
+        verify(transports.get(1).transportMock).initializeDevice();
+        verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR));
+        verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK));
+        verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
+    }
+
+    @Test
+    public void testRun_withMultipleTransports() throws Exception {
+        List<TransportData> transports =
+                TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES);
+        configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+        configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+        configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES);
+
+        performInitializeTask.run();
+
+        for (TransportData transport : transports) {
+            verify(mTransportManager).getTransportClient(eq(transport.transportName), any());
+            verify(mTransportManager)
+                    .disposeOfTransportClient(eq(transport.transportClientMock), any());
+        }
+    }
+
+    @Test
+    public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception {
+        List<TransportData> transports =
+                TransportTestUtils.setUpTransports(
+                        mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+        configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
+        configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask =
+                createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+
+        performInitializeTask.run();
+
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any());
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any());
+    }
+
+    @Test
+    public void testRun_whenTransportNotRegistered() throws Exception {
+        TransportTestUtils.setUpTransports(
+                mTransportManager, new TransportData(TRANSPORT_NAME, null, null));
+
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mTransportManager, never()).disposeOfTransportClient(any(), any());
+        verify(mObserver, never()).onResult(any(), anyInt());
+        verify(mObserver).backupFinished(eq(TRANSPORT_OK));
+    }
+
+    @Test
+    public void testRun_whenOnlyOneTransportNotRegistered() throws Exception {
+        List<TransportData> transports =
+                TransportTestUtils.setUpTransports(
+                        mTransportManager,
+                        new TransportData(TRANSPORT_NAMES[0], null, null),
+                        new TransportData(TRANSPORT_NAMES[1]));
+        String registeredTransportName = transports.get(1).transportName;
+        IBackupTransport registeredTransport = transports.get(1).transportMock;
+        TransportClient registeredTransportClient = transports.get(1).transportClientMock;
+        PerformInitializeTask performInitializeTask =
+                createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+
+        performInitializeTask.run();
+
+        verify(registeredTransport).initializeDevice();
+        verify(mTransportManager).disposeOfTransportClient(eq(registeredTransportClient), any());
+        verify(mObserver).onResult(eq(registeredTransportName), eq(TRANSPORT_OK));
+    }
+
+    @Test
+    public void testRun_whenTransportNotAvailable() throws Exception {
+        TransportClient transportClient = mock(TransportClient.class);
+        TransportTestUtils.setUpTransports(
+                mTransportManager, new TransportData(TRANSPORT_NAME, null, transportClient));
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any());
+        verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
+        verify(mListener).onFinished(any());
+    }
+
+    @Test
+    public void testRun_whenTransportThrowsDeadObjectException() throws Exception {
+        TransportClient transportClient = mock(TransportClient.class);
+        TransportTestUtils.setUpTransports(
+                mTransportManager, new TransportData(TRANSPORT_NAME, mTransport, transportClient));
+        when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+        performInitializeTask.run();
+
+        verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any());
+        verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
+        verify(mListener).onFinished(any());
+    }
+
+    private PerformInitializeTask createPerformInitializeTask(String... transportNames) {
+        return new PerformInitializeTask(
+                mBackupManagerService,
+                mTransportManager,
+                transportNames,
+                mObserver,
+                mListener,
+                mBaseStateDir);
+    }
+
+    private void configureTransport(
+            IBackupTransport transportMock, int initializeDeviceStatus, int finishBackupStatus)
+            throws Exception {
+        when(transportMock.initializeDevice()).thenReturn(initializeDeviceStatus);
+        when(transportMock.finishBackup()).thenReturn(finishBackupStatus);
+    }
+
+    private void setUpTransport(String transportName) throws Exception {
+        TransportTestUtils.setUpTransport(
+                mTransportManager,
+                new TransportData(transportName, mTransport, mock(TransportClient.class)));
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowAppBackupUtils.java b/services/robotests/src/com/android/server/backup/testing/ShadowAppBackupUtils.java
new file mode 100644
index 0000000..73cb4c0
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/ShadowAppBackupUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.testing;
+
+import android.annotation.Nullable;
+import android.content.pm.PackageManager;
+
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.utils.AppBackupUtils;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.function.Function;
+
+@Implements(AppBackupUtils.class)
+public class ShadowAppBackupUtils {
+    public static Function<String, Boolean> sAppIsRunningAndEligibleForBackupWithTransport;
+    static {
+        reset();
+    }
+
+    @Implementation
+    public static boolean appIsRunningAndEligibleForBackupWithTransport(
+            @Nullable TransportClient transportClient, String packageName, PackageManager pm) {
+        return sAppIsRunningAndEligibleForBackupWithTransport.apply(packageName);
+    }
+
+    public static void reset() {
+        sAppIsRunningAndEligibleForBackupWithTransport = p -> true;
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportReadyCallbackStub.java b/services/robotests/src/com/android/server/backup/testing/TransportReadyCallbackStub.java
deleted file mode 100644
index bbe7eba..0000000
--- a/services/robotests/src/com/android/server/backup/testing/TransportReadyCallbackStub.java
+++ /dev/null
@@ -1,55 +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.server.backup.testing;
-
-import com.android.server.backup.TransportManager;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Stub implementation of TransportReadyCallback, which can tell which calls were made.
- */
-public class TransportReadyCallbackStub implements
-        TransportManager.TransportReadyCallback {
-    private final Set<String> mSuccessCalls = new HashSet<>();
-    private final Set<Integer> mFailureCalls = new HashSet<>();
-
-    @Override
-    public void onSuccess(String transportName) {
-        mSuccessCalls.add(transportName);
-    }
-
-    @Override
-    public void onFailure(int reason) {
-        mFailureCalls.add(reason);
-    }
-
-    /**
-     * Returns set of transport names for which {@link #onSuccess(String)} was called.
-     */
-    public Set<String> getSuccessCalls() {
-        return mSuccessCalls;
-    }
-
-    /**
-     * Returns set of reasons for which {@link #onFailure(int)} } was called.
-     */
-    public Set<Integer> getFailureCalls() {
-        return mFailureCalls;
-    }
-}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
new file mode 100644
index 0000000..9770e40
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.testing;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportNotAvailableException;
+import com.android.server.backup.transport.TransportNotRegisteredException;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class TransportTestUtils {
+    public static final String[] TRANSPORT_NAMES = {
+        "android/com.android.internal.backup.LocalTransport",
+        "com.google.android.gms/.backup.migrate.service.D2dTransport",
+        "com.google.android.gms/.backup.BackupTransportService"
+    };
+
+    public static final String TRANSPORT_NAME = TRANSPORT_NAMES[0];
+
+    /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
+    public static TransportData setUpCurrentTransport(
+            TransportManager transportManager, String transportName) throws Exception {
+        TransportData transport = setUpTransports(transportManager, transportName).get(0);
+        when(transportManager.getCurrentTransportClient(any()))
+                .thenReturn(transport.transportClientMock);
+        return transport;
+    }
+
+    /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
+    public static List<TransportData> setUpTransports(
+            TransportManager transportManager, String... transportNames) throws Exception {
+        return setUpTransports(
+                transportManager,
+                Arrays.stream(transportNames)
+                        .map(TransportData::new)
+                        .toArray(TransportData[]::new));
+    }
+
+    /** @see #setUpTransport(TransportManager, TransportData) */
+    public static List<TransportData> setUpTransports(
+            TransportManager transportManager, TransportData... transports) throws Exception {
+        for (TransportData transport : transports) {
+            setUpTransport(transportManager, transport);
+        }
+        return Arrays.asList(transports);
+    }
+
+    /**
+     * Configures transport according to {@link TransportData}:
+     *
+     * <ul>
+     *   <li>{@link TransportData#transportMock} {@code null} means transport not available.
+     *   <li>{@link TransportData#transportClientMock} {@code null} means transport not registered.
+     * </ul>
+     */
+    public static void setUpTransport(TransportManager transportManager, TransportData transport)
+            throws Exception {
+        String transportName = transport.transportName;
+        String transportDirName = transportDirName(transportName);
+        ComponentName transportComponent = transportComponentName(transportName);
+        IBackupTransport transportMock = transport.transportMock;
+        TransportClient transportClientMock = transport.transportClientMock;
+
+        if (transportClientMock != null) {
+            // Transport registered
+            when(transportManager.getTransportClient(eq(transportName), any()))
+                    .thenReturn(transportClientMock);
+            when(transportManager.getTransportClientOrThrow(eq(transportName), any()))
+                    .thenReturn(transportClientMock);
+            when(transportManager.getTransportName(transportComponent)).thenReturn(transportName);
+            when(transportManager.getTransportDirName(eq(transportName)))
+                    .thenReturn(transportDirName);
+            when(transportManager.getTransportDirName(eq(transportComponent)))
+                    .thenReturn(transportDirName);
+            when(transportClientMock.getTransportComponent()).thenReturn(transportComponent);
+
+            if (transportMock != null) {
+                // Transport registered and available
+                when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
+                when(transportMock.name()).thenReturn(transportName);
+                when(transportMock.transportDirName()).thenReturn(transportDirName);
+            } else {
+                // Transport registered but unavailable
+                when(transportClientMock.connectOrThrow(any()))
+                        .thenThrow(TransportNotAvailableException.class);
+            }
+        } else {
+            // Transport not registered
+            when(transportManager.getTransportClient(eq(transportName), any())).thenReturn(null);
+            when(transportManager.getTransportClientOrThrow(eq(transportName), any()))
+                    .thenThrow(TransportNotRegisteredException.class);
+            when(transportManager.getTransportName(transportComponent))
+                    .thenThrow(TransportNotRegisteredException.class);
+            when(transportManager.getTransportDirName(eq(transportName)))
+                    .thenThrow(TransportNotRegisteredException.class);
+            when(transportManager.getTransportDirName(eq(transportComponent)))
+                    .thenThrow(TransportNotRegisteredException.class);
+        }
+    }
+
+    /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
+    public static ComponentName transportComponentName(String transportName) {
+        return ComponentName.unflattenFromString(transportName);
+    }
+
+    public static String transportDirName(String transportName) {
+        return transportName + "_dir_name";
+    }
+
+    public static class TransportData {
+        public final String transportName;
+        @Nullable public final IBackupTransport transportMock;
+        @Nullable public final TransportClient transportClientMock;
+
+        public TransportData(
+                String transportName,
+                @Nullable IBackupTransport transportMock,
+                @Nullable TransportClient transportClientMock) {
+            this.transportName = transportName;
+            this.transportMock = transportMock;
+            this.transportClientMock = transportClientMock;
+        }
+
+        public TransportData(String transportName) {
+            this(transportName, mock(IBackupTransport.class), mock(TransportClient.class));
+        }
+    }
+
+    private TransportTestUtils() {}
+}
diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
index 4462d2a..3bc0b30 100644
--- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
+++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
@@ -43,7 +43,6 @@
 import com.android.server.testing.SystemLoaderClasses;
 
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -65,7 +64,6 @@
     @Mock private IBackupTransport.Stub mIBackupTransport;
     private TransportClient mTransportClient;
     private ComponentName mTransportComponent;
-    private String mTransportDirName;
     private Intent mBindIntent;
     private ShadowLooper mShadowLooper;
 
@@ -77,14 +75,12 @@
         mShadowLooper = shadowOf(mainLooper);
         mTransportComponent =
                 new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport");
-        mTransportDirName = mTransportComponent.toString();
         mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent);
         mTransportClient =
                 new TransportClient(
                         mContext,
                         mBindIntent,
                         mTransportComponent,
-                        mTransportDirName,
                         "1",
                         new Handler(mainLooper));
 
@@ -97,11 +93,6 @@
     }
 
     @Test
-    public void testGetTransportDirName_returnsTransportDirName() {
-        assertThat(mTransportClient.getTransportDirName()).isEqualTo(mTransportDirName);
-    }
-
-    @Test
     public void testGetTransportComponent_returnsTransportComponent() {
         assertThat(mTransportClient.getTransportComponent()).isEqualTo(mTransportComponent);
     }
diff --git a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
index 78ac4ed..6c7313b 100644
--- a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
+++ b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
@@ -27,14 +27,9 @@
 import org.robolectric.internal.bytecode.SandboxClassLoader;
 import org.robolectric.util.Util;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
 import java.net.URL;
-import java.util.Arrays;
 import java.util.Set;
 
 import javax.annotation.Nonnull;
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 3b7db9f..4176d2a 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -24,12 +24,15 @@
 import static junit.framework.Assert.fail;
 
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyListOf;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
+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.verifyNoMoreInteractions;
@@ -42,7 +45,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.net.INetworkRecommendationProvider;
 import android.net.INetworkScoreCache;
 import android.net.NetworkKey;
@@ -63,6 +68,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -128,7 +134,9 @@
     @Mock private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
     @Mock private WifiInfo mWifiInfo;
     @Mock private NetworkScoreService.ScoringServiceConnection mServiceConnection;
+    @Mock private PackageManagerInternal mPackageManagerInternal;
     @Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;
+    @Captor private ArgumentCaptor<PackageManagerInternal.PackagesProvider> mPackagesProviderCaptor;
 
     private ContentResolver mContentResolver;
     private NetworkScoreService mNetworkScoreService;
@@ -156,6 +164,7 @@
         when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
         mHandlerThread = new HandlerThread("NetworkScoreServiceTest");
         mHandlerThread.start();
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
         mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager,
                 networkScorerAppData -> mServiceConnection, mHandlerThread.getLooper());
         WifiConfiguration configuration = new WifiConfiguration();
@@ -181,6 +190,29 @@
     @After
     public void tearDown() throws Exception {
         mHandlerThread.quitSafely();
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+    }
+
+    @Test
+    public void testConstructor_setsUseOpenWifiPackagesProvider() {
+        Settings.Global.putString(mContentResolver,
+                Settings.Global.USE_OPEN_WIFI_PACKAGE, "com.some.app");
+
+        verify(mPackageManagerInternal)
+                .setUseOpenWifiAppPackagesProvider(mPackagesProviderCaptor.capture());
+
+        String[] packages = mPackagesProviderCaptor.getValue().getPackages(0);
+        assertEquals(1, packages.length);
+        assertEquals("com.some.app", packages[0]);
+    }
+
+    @Test
+    public void testConstructor_registersUseOpenWifiPackageContentObserver() {
+        Settings.Global.putString(mContentResolver,
+                Settings.Global.USE_OPEN_WIFI_PACKAGE, "com.some.other.app");
+
+        verify(mPackageManagerInternal, timeout(500))
+                .grantDefaultPermissionsToDefaultUseOpenWifiApp("com.some.other.app", 0);
     }
 
     @Test
@@ -947,8 +979,8 @@
     }
 
     private static class CountDownHandler extends Handler {
-        CountDownLatch latch = new CountDownLatch(1);
-        int receivedWhat;
+        final CountDownLatch latch = new CountDownLatch(1);
+        volatile int receivedWhat;
 
         CountDownHandler(Looper looper) {
             super(looper);
@@ -956,8 +988,8 @@
 
         @Override
         public void handleMessage(Message msg) {
-            latch.countDown();
             receivedWhat = msg.what;
+            latch.countDown();
         }
     }
 }
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 8a54c4e..8d5556e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -32,6 +32,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.os.Message;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.DebugUtils;
@@ -48,6 +49,34 @@
 import java.util.function.IntConsumer;
 
 
+/**
+ * Tests the state transitions of {@link MagnificationGestureHandler}
+ *
+ * Here's a dot graph describing the transitions being tested:
+ * {@code
+ *      digraph {
+ *          IDLE -> SHORTCUT_TRIGGERED [label="a11y\nbtn"]
+ *          SHORTCUT_TRIGGERED -> IDLE [label="a11y\nbtn"]
+ *          IDLE -> DOUBLE_TAP [label="2tap"]
+ *          DOUBLE_TAP -> IDLE [label="timeout"]
+ *          DOUBLE_TAP -> TRIPLE_TAP_AND_HOLD [label="down"]
+ *          SHORTCUT_TRIGGERED -> TRIPLE_TAP_AND_HOLD [label="down"]
+ *          TRIPLE_TAP_AND_HOLD -> ZOOMED [label="up"]
+ *          TRIPLE_TAP_AND_HOLD -> DRAGGING_TMP [label="hold/\nswipe"]
+ *          DRAGGING_TMP -> IDLE [label="release"]
+ *          ZOOMED -> ZOOMED_DOUBLE_TAP [label="2tap"]
+ *          ZOOMED_DOUBLE_TAP -> ZOOMED [label="timeout"]
+ *          ZOOMED_DOUBLE_TAP -> DRAGGING [label="hold"]
+ *          ZOOMED_DOUBLE_TAP -> IDLE [label="tap"]
+ *          DRAGGING -> ZOOMED [label="release"]
+ *          ZOOMED -> IDLE [label="a11y\nbtn"]
+ *          ZOOMED -> PANNING [label="2hold"]
+ *          PANNING -> PANNING_SCALING [label="pinch"]
+ *          PANNING_SCALING -> ZOOMED [label="release"]
+ *          PANNING -> ZOOMED [label="release"]
+ *      }
+ * }
+ */
 @RunWith(AndroidJUnit4.class)
 public class MagnificationGestureHandlerTest {
 
@@ -76,6 +105,8 @@
     private MagnificationGestureHandler mMgh;
     private TestHandler mHandler;
 
+    private long mLastDownTime = Integer.MIN_VALUE;
+
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getContext();
@@ -104,7 +135,13 @@
         MagnificationGestureHandler h = new MagnificationGestureHandler(
                 mContext, mMagnificationController,
                 detectTripleTap, detectShortcutTrigger);
-        mHandler = new TestHandler(h.mDetectingState, mClock);
+        mHandler = new TestHandler(h.mDetectingState, mClock) {
+            @Override
+            protected String messageToString(Message m) {
+                return DebugUtils.valueToString(
+                        MagnificationGestureHandler.DetectingState.class, "MESSAGE_", m.what);
+            }
+        };
         h.mDetectingState.mHandler = mHandler;
         h.setNext(strictMock(EventStreamTransformation.class));
         return h;
@@ -184,11 +221,11 @@
             fastForward1sec();
         }, STATE_ZOOMED);
 
-        // tap+tap+swipe gets delegated
-        assertTransition(STATE_2TAPS, () -> {
-            allowEventDelegation();
-            swipe();
-        }, STATE_IDLE);
+        // tap+tap+swipe doesn't get delegated
+        assertTransition(STATE_2TAPS, () -> swipe(), STATE_IDLE);
+
+        // tap+tap+swipe initiates viewport dragging immediately
+        assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_DRAGGING_TMP);
     }
 
     @Test
@@ -439,23 +476,24 @@
     }
 
     private void tap() {
-        MotionEvent downEvent = downEvent();
-        send(downEvent);
-        send(upEvent(downEvent.getDownTime()));
+        send(downEvent());
+        send(upEvent());
     }
 
     private void swipe() {
-        MotionEvent downEvent = downEvent();
-        send(downEvent);
+        swipeAndHold();
+        send(upEvent());
+    }
+
+    private void swipeAndHold() {
+        send(downEvent());
         send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2));
-        send(upEvent(downEvent.getDownTime()));
     }
 
     private void longTap() {
-        MotionEvent downEvent = downEvent();
-        send(downEvent);
+        send(downEvent());
         fastForward(2000);
-        send(upEvent(downEvent.getDownTime()));
+        send(upEvent());
     }
 
     private void triggerShortcut() {
@@ -473,16 +511,17 @@
     }
 
     private MotionEvent moveEvent(float x, float y) {
-        return MotionEvent.obtain(defaultDownTime(), mClock.now(), ACTION_MOVE, x, y, 0);
+        return MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0);
     }
 
     private MotionEvent downEvent() {
-        return MotionEvent.obtain(mClock.now(), mClock.now(),
+        mLastDownTime = mClock.now();
+        return MotionEvent.obtain(mLastDownTime, mLastDownTime,
                 ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0);
     }
 
     private MotionEvent upEvent() {
-        return upEvent(defaultDownTime());
+        return upEvent(mLastDownTime);
     }
 
     private MotionEvent upEvent(long downTime) {
@@ -490,11 +529,6 @@
                 MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0);
     }
 
-    private long defaultDownTime() {
-        MotionEvent lastDown = mMgh.mDetectingState.mLastDown;
-        return lastDown == null ? mClock.now() - 1 : lastDown.getDownTime();
-    }
-
     private MotionEvent pointerEvent(int action, float x, float y) {
         MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
         defPointerProperties.id = 0;
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 6b09363..bcbf40e 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -47,7 +47,7 @@
  * Tests for the {@link ActivityStack} class.
  *
  * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.am.ActivityStackTests
+ *  atest ActivityStackTests
  */
 @SmallTest
 @Presubmit
@@ -104,6 +104,29 @@
     }
 
     @Test
+    public void testPrimarySplitScreenToFullscreenWhenMovedToBack() throws Exception {
+        // Create primary splitscreen stack. This will create secondary stacks and places the
+        // existing fullscreen stack on the bottom.
+        final ActivityStack primarySplitScreen = mService.mStackSupervisor.getDefaultDisplay()
+                .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
+                        true /* onTop */);
+
+        // Assert windowing mode.
+        assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Move primary to back.
+        primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack",
+                null /* task */);
+
+        // Assert that stack is at the bottom.
+        assertEquals(mService.mStackSupervisor.getDefaultDisplay().getIndexOf(primarySplitScreen),
+                0);
+
+        // Ensure no longer in splitscreen.
+        assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_FULLSCREEN);
+    }
+
+    @Test
     public void testStopActivityWhenActivityDestroyed() throws Exception {
         final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
         r.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
diff --git a/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java b/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java
new file mode 100644
index 0000000..d9b3e1c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for {@link GlobalSettingsToPropertiesMapper}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GlobalSettingsToPropertiesMapperTest {
+    private static final String[][] TEST_MAPPING = new String[][] {
+        {Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "TestProperty"}
+    };
+
+    private TestMapper mTestMapper;
+    private MockContentResolver mMockContentResolver;
+
+    @Before
+    public void setup() {
+        // Use FakeSettingsProvider to not affect global state
+        mMockContentResolver = new MockContentResolver(InstrumentationRegistry.getContext());
+        mMockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        mTestMapper = new TestMapper(mMockContentResolver);
+    }
+
+    @Test
+    public void testUpdatePropertiesFromGlobalSettings() {
+        Settings.Global.putString(mMockContentResolver,
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue");
+
+        mTestMapper.updatePropertiesFromGlobalSettings();
+        String propValue = mTestMapper.systemPropertiesGet("TestProperty");
+        Assert.assertEquals("testValue", propValue);
+
+        Settings.Global.putString(mMockContentResolver,
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2");
+        mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+                "TestProperty");
+        propValue = mTestMapper.systemPropertiesGet("TestProperty");
+        Assert.assertEquals("testValue2", propValue);
+
+        Settings.Global.putString(mMockContentResolver,
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null);
+        mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+                "TestProperty");
+        propValue = mTestMapper.systemPropertiesGet("TestProperty");
+        Assert.assertEquals("", propValue);
+    }
+
+    @Test
+    public void testUpdatePropertiesFromGlobalSettings_PropertyAndSettingNotPresent() {
+        // Test that empty property will not not be set if setting is not set
+        mTestMapper.updatePropertiesFromGlobalSettings();
+        String propValue = mTestMapper.systemPropertiesGet("TestProperty");
+        Assert.assertNull("Property should not be set if setting is null", propValue);
+    }
+
+    private static class TestMapper extends GlobalSettingsToPropertiesMapper {
+        private final Map<String, String> mProps = new HashMap<>();
+
+        TestMapper(ContentResolver contentResolver) {
+            super(contentResolver, TEST_MAPPING);
+        }
+
+        @Override
+        protected String systemPropertiesGet(String key) {
+            Preconditions.checkNotNull(key);
+            return mProps.get(key);
+        }
+
+        @Override
+        protected void systemPropertiesSet(String key, String value) {
+            Preconditions.checkNotNull(value);
+            Preconditions.checkNotNull(key);
+            mProps.put(key, value);
+        }
+    }
+
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index f384044..5a21102 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -572,7 +572,6 @@
         });
         assertSecurityException(expectCallable, () -> mService.getTaskDescription(0));
         assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
-        assertSecurityException(expectCallable, () -> mService.cancelTaskThumbnailTransition(0));
         assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null,
                 null, 0));
     }
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index f4c5442..766d30d 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -48,6 +48,7 @@
 @SmallTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
+// TODO: Migrate this to Robolectric and merge with BackupManagerServiceRoboTest (and remove 'Robo')
 public class BackupManagerServiceTest {
     private static final String TAG = "BMSTest";
     private static final ComponentName TRANSPORT_COMPONENT =
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
index c7fd551..9cac536 100644
--- a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
@@ -1,6 +1,5 @@
 package com.android.server.backup.testutils;
 
-import android.app.PackageInstallObserver;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -32,7 +31,6 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.net.Uri;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.storage.VolumeInfo;
@@ -622,12 +620,6 @@
     }
 
     @Override
-    public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags,
-            String installerPackageName) {
-
-    }
-
-    @Override
     public int installExistingPackage(String packageName)
             throws NameNotFoundException {
         return 0;
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 60783db..58ac7d2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -17,6 +17,10 @@
 
 import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
 import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
@@ -71,6 +75,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.security.KeyChain;
+import android.security.keystore.AttestationUtils;
 import android.telephony.TelephonyManager;
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -4459,6 +4464,47 @@
         });
     }
 
+    private void assertAttestationFlags(int attestationFlags, int[] expectedFlags) {
+        int[] gotFlags = DevicePolicyManagerService.translateIdAttestationFlags(attestationFlags);
+        Arrays.sort(gotFlags);
+        Arrays.sort(expectedFlags);
+        assertTrue(Arrays.equals(expectedFlags, gotFlags));
+    }
+
+    public void testTranslationOfIdAttestationFlag() {
+        int[] allIdTypes = new int[]{ID_TYPE_SERIAL, ID_TYPE_IMEI, ID_TYPE_MEID};
+        int[] correspondingAttUtilsTypes = new int[]{
+            AttestationUtils.ID_TYPE_SERIAL, AttestationUtils.ID_TYPE_IMEI,
+            AttestationUtils.ID_TYPE_MEID};
+
+        // Test translation of zero flags
+        assertNull(DevicePolicyManagerService.translateIdAttestationFlags(0));
+
+        // Test translation of the ID_TYPE_BASE_INFO flag, which should yield an empty, but
+        // non-null array
+        assertAttestationFlags(ID_TYPE_BASE_INFO, new int[] {});
+
+        // Test translation of a single flag
+        assertAttestationFlags(ID_TYPE_BASE_INFO | ID_TYPE_SERIAL,
+                new int[] {AttestationUtils.ID_TYPE_SERIAL});
+        assertAttestationFlags(ID_TYPE_SERIAL, new int[] {AttestationUtils.ID_TYPE_SERIAL});
+
+        // Test translation of two flags
+        assertAttestationFlags(ID_TYPE_SERIAL | ID_TYPE_IMEI,
+                new int[] {AttestationUtils.ID_TYPE_IMEI, AttestationUtils.ID_TYPE_SERIAL});
+        assertAttestationFlags(ID_TYPE_BASE_INFO | ID_TYPE_MEID | ID_TYPE_SERIAL,
+                new int[] {AttestationUtils.ID_TYPE_MEID, AttestationUtils.ID_TYPE_SERIAL});
+
+        // Test translation of all three flags
+        assertAttestationFlags(ID_TYPE_SERIAL | ID_TYPE_IMEI | ID_TYPE_MEID,
+                new int[] {AttestationUtils.ID_TYPE_IMEI, AttestationUtils.ID_TYPE_SERIAL,
+                    AttestationUtils.ID_TYPE_MEID});
+        // Test translation of all three flags
+        assertAttestationFlags(ID_TYPE_SERIAL | ID_TYPE_IMEI | ID_TYPE_MEID | ID_TYPE_BASE_INFO,
+                new int[] {AttestationUtils.ID_TYPE_IMEI, AttestationUtils.ID_TYPE_SERIAL,
+                    AttestationUtils.ID_TYPE_MEID});
+    }
+
     private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
         when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
                 userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index 4447fe9..939a272 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -261,7 +261,7 @@
     }
 
     private void verifyAppsAreNonRequired(String action, String... appArray) {
-        assertEquals(listFromArray(appArray),
+        assertEquals(setFromArray(appArray),
                 mHelper.getNonRequiredApps(TEST_MDM_COMPONENT_NAME, TEST_USER_ID, action));
     }
 
@@ -347,14 +347,7 @@
         if (array == null) {
             return null;
         }
-        return new HashSet<T>(Arrays.asList(array));
-    }
-
-    private <T> List<T> listFromArray(T... array) {
-        if (array == null) {
-            return null;
-        }
-        return Arrays.asList(array);
+        return new HashSet<>(Arrays.asList(array));
     }
 
     class FakePackageManager extends MockPackageManager {
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index 2629b12..9e7ef65 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -17,10 +17,18 @@
 package com.android.server.display;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.hardware.display.BrightnessConfiguration;
 import android.os.PowerManager;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -35,18 +43,18 @@
 @RunWith(AndroidJUnit4.class)
 public class BrightnessMappingStrategyTest {
 
-    private static final float[] LUX_LEVELS = {
-        0f,
-        5f,
-        20f,
-        40f,
-        100f,
-        325f,
-        600f,
-        1250f,
-        2200f,
-        4000f,
-        5000f
+    private static final int[] LUX_LEVELS = {
+        0,
+        5,
+        20,
+        40,
+        100,
+        325,
+        600,
+        1250,
+        2200,
+        4000,
+        5000
     };
 
     private static final float[] DISPLAY_LEVELS_NITS = {
@@ -80,11 +88,13 @@
     private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
     private static final int[] BACKLIGHT_RANGE = { 1, 255 };
 
+    private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
+
     @Test
     public void testSimpleStrategyMappingAtControlPoints() {
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(
-                LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT,
-                null /*brightnessLevelsNits*/, null /*nitsRange*/, null /*backlightRange*/);
+        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
         for (int i = 0; i < LUX_LEVELS.length; i++) {
             final float expectedLevel =
@@ -96,24 +106,52 @@
 
     @Test
     public void testSimpleStrategyMappingBetweenControlPoints() {
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(
-                LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT,
-                null /*brightnessLevelsNits*/, null /*nitsRange*/, null /*backlightRange*/);
+        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
         for (int i = 1; i < LUX_LEVELS.length; i++) {
             final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
             final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
             assertTrue("Desired brightness should be between adjacent control points.",
-                    backlight > DISPLAY_LEVELS_BACKLIGHT[i-1]
+                    backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1]
                         && backlight < DISPLAY_LEVELS_BACKLIGHT[i]);
         }
     }
 
     @Test
+    public void testSimpleStrategyIgnoresNewConfiguration() {
+        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
+
+        final int N = LUX_LEVELS.length;
+        final float[] lux = { 0f, 1f };
+        final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
+
+        BrightnessConfiguration config = new BrightnessConfiguration.Builder()
+                .setCurve(lux, nits)
+                .build();
+        strategy.setBrightnessConfiguration(config);
+        assertNotEquals(1.0f, strategy.getBrightness(1f), 0.01 /*tolerance*/);
+    }
+
+    @Test
+    public void testSimpleStrategyIgnoresNullConfiguration() {
+        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
+
+        strategy.setBrightnessConfiguration(null);
+        final int N = DISPLAY_LEVELS_BACKLIGHT.length;
+        final float expectedBrightness =
+                (float) DISPLAY_LEVELS_BACKLIGHT[N - 1] / PowerManager.BRIGHTNESS_ON;
+        assertEquals(expectedBrightness,
+                strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01 /*tolerance*/);
+    }
+
+    @Test
     public void testPhysicalStrategyMappingAtControlPoints() {
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(
-                LUX_LEVELS, null /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
+                DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res);
         assertNotNull("BrightnessMappingStrategy should not be null", physical);
         for (int i = 0; i < LUX_LEVELS.length; i++) {
             final float expectedLevel = DISPLAY_LEVELS_NITS[i] / DISPLAY_RANGE_NITS[1];
@@ -124,9 +162,9 @@
 
     @Test
     public void testPhysicalStrategyMappingBetweenControlPoints() {
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(
-                LUX_LEVELS, null /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
+                DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res);
         assertNotNull("BrightnessMappingStrategy should not be null", physical);
         Spline backlightToBrightness =
                 Spline.createSpline(toFloatArray(BACKLIGHT_RANGE), DISPLAY_RANGE_NITS);
@@ -135,88 +173,111 @@
             final float backlight = physical.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
             final float nits = backlightToBrightness.interpolate(backlight);
             assertTrue("Desired brightness should be between adjacent control points.",
-                    nits > DISPLAY_LEVELS_NITS[i-1] && nits < DISPLAY_LEVELS_NITS[i]);
+                    nits > DISPLAY_LEVELS_NITS[i - 1] && nits < DISPLAY_LEVELS_NITS[i]);
         }
     }
 
     @Test
+    public void testPhysicalStrategyUsesNewConfigurations() {
+        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
+                DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
+
+        final float[] lux = { 0f, 1f };
+        final float[] nits = {
+                DISPLAY_RANGE_NITS[0],
+                DISPLAY_RANGE_NITS[DISPLAY_RANGE_NITS.length - 1]
+        };
+
+        BrightnessConfiguration config = new BrightnessConfiguration.Builder()
+                .setCurve(lux, nits)
+                .build();
+        strategy.setBrightnessConfiguration(config);
+        assertEquals(1.0f, strategy.getBrightness(1f), 0.01 /*tolerance*/);
+
+        // Check that null returns us to the default configuration.
+        strategy.setBrightnessConfiguration(null);
+        final int N = DISPLAY_LEVELS_NITS.length;
+        final float expectedBrightness = DISPLAY_LEVELS_NITS[N - 1] / DISPLAY_RANGE_NITS[1];
+        assertEquals(expectedBrightness,
+                strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01f /*tolerance*/);
+    }
+
+    @Test
     public void testDefaultStrategyIsPhysical() {
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(
-                LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT,
+        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT,
                 DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
         assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
     }
 
     @Test
     public void testNonStrictlyIncreasingLuxLevelsFails() {
-        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
+        final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
         final int idx = lux.length / 2;
-        float tmp = lux[idx];
+        int tmp = lux[idx];
         lux[idx] = lux[idx+1];
         lux[idx+1] = tmp;
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(
-                lux, null /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        Resources res = createResources(lux, DISPLAY_LEVELS_NITS,
+                DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
         assertNull(strategy);
 
         // And make sure we get the same result even if it's monotone but not increasing.
         lux[idx] = lux[idx+1];
-        strategy = BrightnessMappingStrategy.create(
-                lux, null /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        res = createResources(lux, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        strategy = BrightnessMappingStrategy.create(res);
         assertNull(strategy);
     }
 
     @Test
     public void testDifferentNumberOfControlPointValuesFails() {
         //Extra lux level
-        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length+1);
+        final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length+1);
         // Make sure it's strictly increasing so that the only failure is the differing array
         // lengths
         lux[lux.length - 1] = lux[lux.length - 2] + 1;
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(
-                lux, null /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        Resources res = createResources(lux, DISPLAY_LEVELS_NITS,
+                DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
         assertNull(strategy);
 
-        strategy = BrightnessMappingStrategy.create(
-                lux, DISPLAY_LEVELS_BACKLIGHT,
-                null /*brightnessLevelsNits*/, null /*nitsRange*/, null /*backlightRange*/);
+        res = createResources(lux, DISPLAY_LEVELS_BACKLIGHT);
+        strategy = BrightnessMappingStrategy.create(res);
         assertNull(strategy);
 
         // Extra backlight level
         final int[] backlight = Arrays.copyOf(
                 DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length+1);
         backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
-        strategy = BrightnessMappingStrategy.create(
-                LUX_LEVELS, backlight,
-                null /*brightnessLevelsNits*/, null /*nitsRange*/, null /*backlightRange*/);
+        res = createResources(LUX_LEVELS, backlight);
+        strategy = BrightnessMappingStrategy.create(res);
         assertNull(strategy);
 
         // Extra nits level
         final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length+1);
         nits[nits.length - 1] = nits[nits.length - 2] + 1;
-        strategy = BrightnessMappingStrategy.create(
-                LUX_LEVELS, null /*brightnessLevelsBacklight*/,
-                nits, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        res = createResources(LUX_LEVELS, nits, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+        strategy = BrightnessMappingStrategy.create(res);
         assertNull(strategy);
     }
 
     @Test
     public void testPhysicalStrategyRequiresNitsMapping() {
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(
-                LUX_LEVELS, null /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS, null, BACKLIGHT_RANGE);
+        Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
+                DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/, BACKLIGHT_RANGE);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res);
         assertNull(physical);
 
-        physical = BrightnessMappingStrategy.create(
-                LUX_LEVELS, null /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, null);
+        res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
+                DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, EMPTY_INT_ARRAY /*backlightRange*/);
+        physical = BrightnessMappingStrategy.create(res);
         assertNull(physical);
 
-        physical = BrightnessMappingStrategy.create(
-                LUX_LEVELS, null /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS, null, null);
+        res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
+                DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/,
+                EMPTY_INT_ARRAY /*backlightRange*/);
+        physical = BrightnessMappingStrategy.create(res);
         assertNull(physical);
     }
 
@@ -227,4 +288,73 @@
         }
         return newVals;
     }
+
+    private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight) {
+        return createResources(luxLevels, brightnessLevelsBacklight,
+                EMPTY_FLOAT_ARRAY /*brightnessLevelsNits*/, EMPTY_FLOAT_ARRAY /*nitsRange*/,
+                EMPTY_INT_ARRAY /*backlightRange*/);
+    }
+
+    private Resources createResources(int[] luxLevels, float[] brightnessLevelsNits,
+            float[] nitsRange, int[] backlightRange) {
+        return createResources(luxLevels, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
+                brightnessLevelsNits, nitsRange, backlightRange);
+    }
+
+    private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight,
+            float[] brightnessLevelsNits, float[] nitsRange, int[] backlightRange) {
+        Resources mockResources = mock(Resources.class);
+        // For historical reasons, the lux levels resource implicitly defines the first point as 0,
+        // so we need to chop it off of the array the mock resource object returns.
+        int[] luxLevelsResource = Arrays.copyOfRange(luxLevels, 1, luxLevels.length);
+        when(mockResources.getIntArray(com.android.internal.R.array.config_autoBrightnessLevels))
+                .thenReturn(luxLevelsResource);
+
+        when(mockResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
+                .thenReturn(brightnessLevelsBacklight);
+
+        TypedArray mockBrightnessLevelNits = createFloatTypedArray(brightnessLevelsNits);
+        when(mockResources.obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+                .thenReturn(mockBrightnessLevelNits);
+
+        TypedArray mockNitsRange = createFloatTypedArray(nitsRange);
+        when(mockResources.obtainTypedArray(
+                com.android.internal.R.array.config_screenBrightnessNits))
+                .thenReturn(mockNitsRange);
+
+        when(mockResources.getIntArray(
+                com.android.internal.R.array.config_screenBrightnessBacklight))
+                .thenReturn(backlightRange);
+
+        when(mockResources.getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
+                .thenReturn(1);
+        when(mockResources.getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
+                .thenReturn(255);
+        return mockResources;
+    }
+
+    private TypedArray createFloatTypedArray(float[] vals) {
+        TypedArray mockArray = mock(TypedArray.class);
+        when(mockArray.length()).thenAnswer(invocation -> {
+            return vals.length;
+        });
+        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+            final float def = (float) invocation.getArguments()[1];
+            if (vals == null) {
+                return def;
+            }
+            int idx = (int) invocation.getArguments()[0];
+            if (idx >= 0 && idx < vals.length) {
+                return vals[idx];
+            } else {
+                return def;
+            }
+        });
+        return mockArray;
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 926009e..08edd52 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -66,6 +66,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BrightnessTrackerTest {
+    private static final float DEFAULT_INITIAL_BRIGHTNESS = 2.5f;
+    private static final float FLOAT_DELTA = 0.01f;
 
     private BrightnessTracker mTracker;
     private TestInjector mInjector;
@@ -98,7 +100,6 @@
         mInjector.mInteractive = false;
         startTracker(mTracker);
         assertNull(mInjector.mSensorListener);
-        assertNotNull(mInjector.mSettingsObserver);
         assertNotNull(mInjector.mBroadcastReceiver);
         assertTrue(mInjector.mIdleScheduled);
         Intent onIntent = new Intent();
@@ -119,7 +120,6 @@
 
         mTracker.stop();
         assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mSettingsObserver);
         assertNull(mInjector.mBroadcastReceiver);
         assertFalse(mInjector.mIdleScheduled);
     }
@@ -128,12 +128,10 @@
     public void testBrightnessEvent() {
         final int brightness = 20;
 
-        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
         startTracker(mTracker);
         mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
         mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
-                Settings.System.SCREEN_BRIGHTNESS));
+        notifyBrightnessChanged(mTracker, brightness);
         List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
         mTracker.stop();
 
@@ -141,10 +139,11 @@
         BrightnessChangeEvent event = events.get(0);
         assertEquals(mInjector.currentTimeMillis(), event.timeStamp);
         assertEquals(1, event.luxValues.length);
-        assertEquals(1.0f, event.luxValues[0], 0.1f);
+        assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA);
         assertEquals(mInjector.currentTimeMillis() - TimeUnit.SECONDS.toMillis(2),
                 event.luxTimestamps[0]);
-        assertEquals(brightness, event.brightness);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
+        assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA);
 
         // System had no data so these should all be at defaults.
         assertEquals(Float.NaN, event.batteryLevel, 0.0);
@@ -154,21 +153,18 @@
 
     @Test
     public void testBrightnessFullPopulatedEvent() {
-        final int lastBrightness = 230;
+        final int initialBrightness = 230;
         final int brightness = 130;
 
-        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, lastBrightness);
         mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
         mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3333);
 
-        startTracker(mTracker);
-        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
+        startTracker(mTracker, initialBrightness);
         mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
                 batteryChangeEvent(30, 60));
         mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
         final long sensorTime = mInjector.currentTimeMillis();
-        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
-                Settings.System.SCREEN_BRIGHTNESS));
+        notifyBrightnessChanged(mTracker, brightness);
         List<BrightnessChangeEvent> eventsNoPackage
                 = mTracker.getEvents(0, false).getList();
         List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
@@ -179,9 +175,9 @@
         assertEquals(event.timeStamp, mInjector.currentTimeMillis());
         assertArrayEquals(new float[] {1000.0f}, event.luxValues, 0.01f);
         assertArrayEquals(new long[] {sensorTime}, event.luxTimestamps);
-        assertEquals(brightness, event.brightness);
-        assertEquals(lastBrightness, event.lastBrightness);
-        assertEquals(0.5, event.batteryLevel, 0.01);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
+        assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA);
+        assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
         assertTrue(event.nightMode);
         assertEquals(3333, event.colorTemperature);
         assertEquals("a.package", event.packageName);
@@ -192,45 +188,34 @@
     }
 
     @Test
-    public void testIgnoreSelfChange() {
+    public void testIgnoreAutomaticBrightnessChange() {
         final int initialBrightness = 30;
-        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, initialBrightness);
-        startTracker(mTracker);
+        startTracker(mTracker, initialBrightness);
         mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
         mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
 
         final int systemUpdatedBrightness = 20;
-        mTracker.setBrightness(systemUpdatedBrightness, 0);
-        assertEquals(systemUpdatedBrightness,
-                (int) mInjector.mSystemIntSettings.get(Settings.System.SCREEN_BRIGHTNESS));
-        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
-                Settings.System.SCREEN_BRIGHTNESS));
+        notifyBrightnessChanged(mTracker, systemUpdatedBrightness, false /*userInitiated*/);
         List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
         // No events because we filtered out our change.
         assertEquals(0, events.size());
 
         final int firstUserUpdateBrightness = 20;
         // Then change comes from somewhere else so we shouldn't filter.
-        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS,
-                firstUserUpdateBrightness);
-        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
-                Settings.System.SCREEN_BRIGHTNESS));
+        notifyBrightnessChanged(mTracker, firstUserUpdateBrightness);
 
         // and with a different brightness value.
         final int secondUserUpdateBrightness = 34;
-        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS,
-                secondUserUpdateBrightness);
-        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
-                Settings.System.SCREEN_BRIGHTNESS));
+        notifyBrightnessChanged(mTracker, secondUserUpdateBrightness);
         events = mTracker.getEvents(0, true).getList();
 
         assertEquals(2, events.size());
         // First event is change from system update (20) to first user update (20)
-        assertEquals(systemUpdatedBrightness, events.get(0).lastBrightness);
-        assertEquals(firstUserUpdateBrightness, events.get(0).brightness);
+        assertEquals(systemUpdatedBrightness, events.get(0).lastBrightness, FLOAT_DELTA);
+        assertEquals(firstUserUpdateBrightness, events.get(0).brightness, FLOAT_DELTA);
         // Second event is from first to second user update.
-        assertEquals(firstUserUpdateBrightness, events.get(1).lastBrightness);
-        assertEquals(secondUserUpdateBrightness, events.get(1).brightness);
+        assertEquals(firstUserUpdateBrightness, events.get(1).lastBrightness, FLOAT_DELTA);
+        assertEquals(secondUserUpdateBrightness, events.get(1).brightness, FLOAT_DELTA);
 
         mTracker.stop();
     }
@@ -243,9 +228,7 @@
         for (int brightness = 0; brightness <= 255; ++brightness) {
             mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
             mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1));
-            mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
-            mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
-                    Settings.System.SCREEN_BRIGHTNESS));
+            notifyBrightnessChanged(mTracker, brightness);
         }
         List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
         mTracker.stop();
@@ -254,14 +237,13 @@
         assertEquals(100, events.size());
         for (int i = 0; i < events.size(); i++) {
             BrightnessChangeEvent event = events.get(i);
-            assertEquals(156 + i, event.brightness);
+            assertEquals(156 + i, event.brightness, FLOAT_DELTA);
         }
     }
 
     @Test
     public void testLimitedSensorEvents() {
         final int brightness = 20;
-        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
 
         startTracker(mTracker);
         // 20 Sensor events 1 second apart.
@@ -269,8 +251,7 @@
             mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
             mInjector.mSensorListener.onSensorChanged(createSensorEvent(i + 1.0f));
         }
-        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
-                Settings.System.SCREEN_BRIGHTNESS));
+        notifyBrightnessChanged(mTracker, 20);
         List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
         mTracker.stop();
 
@@ -284,7 +265,7 @@
             assertEquals(event.luxTimestamps[11 - i],
                     mInjector.currentTimeMillis() - i * TimeUnit.SECONDS.toMillis(1));
         }
-        assertEquals(brightness, event.brightness);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
     }
 
     @Test
@@ -298,25 +279,25 @@
         String eventFile =
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                 + "<events>\n"
-                + "<event brightness=\"194\" timestamp=\""
+                + "<event nits=\"194.2\" timestamp=\""
                 + Long.toString(someTimeAgo) + "\" packageName=\""
                 + "com.example.app\" user=\"10\" "
-                + "lastBrightness=\"32\" "
+                + "lastNits=\"32.333\" "
                 + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\"\n"
                 + "lux=\"32.2,31.1\" luxTimestamps=\""
                 + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\"/>"
-                + "<event brightness=\"71\" timestamp=\""
+                + "<event nits=\"71\" timestamp=\""
                 + Long.toString(someTimeAgo) + "\" packageName=\""
                 + "com.android.anapp\" user=\"11\" "
-                + "lastBrightness=\"32\" "
+                + "lastNits=\"32\" "
                 + "batteryLevel=\"0.5\" nightMode=\"true\" colorTemperature=\"3235\"\n"
                 + "lux=\"132.2,131.1\" luxTimestamps=\""
                 + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\"/>"
                 // Event that is too old so shouldn't show up.
-                + "<event brightness=\"142\" timestamp=\""
+                + "<event nits=\"142\" timestamp=\""
                 + Long.toString(twoMonthsAgo) + "\" packageName=\""
                 + "com.example.app\" user=\"10\" "
-                + "lastBrightness=\"32\" "
+                + "lastNits=\"32\" "
                 + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\"\n"
                 + "lux=\"32.2,31.1\" luxTimestamps=\""
                 + Long.toString(twoMonthsAgo) + "," + Long.toString(twoMonthsAgo) + "\"/>"
@@ -326,27 +307,27 @@
         assertEquals(1, events.size());
         BrightnessChangeEvent event = events.get(0);
         assertEquals(someTimeAgo, event.timeStamp);
-        assertEquals(194, event.brightness);
-        assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, 0.01f);
+        assertEquals(194.2, event.brightness, FLOAT_DELTA);
+        assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, FLOAT_DELTA);
         assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
-        assertEquals(32, event.lastBrightness);
+        assertEquals(32.333, event.lastBrightness, FLOAT_DELTA);
         assertEquals(0, event.userId);
         assertFalse(event.nightMode);
-        assertEquals(1.0f, event.batteryLevel, 0.01);
+        assertEquals(1.0f, event.batteryLevel, FLOAT_DELTA);
         assertEquals("com.example.app", event.packageName);
 
         events = tracker.getEvents(1, true).getList();
         assertEquals(1, events.size());
         event = events.get(0);
         assertEquals(someTimeAgo, event.timeStamp);
-        assertEquals(71, event.brightness);
-        assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, 0.01f);
+        assertEquals(71, event.brightness, FLOAT_DELTA);
+        assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, FLOAT_DELTA);
         assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
-        assertEquals(32, event.lastBrightness);
+        assertEquals(32, event.lastBrightness, FLOAT_DELTA);
         assertEquals(1, event.userId);
         assertTrue(event.nightMode);
         assertEquals(3235, event.colorTemperature);
-        assertEquals(0.5f, event.batteryLevel, 0.01);
+        assertEquals(0.5f, event.batteryLevel, FLOAT_DELTA);
         assertEquals("com.android.anapp", event.packageName);
     }
 
@@ -370,7 +351,7 @@
         eventFile =
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                         + "<events>\n"
-                        + "<event brightness=\"194\" timestamp=\"" + someTimeAgo + "\" packageName=\""
+                        + "<event nits=\"194\" timestamp=\"" + someTimeAgo + "\" packageName=\""
                         + "com.example.app\" user=\"10\" "
                         + "batteryLevel=\"0.7\" nightMode=\"false\" colorTemperature=\"0\" />\n"
                         + "</events>";
@@ -386,7 +367,6 @@
     public void testWriteThenRead() throws Exception {
         final int brightness = 20;
 
-        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
         mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
         mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
 
@@ -399,8 +379,7 @@
         mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
         final long secondSensorTime = mInjector.currentTimeMillis();
         mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3));
-        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
-                Settings.System.SCREEN_BRIGHTNESS));
+        notifyBrightnessChanged(mTracker, brightness);
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         mTracker.writeEventsLocked(baos);
         mTracker.stop();
@@ -414,10 +393,10 @@
 
         assertEquals(1, events.size());
         BrightnessChangeEvent event = events.get(0);
-        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, 0.01f);
+        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
         assertArrayEquals(new long[] {firstSensorTime, secondSensorTime}, event.luxTimestamps);
-        assertEquals(brightness, event.brightness);
-        assertEquals(0.3, event.batteryLevel, 0.01f);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
+        assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
         assertTrue(event.nightMode);
         assertEquals(3339, event.colorTemperature);
     }
@@ -426,7 +405,6 @@
     public void testWritePrunesOldEvents() throws Exception {
         final int brightness = 20;
 
-        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
         mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
         mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
 
@@ -437,14 +415,12 @@
         mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
         mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
         final long sensorTime = mInjector.currentTimeMillis();
-        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
-                Settings.System.SCREEN_BRIGHTNESS));
+        notifyBrightnessChanged(mTracker, brightness);
 
         // 31 days later
         mInjector.incrementTime(TimeUnit.DAYS.toMillis(31));
         mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
-        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
-                Settings.System.SCREEN_BRIGHTNESS));
+        notifyBrightnessChanged(mTracker, brightness);
         final long eventTime = mInjector.currentTimeMillis();
 
         List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
@@ -460,10 +436,10 @@
         assertEquals(eventTime, event.timeStamp);
 
         // We will keep one of the old sensor events because we keep 1 event outside the window.
-        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, 0.01f);
+        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
         assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps);
-        assertEquals(brightness, event.brightness);
-        assertEquals(0.3, event.batteryLevel, 0.01f);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
+        assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
         assertTrue(event.nightMode);
         assertEquals(3339, event.colorTemperature);
     }
@@ -472,7 +448,7 @@
     public void testParcelUnParcel() {
         Parcel parcel = Parcel.obtain();
         BrightnessChangeEvent event = new BrightnessChangeEvent();
-        event.brightness = 23;
+        event.brightness = 23f;
         event.timeStamp = 345L;
         event.packageName = "com.example";
         event.userId = 12;
@@ -485,7 +461,7 @@
         event.batteryLevel = 0.7f;
         event.nightMode = false;
         event.colorTemperature = 345;
-        event.lastBrightness = 50;
+        event.lastBrightness = 50f;
 
         event.writeToParcel(parcel, 0);
         byte[] parceled = parcel.marshall();
@@ -497,16 +473,16 @@
 
         BrightnessChangeEvent event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
         parcel.recycle();
-        assertEquals(event.brightness, event2.brightness);
+        assertEquals(event.brightness, event2.brightness, FLOAT_DELTA);
         assertEquals(event.timeStamp, event2.timeStamp);
         assertEquals(event.packageName, event2.packageName);
         assertEquals(event.userId, event2.userId);
-        assertArrayEquals(event.luxValues, event2.luxValues, 0.01f);
+        assertArrayEquals(event.luxValues, event2.luxValues, FLOAT_DELTA);
         assertArrayEquals(event.luxTimestamps, event2.luxTimestamps);
-        assertEquals(event.batteryLevel, event2.batteryLevel, 0.01f);
+        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
         assertEquals(event.nightMode, event2.nightMode);
         assertEquals(event.colorTemperature, event2.colorTemperature);
-        assertEquals(event.lastBrightness, event2.lastBrightness);
+        assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA);
 
         parcel = Parcel.obtain();
         event.batteryLevel = Float.NaN;
@@ -518,7 +494,7 @@
         parcel.unmarshall(parceled, 0, parceled.length);
         parcel.setDataPosition(0);
         event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
-        assertEquals(event.batteryLevel, event2.batteryLevel, 0.01f);
+        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
     }
 
     private InputStream getInputStream(String data) {
@@ -550,7 +526,21 @@
     }
 
     private void startTracker(BrightnessTracker tracker) {
-        tracker.start();
+        startTracker(tracker, DEFAULT_INITIAL_BRIGHTNESS);
+    }
+
+    private void startTracker(BrightnessTracker tracker, float initialBrightness) {
+        tracker.start(initialBrightness);
+        mInjector.waitForHandler();
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness) {
+        notifyBrightnessChanged(tracker, brightness, true /*userInitiated*/);
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            boolean userInitiated) {
+        tracker.notifyBrightnessChanged(brightness, userInitiated);
         mInjector.waitForHandler();
     }
 
@@ -578,7 +568,6 @@
 
     private class TestInjector extends BrightnessTracker.Injector {
         SensorEventListener mSensorListener;
-        ContentObserver mSettingsObserver;
         BroadcastReceiver mBroadcastReceiver;
         Map<String, Integer> mSystemIntSettings = new HashMap<>();
         Map<String, Integer> mSecureIntSettings = new HashMap<>();
@@ -610,18 +599,6 @@
         }
 
         @Override
-        public void registerBrightnessObserver(ContentResolver resolver,
-                ContentObserver settingsObserver) {
-            mSettingsObserver = settingsObserver;
-        }
-
-        @Override
-        public void unregisterBrightnessObserver(Context context,
-                ContentObserver settingsObserver) {
-            mSettingsObserver = null;
-        }
-
-        @Override
         public void registerReceiver(Context context,
                 BroadcastReceiver shutdownReceiver, IntentFilter shutdownFilter) {
             mBroadcastReceiver = shutdownReceiver;
@@ -647,23 +624,6 @@
         }
 
         @Override
-        public int getSystemIntForUser(ContentResolver resolver, String setting, int defaultValue,
-                int userId) {
-            Integer value = mSystemIntSettings.get(setting);
-            if (value == null) {
-                return defaultValue;
-            } else {
-                return value;
-            }
-        }
-
-        @Override
-        public void putSystemIntForUser(ContentResolver resolver, String setting, int value,
-                int userId) {
-            mSystemIntSettings.put(setting, value);
-        }
-
-        @Override
         public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
                 int userId) {
             Integer value = mSecureIntSettings.get(setting);
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 7cba280..ccf2aaf 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -192,11 +192,15 @@
         assertTrue(FileUtils.deleteContents(storageDir));
     }
 
+    protected void assertNotEquals(long expected, long actual) {
+        assertTrue(expected != actual);
+    }
+
     protected static void assertArrayEquals(byte[] expected, byte[] actual) {
         assertTrue(Arrays.equals(expected, actual));
     }
 
-    protected static void assertArrayNotSame(byte[] expected, byte[] actual) {
+    protected static void assertArrayNotEquals(byte[] expected, byte[] actual) {
         assertFalse(Arrays.equals(expected, actual));
     }
 }
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 ff25172..2e4c74f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -23,6 +23,7 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
+
 import static org.mockito.Mockito.verify;
 
 import android.app.admin.PasswordMetrics;
@@ -97,15 +98,18 @@
         final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
         enableSyntheticPassword();
         // Performs migration
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                    .getResponseCode());
         assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
 
         // SP-based verification
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
-        assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                        .getResponseCode());
+        assertArrayNotEquals(primaryStorageKey,
+                mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
     private void initializeCredentialUnderSP(String password, int userId) throws RemoteException {
@@ -126,8 +130,9 @@
         mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD,
                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
         mGateKeeperService.clearSecureUserId(PRIMARY_USER_ID);
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                        .getResponseCode());
         assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
@@ -136,11 +141,13 @@
         final String BADPASSWORD = "testSyntheticPasswordVerifyCredential-badpassword";
 
         initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                        .getResponseCode());
 
-        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
-                mService.verifyCredential(BADPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
+                BADPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                    .getResponseCode());
     }
 
     public void testSyntheticPasswordClearCredential() throws RemoteException {
@@ -157,9 +164,10 @@
         // set a new password
         mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
-        assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                    .getResponseCode());
+        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
     public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
@@ -176,9 +184,10 @@
         // set a new password
         mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
-        assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                    .getResponseCode());
+        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
     public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
@@ -190,15 +199,15 @@
         // Untrusted change password
         mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
-        assertNotSame(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-        assertNotSame(sid ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+        assertNotEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
 
         // Verify the password
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                    .getResponseCode());
     }
 
-
     public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
         final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd";
         disableSyntheticPassword();
@@ -215,16 +224,20 @@
 
         // do migration
         enableSyntheticPassword();
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                        .getResponseCode());
 
         // verify
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                        .getResponseCode());
         assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
-        assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
-        assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
+        assertArrayNotEquals(primaryStorageKey,
+                mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+        assertArrayNotEquals(profileStorageKey,
+                mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
         assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
         assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
     }
@@ -247,20 +260,26 @@
 
         // do migration
         enableSyntheticPassword();
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                        .getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                0, MANAGED_PROFILE_USER_ID).getResponseCode());
 
         // verify
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                        .getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                0, MANAGED_PROFILE_USER_ID).getResponseCode());
         assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
-        assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
-        assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
+        assertArrayNotEquals(primaryStorageKey,
+                mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+        assertArrayNotEquals(profileStorageKey,
+                mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
         assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
         assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
     }
@@ -288,9 +307,9 @@
         metric.quality = PASSWORD_QUALITY_SOMETHING;
         verify(mDevicePolicyManager).setActivePasswordState(metric, PRIMARY_USER_ID);
 
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0,
-                        PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID)
+                    .getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
@@ -304,7 +323,8 @@
         long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
         assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                0, PRIMARY_USER_ID).getResponseCode();
         assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, handle,
@@ -312,8 +332,9 @@
         mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
                 handle, TOKEN.getBytes(), PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
 
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID)
+                        .getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
@@ -328,7 +349,8 @@
         long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
         assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                0, PRIMARY_USER_ID).getResponseCode();
         assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD,
@@ -337,12 +359,14 @@
         mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
                 handle, TOKEN.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
 
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                    .getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
-    public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration() throws RemoteException {
+    public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration()
+            throws RemoteException {
         final String TOKEN = "some-high-entropy-secure-token";
         enableSyntheticPassword();
         long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
@@ -351,7 +375,8 @@
         assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
     }
 
-    public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration() throws RemoteException {
+    public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration()
+            throws RemoteException {
         final String TOKEN = "some-high-entropy-secure-token";
         initializeCredentialUnderSP(null, PRIMARY_USER_ID);
         long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
@@ -360,7 +385,8 @@
         assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
     }
 
-    public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration() throws RemoteException {
+    public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration()
+            throws RemoteException {
         final String TOKEN = "some-high-entropy-secure-token";
         final String PASSWORD = "password";
         // Set up pre-SP user password
@@ -373,9 +399,9 @@
         // Token not activated immediately since user password exists
         assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         // Activate token (password gets migrated to SP at the same time)
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
-                        PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                    .getResponseCode());
         // Verify token is activated
         assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
@@ -422,7 +448,7 @@
         assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
     }
 
-    // b/34600579
+    // b/62213311
     //TODO: add non-migration work profile case, and unify/un-unify transition.
     //TODO: test token after user resets password
     //TODO: test token based reset after unified work challenge
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
new file mode 100644
index 0000000..1895e15
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.locksettings.recoverablekeystore;
+
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PATTERN;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PIN;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.security.keystore.AndroidKeyStoreSecretKey;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.security.recoverablekeystore.KeyDerivationParameters;
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KeySyncTaskTest {
+    private static final String KEY_ALGORITHM = "AES";
+    private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
+    private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey";
+    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+    private static final int TEST_USER_ID = 1000;
+    private static final int TEST_RECOVERY_AGENT_UID = 10009;
+    private static final int TEST_RECOVERY_AGENT_UID2 = 10010;
+    private static final long TEST_DEVICE_ID = 13295035643L;
+    private static final String TEST_APP_KEY_ALIAS = "rcleaver";
+    private static final int TEST_GENERATION_ID = 2;
+    private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD;
+    private static final String TEST_CREDENTIAL = "password1234";
+    private static final byte[] THM_ENCRYPTED_RECOVERY_KEY_HEADER =
+            "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
+
+    @Mock private PlatformKeyManager mPlatformKeyManager;
+    @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage;
+
+    private RecoverySnapshotStorage mRecoverySnapshotStorage;
+    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private File mDatabaseFile;
+    private KeyPair mKeyPair;
+    private AndroidKeyStoreSecretKey mWrappingKey;
+    private PlatformEncryptionKey mEncryptKey;
+
+    private KeySyncTask mKeySyncTask;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+        mKeyPair = SecureBox.genKeyPair();
+
+        mRecoverySnapshotStorage = new RecoverySnapshotStorage();
+
+        mKeySyncTask = new KeySyncTask(
+                mRecoverableKeyStoreDb,
+                mRecoverySnapshotStorage,
+                mSnapshotListenersStorage,
+                TEST_USER_ID,
+                TEST_CREDENTIAL_TYPE,
+                TEST_CREDENTIAL,
+                /*credentialUpdated=*/ false,
+                () -> mPlatformKeyManager);
+
+        mWrappingKey = generateAndroidKeyStoreKey();
+        mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey);
+        when(mPlatformKeyManager.getDecryptKey(TEST_USER_ID)).thenReturn(
+                new PlatformDecryptionKey(TEST_GENERATION_ID, mWrappingKey));
+    }
+
+    @After
+    public void tearDown() {
+        mRecoverableKeyStoreDb.close();
+        mDatabaseFile.delete();
+    }
+
+    @Test
+    public void isPin_isTrueForNumericString() {
+        assertTrue(KeySyncTask.isPin("3298432574398654376547"));
+    }
+
+    @Test
+    public void isPin_isFalseForStringContainingLetters() {
+        assertFalse(KeySyncTask.isPin("398i54369548654"));
+    }
+
+    @Test
+    public void isPin_isFalseForStringContainingSymbols() {
+        assertFalse(KeySyncTask.isPin("-3987543643"));
+    }
+
+    @Test
+    public void hashCredentials_returnsSameHashForSameCredentialsAndSalt() {
+        String credentials = "password1234";
+        byte[] salt = randomBytes(16);
+
+        assertArrayEquals(
+                KeySyncTask.hashCredentials(salt, credentials),
+                KeySyncTask.hashCredentials(salt, credentials));
+    }
+
+    @Test
+    public void hashCredentials_returnsDifferentHashForDifferentCredentials() {
+        byte[] salt = randomBytes(16);
+
+        assertFalse(
+                Arrays.equals(
+                    KeySyncTask.hashCredentials(salt, "password1234"),
+                    KeySyncTask.hashCredentials(salt, "password12345")));
+    }
+
+    @Test
+    public void hashCredentials_returnsDifferentHashForDifferentSalt() {
+        String credentials = "wowmuch";
+
+        assertFalse(
+                Arrays.equals(
+                        KeySyncTask.hashCredentials(randomBytes(64), credentials),
+                        KeySyncTask.hashCredentials(randomBytes(64), credentials)));
+    }
+
+    @Test
+    public void hashCredentials_returnsDifferentHashEvenIfConcatIsSame() {
+        assertFalse(
+                Arrays.equals(
+                        KeySyncTask.hashCredentials(utf8Bytes("123"), "4567"),
+                        KeySyncTask.hashCredentials(utf8Bytes("1234"), "567")));
+    }
+
+    @Test
+    public void getUiFormat_returnsPinIfPin() {
+        assertEquals(TYPE_PIN,
+                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234"));
+    }
+
+    @Test
+    public void getUiFormat_returnsPasswordIfPassword() {
+        assertEquals(TYPE_PASSWORD,
+                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a"));
+    }
+
+    @Test
+    public void getUiFormat_returnsPatternIfPattern() {
+        assertEquals(TYPE_PATTERN,
+                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234"));
+
+    }
+
+    @Test
+    public void run_doesNotSendAnythingIfNoKeysToSync() throws Exception {
+        mKeySyncTask.run();
+
+        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
+    }
+
+    @Test
+    public void run_doesNotSendAnythingIfSnapshotIsUpToDate() throws Exception {
+        mKeySyncTask.run();
+
+        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
+    }
+
+    @Test
+    public void run_doesNotSendAnythingIfNoRecoveryAgentSet() throws Exception {
+        SecretKey applicationKey = generateKey();
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+        mRecoverableKeyStoreDb.insertKey(
+                TEST_USER_ID,
+                TEST_RECOVERY_AGENT_UID,
+                TEST_APP_KEY_ALIAS,
+                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
+        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+
+        mKeySyncTask.run();
+
+        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
+    }
+
+    @Test
+    public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception {
+        SecretKey applicationKey = generateKey();
+        mRecoverableKeyStoreDb.setServerParameters(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID);
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+        mRecoverableKeyStoreDb.insertKey(
+                TEST_USER_ID,
+                TEST_RECOVERY_AGENT_UID,
+                TEST_APP_KEY_ALIAS,
+                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
+
+        mKeySyncTask.run();
+
+        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
+    }
+
+    @Test
+    public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception {
+        SecretKey applicationKey = generateKey();
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+        mRecoverableKeyStoreDb.insertKey(
+                TEST_USER_ID,
+                TEST_RECOVERY_AGENT_UID,
+                TEST_APP_KEY_ALIAS,
+                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
+        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+
+        mKeySyncTask.run();
+
+        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
+    }
+
+    @Test
+    public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception {
+
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
+        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+        SecretKey applicationKey =
+                addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+        mKeySyncTask.run();
+
+        KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+        KeyDerivationParameters keyDerivationParameters =
+                recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters();
+        assertThat(keyDerivationParameters.getAlgorithm()).isEqualTo(
+                KeyDerivationParameters.ALGORITHM_SHA256);
+        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
+        byte[] lockScreenHash = KeySyncTask.hashCredentials(
+                keyDerivationParameters.getSalt(),
+                TEST_CREDENTIAL);
+        Long counterId = mRecoverableKeyStoreDb.getCounterId(TEST_USER_ID, TEST_RECOVERY_AGENT_UID);
+        assertThat(counterId).isNotNull();
+        byte[] recoveryKey = decryptThmEncryptedKey(
+                lockScreenHash,
+                recoveryData.getEncryptedRecoveryKeyBlob(),
+                /*vaultParams=*/ KeySyncUtils.packVaultParams(
+                        mKeyPair.getPublic(),
+                        counterId,
+                        /*maxAttempts=*/ 10,
+                        TEST_DEVICE_ID));
+        List<KeyEntryRecoveryData> applicationKeys = recoveryData.getApplicationKeyBlobs();
+        assertThat(applicationKeys).hasSize(1);
+        KeyEntryRecoveryData keyData = applicationKeys.get(0);
+        assertEquals(TEST_APP_KEY_ALIAS, keyData.getAlias());
+        assertThat(keyData.getAlias()).isEqualTo(keyData.getAlias());
+        byte[] appKey = KeySyncUtils.decryptApplicationKey(
+                recoveryKey, keyData.getEncryptedKeyMaterial());
+        assertThat(appKey).isEqualTo(applicationKey.getEncoded());
+    }
+
+    @Test
+    public void run_setsCorrectSnapshotVersion() throws Exception {
+
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
+        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+        SecretKey applicationKey =
+                addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+
+        mKeySyncTask.run();
+
+        KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+        assertThat(recoveryData.getSnapshotVersion()).isEqualTo(1); // default value;
+        mRecoverableKeyStoreDb.setShouldCreateSnapshot(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, true);
+
+        mKeySyncTask.run();
+
+        recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+        assertThat(recoveryData.getSnapshotVersion()).isEqualTo(2); // Updated
+    }
+
+    @Test
+    public void run_sendsEncryptedKeysWithTwoRegisteredAgents() throws Exception {
+
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, mKeyPair.getPublic());
+        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(true);
+        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_APP_KEY_ALIAS);
+        mKeySyncTask.run();
+
+        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
+        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2);
+    }
+
+    @Test
+    public void run_doesnSendKeyToNonregisteredAgent() throws Exception {
+
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, mKeyPair.getPublic());
+        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(false);
+        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_APP_KEY_ALIAS);
+        mKeySyncTask.run();
+
+        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
+        verify(mSnapshotListenersStorage, never()).
+                recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2);
+    }
+
+    private SecretKey addApplicationKey(int userId, int recoveryAgentUid, String alias)
+            throws Exception{
+        SecretKey applicationKey = generateKey();
+        mRecoverableKeyStoreDb.setServerParameters(
+                userId, recoveryAgentUid, TEST_DEVICE_ID);
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, TEST_GENERATION_ID);
+
+        // Newly added key is not synced.
+        mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, recoveryAgentUid, true);
+
+        mRecoverableKeyStoreDb.insertKey(
+                userId,
+                recoveryAgentUid,
+                alias,
+                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
+        return applicationKey;
+    }
+
+    private byte[] decryptThmEncryptedKey(
+            byte[] lockScreenHash, byte[] encryptedKey, byte[] vaultParams) throws Exception {
+        byte[] locallyEncryptedKey = SecureBox.decrypt(
+                mKeyPair.getPrivate(),
+                /*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash),
+                /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
+                encryptedKey
+        );
+        return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey);
+    }
+
+    private SecretKey generateKey() throws Exception {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
+        keyGenerator.init(/*keySize=*/ 256);
+        return keyGenerator.generateKey();
+    }
+
+    private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(
+                KEY_ALGORITHM,
+                ANDROID_KEY_STORE_PROVIDER);
+        keyGenerator.init(new KeyGenParameterSpec.Builder(
+                WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                .build());
+        return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
+    }
+
+    private static byte[] utf8Bytes(String s) {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
+
+    private static byte[] randomBytes(int n) {
+        byte[] bytes = new byte[n];
+        new Random().nextBytes(bytes);
+        return bytes;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index c328dda..114da1a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -30,9 +30,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
 import java.security.KeyPair;
 import java.security.MessageDigest;
+import java.security.PublicKey;
 import java.util.Arrays;
 import java.util.Map;
 import java.util.Random;
@@ -55,6 +58,10 @@
             utf8Bytes("snQzsbvclkSsG6PwasAp1oFLzbq3KtFe");
     private static final byte[] RECOVERY_CLAIM_HEADER =
             "V1 KF_claim".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] RECOVERY_RESPONSE_HEADER =
+            "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
+    private static final int PUBLIC_KEY_LENGTH_BYTES = 65;
+    private static final int VAULT_PARAMS_LENGTH_BYTES = 85;
 
     @Test
     public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception {
@@ -173,6 +180,42 @@
     }
 
     @Test
+    public void decryptRecoveryClaimResponse_decryptsAValidResponse() throws Exception {
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+        byte[] vaultParams = randomBytes(100);
+        byte[] recoveryKey = randomBytes(32);
+        byte[] encryptedPayload = SecureBox.encrypt(
+                /*theirPublicKey=*/ null,
+                /*sharedSecret=*/ keyClaimant,
+                /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*payload=*/ recoveryKey);
+
+        byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse(
+                keyClaimant, vaultParams, encryptedPayload);
+
+        assertArrayEquals(recoveryKey, decrypted);
+    }
+
+    @Test
+    public void decryptRecoveryClaimResponse_throwsIfCannotDecrypt() throws Exception {
+        byte[] vaultParams = randomBytes(100);
+        byte[] recoveryKey = randomBytes(32);
+        byte[] encryptedPayload = SecureBox.encrypt(
+                /*theirPublicKey=*/ null,
+                /*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(),
+                /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*payload=*/ recoveryKey);
+
+        try {
+            KeySyncUtils.decryptRecoveryClaimResponse(
+                    KeySyncUtils.generateKeyClaimant(), vaultParams, encryptedPayload);
+            fail("Did not throw decrypting with bad keyClaimant");
+        } catch (AEADBadTagException error) {
+            // expected
+        }
+    }
+
+    @Test
     public void encryptRecoveryClaim_encryptsLockScreenAndKeyClaimant() throws Exception {
         KeyPair keyPair = SecureBox.genKeyPair();
         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
@@ -298,6 +341,83 @@
         }
     }
 
+    @Test
+    public void packVaultParams_returns85Bytes() throws Exception {
+        PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic();
+
+        byte[] packedForm = KeySyncUtils.packVaultParams(
+                thmPublicKey,
+                /*counterId=*/ 1001L,
+                /*maxAttempts=*/ 10,
+                /*deviceId=*/ 1L);
+
+        assertEquals(VAULT_PARAMS_LENGTH_BYTES, packedForm.length);
+    }
+
+    @Test
+    public void packVaultParams_encodesPublicKeyInFirst65Bytes() throws Exception {
+        PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic();
+
+        byte[] packedForm = KeySyncUtils.packVaultParams(
+                thmPublicKey,
+                /*counterId=*/ 1001L,
+                /*maxAttempts=*/ 10,
+                /*deviceId=*/ 1L);
+
+        assertArrayEquals(
+                SecureBox.encodePublicKey(thmPublicKey),
+                Arrays.copyOf(packedForm, PUBLIC_KEY_LENGTH_BYTES));
+    }
+
+    @Test
+    public void packVaultParams_encodesCounterIdAsSecondParam() throws Exception {
+        long counterId = 103502L;
+
+        byte[] packedForm = KeySyncUtils.packVaultParams(
+                SecureBox.genKeyPair().getPublic(),
+                counterId,
+                /*maxAttempts=*/ 10,
+                /*deviceId=*/ 1L);
+
+        ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES);
+        assertEquals(counterId, byteBuffer.getLong());
+    }
+
+    @Test
+    public void packVaultParams_encodesDeviceIdAsThirdParam() throws Exception {
+        long deviceId = 102942158152L;
+
+        byte[] packedForm = KeySyncUtils.packVaultParams(
+                SecureBox.genKeyPair().getPublic(),
+                /*counterId=*/ 10021L,
+                /*maxAttempts=*/ 10,
+                deviceId);
+
+        ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES);
+        assertEquals(deviceId, byteBuffer.getLong());
+    }
+
+    @Test
+    public void packVaultParams_encodesMaxAttemptsAsLastParam() throws Exception {
+        int maxAttempts = 10;
+
+        byte[] packedForm = KeySyncUtils.packVaultParams(
+                SecureBox.genKeyPair().getPublic(),
+                /*counterId=*/ 1001L,
+                maxAttempts,
+                /*deviceId=*/ 1L);
+
+        ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + 2 * Long.BYTES);
+        assertEquals(maxAttempts, byteBuffer.getInt());
+    }
+
+
     private static byte[] randomBytes(int n) {
         byte[] bytes = new byte[n];
         new Random().nextBytes(bytes);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index e20f664..b1bff70 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -29,7 +29,6 @@
 
 import android.app.KeyguardManager;
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.support.test.InstrumentationRegistry;
@@ -79,7 +78,7 @@
         mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
         mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
         mPlatformKeyManager = new PlatformKeyManager(
-                USER_ID_FIXTURE, mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
+                mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
 
         when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
         when(mContext.getSystemServiceName(any())).thenReturn("test");
@@ -94,7 +93,7 @@
 
     @Test
     public void init_createsEncryptKeyWithCorrectAlias() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
                 eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
@@ -104,14 +103,14 @@
 
     @Test
     public void init_createsEncryptKeyWithCorrectPurposes() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         assertEquals(KeyProperties.PURPOSE_ENCRYPT, getEncryptKeyProtection().getPurposes());
     }
 
     @Test
     public void init_createsEncryptKeyWithCorrectPaddings() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         assertArrayEquals(
                 new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
@@ -120,7 +119,7 @@
 
     @Test
     public void init_createsEncryptKeyWithCorrectBlockModes() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         assertArrayEquals(
                 new String[] { KeyProperties.BLOCK_MODE_GCM },
@@ -129,14 +128,14 @@
 
     @Test
     public void init_createsEncryptKeyWithoutAuthenticationRequired() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         assertFalse(getEncryptKeyProtection().isUserAuthenticationRequired());
     }
 
     @Test
     public void init_createsDecryptKeyWithCorrectAlias() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
                 eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
@@ -146,14 +145,14 @@
 
     @Test
     public void init_createsDecryptKeyWithCorrectPurposes() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         assertEquals(KeyProperties.PURPOSE_DECRYPT, getDecryptKeyProtection().getPurposes());
     }
 
     @Test
     public void init_createsDecryptKeyWithCorrectPaddings() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         assertArrayEquals(
                 new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
@@ -162,7 +161,7 @@
 
     @Test
     public void init_createsDecryptKeyWithCorrectBlockModes() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         assertArrayEquals(
                 new String[] { KeyProperties.BLOCK_MODE_GCM },
@@ -171,14 +170,14 @@
 
     @Test
     public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
     }
 
     @Test
     public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         assertEquals(
                 USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS,
@@ -187,7 +186,7 @@
 
     @Test
     public void init_createsDecryptKeyBoundToTheUsersAuthentication() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         assertEquals(
                 USER_ID_FIXTURE,
@@ -196,7 +195,7 @@
 
     @Test
     public void init_createsBothKeysWithSameMaterial() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy, times(2)).setEntry(any(), mEntryArgumentCaptor.capture(), any());
         List<KeyStore.Entry> entries = mEntryArgumentCaptor.getAllValues();
@@ -206,15 +205,52 @@
     }
 
     @Test
-    public void init_setsGenerationIdTo1() throws Exception {
-        mPlatformKeyManager.init();
+    public void init_savesGenerationIdToDatabase() throws Exception {
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(1, mPlatformKeyManager.getGenerationId());
+        assertEquals(1,
+                mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE));
+    }
+
+    @Test
+    public void init_setsGenerationIdTo1() throws Exception {
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
+
+        assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+    }
+
+    @Test
+    public void init_incrementsGenerationIdIfKeyIsUnavailable() throws Exception {
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
+
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
+
+        assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+    }
+
+    @Test
+    public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception {
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/decrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/encrypt")).thenReturn(true);
+
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
+
+        assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+    }
+
+    @Test
+    public void getGenerationId_returnsMinusOneIfNotInitialized() throws Exception {
+        assertEquals(-1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
     public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
-        mPlatformKeyManager.getDecryptKey();
+        mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
                 eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
@@ -223,7 +259,7 @@
 
     @Test
     public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
-        mPlatformKeyManager.getEncryptKey();
+        mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
                 eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
@@ -232,18 +268,18 @@
 
     @Test
     public void regenerate_incrementsTheGenerationId() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        mPlatformKeyManager.regenerate();
+        mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
-        assertEquals(2, mPlatformKeyManager.getGenerationId());
+        assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
     public void regenerate_generatesANewEncryptKeyWithTheCorrectAlias() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        mPlatformKeyManager.regenerate();
+        mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
                 eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
@@ -253,9 +289,9 @@
 
     @Test
     public void regenerate_generatesANewDecryptKeyWithTheCorrectAlias() throws Exception {
-        mPlatformKeyManager.init();
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        mPlatformKeyManager.regenerate();
+        mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
                 eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index b3dbf93..8a461ac 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
@@ -16,13 +16,12 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import static junit.framework.Assert.fail;
+import static junit.framework.Assert.assertNotNull;
 
 import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import android.content.Context;
-import android.security.keystore.AndroidKeyStoreProvider;
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
@@ -32,8 +31,6 @@
 
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 
-import com.google.common.collect.ImmutableMap;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -41,13 +38,10 @@
 
 import java.io.File;
 import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
 import java.security.KeyStore;
-import java.util.Arrays;
 
 import javax.crypto.Cipher;
 import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
 import javax.crypto.spec.GCMParameterSpec;
 
 @SmallTest
@@ -57,14 +51,13 @@
     private static final int TEST_GENERATION_ID = 3;
     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
     private static final String KEY_ALGORITHM = "AES";
-    private static final String SUPPORTED_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
-    private static final String UNSUPPORTED_CIPHER_ALGORITHM = "AES/CTR/NoPadding";
+    private static final int KEY_SIZE_BYTES = 32;
+    private static final String KEY_WRAP_ALGORITHM = "AES/GCM/NoPadding";
     private static final String TEST_ALIAS = "karlin";
     private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey";
     private static final int TEST_USER_ID = 1000;
     private static final int KEYSTORE_UID_SELF = -1;
     private static final int GCM_TAG_LENGTH_BITS = 128;
-    private static final int GCM_NONCE_LENGTH_BYTES = 12;
 
     private PlatformEncryptionKey mPlatformKey;
     private PlatformDecryptionKey mDecryptKey;
@@ -95,67 +88,33 @@
     }
 
     @Test
-    public void generateAndStoreKey_setsKeyInKeyStore() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(
-                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
-
-        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
-        assertTrue(keyStore.containsAlias(TEST_ALIAS));
-    }
-
-    @Test
-    public void generateAndStoreKey_storesKeyEnabledForAesGcmNoPaddingEncryptDecrypt()
-            throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(
-                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
-
-        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
-        SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
-        Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
-        cipher.init(Cipher.ENCRYPT_MODE, key);
-        byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
-        Arrays.fill(nonce, (byte) 0);
-        cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
-    }
-
-    @Test
-    public void generateAndStoreKey_storesKeyDisabledForOtherModes() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(
-                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
-
-        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
-        SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
-        Cipher cipher = Cipher.getInstance(UNSUPPORTED_CIPHER_ALGORITHM);
-
-        try {
-            cipher.init(Cipher.ENCRYPT_MODE, key);
-            fail("Should not be able to use key for " + UNSUPPORTED_CIPHER_ALGORITHM);
-        } catch (InvalidKeyException e) {
-            // expected
-        }
-    }
-
-    @Test
     public void generateAndStoreKey_storesWrappedKey() throws Exception {
         mRecoverableKeyGenerator.generateAndStoreKey(
                 mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
 
-        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
-        SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
         WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
-        SecretKey unwrappedKey = WrappedKey
-                .unwrapKeys(mDecryptKey, ImmutableMap.of(TEST_ALIAS, wrappedKey))
-                .get(TEST_ALIAS);
+        assertNotNull(wrappedKey);
+    }
 
-        // key and unwrappedKey should be equivalent. let's check!
-        byte[] plaintext = getUtf8Bytes("dtianpos");
-        Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
-        cipher.init(Cipher.ENCRYPT_MODE, key);
-        byte[] encrypted = cipher.doFinal(plaintext);
-        byte[] iv = cipher.getIV();
-        cipher.init(Cipher.DECRYPT_MODE, unwrappedKey, new GCMParameterSpec(128, iv));
-        byte[] decrypted = cipher.doFinal(encrypted);
-        assertArrayEquals(decrypted, plaintext);
+    @Test
+    public void generateAndStoreKey_returnsRawMaterialOfCorrectLength() throws Exception {
+        byte[] rawKey = mRecoverableKeyGenerator.generateAndStoreKey(
+                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
+
+        assertEquals(KEY_SIZE_BYTES, rawKey.length);
+    }
+
+    @Test
+    public void generateAndStoreKey_storesTheWrappedVersionOfTheRawMaterial() throws Exception {
+        byte[] rawMaterial = mRecoverableKeyGenerator.generateAndStoreKey(
+                mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
+
+        WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
+        Cipher cipher = Cipher.getInstance(KEY_WRAP_ALGORITHM);
+        cipher.init(Cipher.DECRYPT_MODE, mDecryptKey.getKey(),
+                new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
+        byte[] unwrappedMaterial = cipher.doFinal(wrappedKey.getKeyMaterial());
+        assertArrayEquals(rawMaterial, unwrappedMaterial);
     }
 
     private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 35b18b1..ac2d36b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -19,16 +19,30 @@
 import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
 import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
 
+import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
 import android.content.Context;
-import android.os.RemoteException;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+import android.security.keystore.AndroidKeyStoreSecretKey;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
 import android.security.recoverablekeystore.KeyDerivationParameters;
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
 import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
 import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
 import android.support.test.InstrumentationRegistry;
@@ -37,8 +51,10 @@
 
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 import org.junit.After;
 import org.junit.Before;
@@ -49,6 +65,13 @@
 
 import java.io.File;
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.Executors;
+import java.util.Map;
+import java.util.Random;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -73,29 +96,79 @@
     private static final byte[] TEST_SALT = getUtf8Bytes("salt");
     private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
     private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
-    private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params");
+    private static final byte[] TEST_VAULT_PARAMS = new byte[] {
+        // backend_key
+        (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d,
+        (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda,
+        (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6,
+        (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95,
+        (byte) 0x0f, (byte) 0x10, (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0,
+        (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d,
+        (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b,
+        (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5,
+        (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78,
+        (byte) 0x52, (byte) 0xfa,
+        // counter_id
+        (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00,
+        // device_parameter
+        (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x0,
+        // max_attempts
+        (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00};
+    private static final int TEST_GENERATION_ID = 2;
     private static final int TEST_USER_ID = 10009;
     private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+    private static final byte[] RECOVERY_RESPONSE_HEADER =
+            "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
+    private static final String TEST_ALIAS = "nick";
+    private static final int RECOVERABLE_KEY_SIZE_BYTES = 32;
+    private static final int GENERATION_ID = 1;
+    private static final byte[] NONCE = getUtf8Bytes("nonce");
+    private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial");
+    private static final String KEY_ALGORITHM = "AES";
+    private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
+    private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey";
 
     @Mock private Context mMockContext;
+    @Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
+    @Mock private KeyguardManager mKeyguardManager;
+    @Mock private PlatformKeyManager mPlatformKeyManager;
 
     private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
     private File mDatabaseFile;
     private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
     private RecoverySessionStorage mRecoverySessionStorage;
+    private RecoverySnapshotStorage mRecoverySnapshotStorage;
+    private PlatformEncryptionKey mPlatformEncryptionKey;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         Context context = InstrumentationRegistry.getTargetContext();
         mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
         mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+
         mRecoverySessionStorage = new RecoverySessionStorage();
+
+        when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
+        when(mMockContext.getSystemServiceName(any())).thenReturn("test");
+        when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+        when(mKeyguardManager.isDeviceSecure(TEST_USER_ID)).thenReturn(true);
+
+        mPlatformEncryptionKey =
+                new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey());
+        when(mPlatformKeyManager.getEncryptKey(anyInt())).thenReturn(mPlatformEncryptionKey);
+
         mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
                 mMockContext,
                 mRecoverableKeyStoreDb,
-                mRecoverySessionStorage);
+                mRecoverySessionStorage,
+                Executors.newSingleThreadExecutor(),
+                mRecoverySnapshotStorage,
+                mMockListenersStorage,
+                mPlatformKeyManager);
     }
 
     @After
@@ -105,22 +178,64 @@
     }
 
     @Test
+    public void generateAndStoreKey_storesTheKey() throws Exception {
+        int uid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
+
+        mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
+
+        assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
+
+        assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+    }
+
+    @Test
+    public void generateAndStoreKey_returnsAKeyOfAppropriateSize() throws Exception {
+        assertThat(mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS))
+                .hasLength(RECOVERABLE_KEY_SIZE_BYTES);
+    }
+
+    @Test
+    public void removeKey_removesAKey() throws Exception {
+        int uid = Binder.getCallingUid();
+        mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
+
+        mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
+
+        assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNull();
+    }
+
+    @Test
+    public void removeKey_UpdatesShouldCreateSnapshot() throws Exception {
+        int uid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
+
+        mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
+        // Pretend that key was synced
+        mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+
+        mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
+
+        assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+    }
+
+    @Test
     public void startRecoverySession_checksPermissionFirst() throws Exception {
         mRecoverableKeyStoreManager.startRecoverySession(
                 TEST_SESSION_ID,
                 TEST_PUBLIC_KEY,
                 TEST_VAULT_PARAMS,
                 TEST_VAULT_CHALLENGE,
-                ImmutableList.of(new KeyStoreRecoveryMetadata(
-                        TYPE_LOCKSCREEN,
-                        TYPE_PASSWORD,
-                        KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
-                        TEST_SECRET)),
-                TEST_USER_ID);
+                ImmutableList.of(
+                        new KeyStoreRecoveryMetadata(
+                                TYPE_LOCKSCREEN,
+                                TYPE_PASSWORD,
+                                KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                TEST_SECRET)));
 
-        verify(mMockContext, times(1)).enforceCallingOrSelfPermission(
-                eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE),
-                any());
+        verify(mMockContext, times(1))
+                .enforceCallingOrSelfPermission(
+                        eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE), any());
     }
 
     @Test
@@ -130,16 +245,16 @@
                 TEST_PUBLIC_KEY,
                 TEST_VAULT_PARAMS,
                 TEST_VAULT_CHALLENGE,
-                ImmutableList.of(new KeyStoreRecoveryMetadata(
-                        TYPE_LOCKSCREEN,
-                        TYPE_PASSWORD,
-                        KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
-                        TEST_SECRET)),
-                TEST_USER_ID);
+                ImmutableList.of(
+                        new KeyStoreRecoveryMetadata(
+                                TYPE_LOCKSCREEN,
+                                TYPE_PASSWORD,
+                                KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                TEST_SECRET)));
 
         assertEquals(1, mRecoverySessionStorage.size());
-        RecoverySessionStorage.Entry entry = mRecoverySessionStorage.get(
-                TEST_USER_ID, TEST_SESSION_ID);
+        RecoverySessionStorage.Entry entry =
+                mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID);
         assertArrayEquals(TEST_SECRET, entry.getLskfHash());
         assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
     }
@@ -152,11 +267,11 @@
                     TEST_PUBLIC_KEY,
                     TEST_VAULT_PARAMS,
                     TEST_VAULT_CHALLENGE,
-                    ImmutableList.of(),
-                    TEST_USER_ID);
-        } catch (RemoteException e) {
-            assertEquals("Only a single KeyStoreRecoveryMetadata is supported",
-                    e.getMessage());
+                    ImmutableList.of());
+            fail("should have thrown");
+        } catch (ServiceSpecificException e) {
+            assertThat(e.getMessage()).startsWith(
+                    "Only a single KeyStoreRecoveryMetadata is supported");
         }
     }
 
@@ -168,19 +283,278 @@
                     getUtf8Bytes("0"),
                     TEST_VAULT_PARAMS,
                     TEST_VAULT_CHALLENGE,
-                    ImmutableList.of(new KeyStoreRecoveryMetadata(
-                            TYPE_LOCKSCREEN,
-                            TYPE_PASSWORD,
-                            KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
-                            TEST_SECRET)),
-                    TEST_USER_ID);
-        } catch (RemoteException e) {
-            assertEquals("Not a valid X509 key",
-                    e.getMessage());
+                    ImmutableList.of(
+                            new KeyStoreRecoveryMetadata(
+                                    TYPE_LOCKSCREEN,
+                                    TYPE_PASSWORD,
+                                    KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                    TEST_SECRET)));
+            fail("should have thrown");
+        } catch (ServiceSpecificException e) {
+            assertEquals("Not a valid X509 key", e.getMessage());
         }
     }
 
+    @Test
+    public void startRecoverySession_throwsIfPublicKeysMismatch() throws Exception {
+        byte[] vaultParams = TEST_VAULT_PARAMS.clone();
+        vaultParams[1] ^= (byte) 1;  // Flip 1 bit
+        try {
+            mRecoverableKeyStoreManager.startRecoverySession(
+                    TEST_SESSION_ID,
+                    TEST_PUBLIC_KEY,
+                    vaultParams,
+                    TEST_VAULT_CHALLENGE,
+                    ImmutableList.of(
+                            new KeyStoreRecoveryMetadata(
+                                    TYPE_LOCKSCREEN,
+                                    TYPE_PASSWORD,
+                                    KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                    TEST_SECRET)));
+            fail("should have thrown");
+        } catch (ServiceSpecificException e) {
+            assertThat(e.getMessage()).contains("do not match");
+        }
+    }
+
+    @Test
+    public void recoverKeys_throwsIfNoSessionIsPresent() throws Exception {
+        try {
+            mRecoverableKeyStoreManager.recoverKeys(
+                    TEST_SESSION_ID,
+                    /*recoveryKeyBlob=*/ randomBytes(32),
+                    /*applicationKeys=*/ ImmutableList.of(
+                            new KeyEntryRecoveryData("alias", randomBytes(32))
+                    ));
+            fail("should have thrown");
+        } catch (ServiceSpecificException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void recoverKeys_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                        TYPE_LOCKSCREEN,
+                        TYPE_PASSWORD,
+                        KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                        TEST_SECRET)));
+
+        try {
+            mRecoverableKeyStoreManager.recoverKeys(
+                    TEST_SESSION_ID,
+                    /*encryptedRecoveryKey=*/ randomBytes(60),
+                    /*applicationKeys=*/ ImmutableList.of());
+            fail("should have thrown");
+        } catch (ServiceSpecificException e) {
+            assertThat(e.getMessage()).startsWith("Failed to decrypt recovery key");
+        }
+    }
+
+    @Test
+    public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                        TYPE_LOCKSCREEN,
+                        TYPE_PASSWORD,
+                        KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                        TEST_SECRET)));
+        byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
+                .getKeyClaimant();
+        SecretKey recoveryKey = randomRecoveryKey();
+        byte[] encryptedClaimResponse = encryptClaimResponse(
+                keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
+        KeyEntryRecoveryData badApplicationKey = new KeyEntryRecoveryData(
+                TEST_ALIAS,
+                randomBytes(32));
+
+        try {
+            mRecoverableKeyStoreManager.recoverKeys(
+                    TEST_SESSION_ID,
+                    /*encryptedRecoveryKey=*/ encryptedClaimResponse,
+                    /*applicationKeys=*/ ImmutableList.of(badApplicationKey));
+            fail("should have thrown");
+        } catch (ServiceSpecificException e) {
+            assertThat(e.getMessage()).startsWith("Failed to recover key with alias 'nick'");
+        }
+    }
+
+    @Test
+    public void recoverKeys_returnsDecryptedKeys() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                        TYPE_LOCKSCREEN,
+                        TYPE_PASSWORD,
+                        KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                        TEST_SECRET)));
+        byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
+                .getKeyClaimant();
+        SecretKey recoveryKey = randomRecoveryKey();
+        byte[] encryptedClaimResponse = encryptClaimResponse(
+                keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
+        byte[] applicationKeyBytes = randomBytes(32);
+        KeyEntryRecoveryData applicationKey = new KeyEntryRecoveryData(
+                TEST_ALIAS,
+                encryptedApplicationKey(recoveryKey, applicationKeyBytes));
+
+        Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys(
+                TEST_SESSION_ID,
+                encryptedClaimResponse,
+                ImmutableList.of(applicationKey));
+
+        assertThat(recoveredKeys).hasSize(1);
+        assertThat(recoveredKeys.get(TEST_ALIAS)).isEqualTo(applicationKeyBytes);
+    }
+
+    @Test
+    public void setSnapshotCreatedPendingIntent() throws Exception {
+        int uid = Binder.getCallingUid();
+        PendingIntent intent = PendingIntent.getBroadcast(
+                InstrumentationRegistry.getTargetContext(), /*requestCode=*/1,
+                new Intent(), /*flags=*/ 0);
+        mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent);
+        verify(mMockListenersStorage).setSnapshotListener(eq(uid), any(PendingIntent.class));
+    }
+
+    @Test
+    public void setRecoverySecretTypes() throws Exception {
+        int[] types1 = new int[]{11, 2000};
+        int[] types2 = new int[]{1, 2, 3};
+        int[] types3 = new int[]{};
+
+        mRecoverableKeyStoreManager.setRecoverySecretTypes(types1);
+        assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
+                types1);
+
+        mRecoverableKeyStoreManager.setRecoverySecretTypes(types2);
+        assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
+                types2);
+
+        mRecoverableKeyStoreManager.setRecoverySecretTypes(types3);
+        assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
+                types3);
+    }
+
+    @Test
+    public void setRecoveryStatus_forOneAlias() throws Exception {
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+        int status = 100;
+        int status2 = 200;
+        String alias = "key1";
+        WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
+        Map<String, Integer> statuses =
+                mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
+        assertThat(statuses).hasSize(1);
+        assertThat(statuses).containsEntry(alias, status);
+
+        mRecoverableKeyStoreManager.setRecoveryStatus(
+                /*packageName=*/ null, new String[] {alias}, status2);
+        statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
+        assertThat(statuses).hasSize(1);
+        assertThat(statuses).containsEntry(alias, status2); // updated
+    }
+
+    @Test
+    public void setRecoveryStatus_for2Aliases() throws Exception {
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+        int status = 100;
+        int status2 = 200;
+        int status3 = 300;
+        String alias = "key1";
+        String alias2 = "key2";
+        WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias2, wrappedKey);
+        Map<String, Integer> statuses =
+                mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
+        assertThat(statuses).hasSize(2);
+        assertThat(statuses).containsEntry(alias, status);
+        assertThat(statuses).containsEntry(alias2, status);
+
+        mRecoverableKeyStoreManager.setRecoveryStatus(
+                /*packageName=*/ null, /*aliases=*/ null, status2);
+        statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
+        assertThat(statuses).hasSize(2);
+        assertThat(statuses).containsEntry(alias, status2); // updated
+        assertThat(statuses).containsEntry(alias2, status2); // updated
+
+        mRecoverableKeyStoreManager.setRecoveryStatus(
+                /*packageName=*/ null, new String[] {alias2}, status3);
+
+        statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
+        assertThat(statuses).hasSize(2);
+        assertThat(statuses).containsEntry(alias, status2);
+        assertThat(statuses).containsEntry(alias2, status3); // updated
+
+        mRecoverableKeyStoreManager.setRecoveryStatus(
+                /*packageName=*/ null, new String[] {alias, alias2}, status);
+
+        statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
+        assertThat(statuses).hasSize(2);
+        assertThat(statuses).containsEntry(alias, status); // updated
+        assertThat(statuses).containsEntry(alias2, status); // updated
+    }
+
+    private static byte[] encryptedApplicationKey(
+            SecretKey recoveryKey, byte[] applicationKey) throws Exception {
+        return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
+                "alias", new SecretKeySpec(applicationKey, "AES")
+        )).get("alias");
+    }
+
+    private static byte[] encryptClaimResponse(
+            byte[] keyClaimant,
+            byte[] lskfHash,
+            byte[] vaultParams,
+            SecretKey recoveryKey) throws Exception {
+        byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey(
+                lskfHash, recoveryKey);
+        return SecureBox.encrypt(
+                /*theirPublicKey=*/ null,
+                /*sharedSecret=*/ keyClaimant,
+                /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*payload=*/ locallyEncryptedRecoveryKey);
+    }
+
+    private static SecretKey randomRecoveryKey() {
+        return new SecretKeySpec(randomBytes(32), "AES");
+    }
+
     private static byte[] getUtf8Bytes(String s) {
         return s.getBytes(StandardCharsets.UTF_8);
     }
+
+    private static byte[] randomBytes(int n) {
+        byte[] bytes = new byte[n];
+        new Random().nextBytes(bytes);
+        return bytes;
+    }
+
+    private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(
+                KEY_ALGORITHM,
+                ANDROID_KEY_STORE_PROVIDER);
+        keyGenerator.init(new KeyGenParameterSpec.Builder(
+                WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                .build());
+        return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
new file mode 100644
index 0000000..b9c1764
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
@@ -0,0 +1,37 @@
+package com.android.server.locksettings.recoverablekeystore;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+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 RecoverySnapshotListenersStorageTest {
+
+    private final RecoverySnapshotListenersStorage mStorage =
+            new RecoverySnapshotListenersStorage();
+
+    @Test
+    public void hasListener_isFalseForUnregisteredUid() {
+        assertFalse(mStorage.hasListener(1000));
+    }
+
+    @Test
+    public void hasListener_isTrueForRegisteredUid() {
+        int recoveryAgentUid = 1000;
+        PendingIntent intent = PendingIntent.getBroadcast(
+                InstrumentationRegistry.getTargetContext(), /*requestCode=*/1,
+                new Intent(), /*flags=*/ 0);
+        mStorage.setSnapshotListener(recoveryAgentUid, intent);
+
+        assertTrue(mStorage.hasListener(recoveryAgentUid));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
index 72b69f0..35ec23b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
@@ -274,9 +274,9 @@
 
     @Test
     public void decrypt_nullEncryptedPayload() throws Exception {
-        IllegalArgumentException expected =
+        NullPointerException expected =
                 expectThrows(
-                        IllegalArgumentException.class,
+                        NullPointerException.class,
                         () ->
                                 SecureBox.decrypt(
                                         THM_PRIVATE_KEY,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 76cbea8..b8080ab 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.locksettings.recoverablekeystore.storage;
 
+import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -27,6 +28,8 @@
 import org.junit.runner.RunWith;
 
 import android.content.Context;
+import android.content.SharedPreferences;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -34,6 +37,10 @@
 
 import java.io.File;
 import java.nio.charset.StandardCharsets;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.spec.ECGenParameterSpec;
+import java.util.List;
 import java.util.Map;
 
 @SmallTest
@@ -107,6 +114,22 @@
     }
 
     @Test
+    public void removeKey_removesAKey() {
+        int userId = 6;
+        int uid = 60001;
+        String alias = "rupertbates";
+        WrappedKey key = new WrappedKey(
+                getUtf8Bytes("nonce1"),
+                getUtf8Bytes("key1"),
+                /*platformKeyGenerationId=*/ 1);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, key);
+
+        assertTrue(mRecoverableKeyStoreDb.removeKey(uid, alias));
+
+        assertNull(mRecoverableKeyStoreDb.getKey(uid, alias));
+    }
+
+    @Test
     public void getKey_returnsNullIfNoKey() {
         WrappedKey key = mRecoverableKeyStoreDb.getKey(
                 /*userId=*/ 1, /*alias=*/ "hello");
@@ -119,10 +142,11 @@
         int userId = 12;
         int uid = 1009;
         int generationId = 6;
+        int status = 120;
         String alias = "test";
         byte[] nonce = getUtf8Bytes("nonce");
         byte[] keyMaterial = getUtf8Bytes("keymaterial");
-        WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId);
+        WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, 120);
         mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
 
         WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
@@ -130,6 +154,7 @@
         assertArrayEquals(nonce, retrievedKey.getNonce());
         assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
         assertEquals(generationId, retrievedKey.getPlatformKeyGenerationId());
+        assertEquals(status,retrievedKey.getRecoveryStatus());
     }
 
     @Test
@@ -143,7 +168,7 @@
         WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId);
         mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
 
-        Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId);
+        Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, uid, generationId);
 
         assertEquals(1, keys.size());
         assertTrue(keys.containsKey(alias));
@@ -165,7 +190,7 @@
                 userId, uid, /*alias=*/ "test", wrappedKey);
 
         Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
-                userId, /*generationId=*/ 7);
+                userId, uid, /*generationId=*/ 7);
 
         assertTrue(keys.isEmpty());
     }
@@ -180,7 +205,7 @@
                 /*userId=*/ 1, uid, /*alias=*/ "test", wrappedKey);
 
         Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
-                /*userId=*/ 2, generationId);
+                /*userId=*/ 2, uid, generationId);
 
         assertTrue(keys.isEmpty());
     }
@@ -208,7 +233,389 @@
         assertEquals(2, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId));
     }
 
+    @Test
+    public void setRecoveryStatus_withSingleKey() {
+        int userId = 12;
+        int uid = 1009;
+        int generationId = 6;
+        int status = 120;
+        int status2 = 121;
+        String alias = "test";
+        byte[] nonce = getUtf8Bytes("nonce");
+        byte[] keyMaterial = getUtf8Bytes("keymaterial");
+        WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, status);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
+
+        WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
+        assertThat(retrievedKey.getRecoveryStatus()).isEqualTo(status);
+
+        mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status2);
+
+        retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
+        assertThat(retrievedKey.getRecoveryStatus()).isEqualTo(status2);
+    }
+
+    @Test
+    public void getStatusForAllKeys_with3Keys() {
+        int userId = 12;
+        int uid = 1009;
+        int generationId = 6;
+        int status = 120;
+        int status2 = 121;
+        String alias = "test";
+        String alias2 = "test2";
+        String alias3 = "test3";
+        byte[] nonce = getUtf8Bytes("nonce");
+        byte[] keyMaterial = getUtf8Bytes("keymaterial");
+
+        WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, status);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias2, wrappedKey);
+        WrappedKey wrappedKey2 = new WrappedKey(nonce, keyMaterial, generationId, status);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias3, wrappedKey);
+        WrappedKey wrappedKeyWithDefaultStatus = new WrappedKey(nonce, keyMaterial, generationId);
+        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKeyWithDefaultStatus);
+
+        Map<String, Integer> statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid);
+        assertThat(statuses).hasSize(3);
+        assertThat(statuses).containsEntry(alias,
+                RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+        assertThat(statuses).containsEntry(alias2, status);
+        assertThat(statuses).containsEntry(alias3, status);
+
+        int updates = mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status2);
+        assertThat(updates).isEqualTo(1);
+        updates = mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias3, status2);
+        assertThat(updates).isEqualTo(1);
+        statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid);
+
+        assertThat(statuses).hasSize(3);
+        assertThat(statuses).containsEntry(alias, status2); // updated from default
+        assertThat(statuses).containsEntry(alias2, status);
+        assertThat(statuses).containsEntry(alias3, status2); // updated
+    }
+
+    @Test
+    public void setRecoveryStatus_withEmptyDatabase() throws Exception{
+        int uid = 1009;
+        String alias = "test";
+        int status = 120;
+        int updates = mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status);
+        assertThat(updates).isEqualTo(0); // database was empty
+    }
+
+
+    @Test
+    public void getStatusForAllKeys_withEmptyDatabase() {
+        int uid = 1009;
+        Map<String, Integer> statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid);
+        assertThat(statuses).hasSize(0);
+    }
+
+    @Test
+    public void setRecoveryServicePublicKey_replaceOldKey() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        PublicKey pubkey1 = genRandomPublicKey();
+        PublicKey pubkey2 = genRandomPublicKey();
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey2);
+    }
+
+    @Test
+    public void getRecoveryServicePublicKey_returnsNullIfNoKey() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
+
+        long serverParams = 123456L;
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
+    }
+
+    @Test
+    public void getRecoveryServicePublicKey_returnsInsertedKey() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        PublicKey pubkey = genRandomPublicKey();
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey);
+    }
+
+    @Test
+    public void getRecoveryAgents_returnsUidIfSet() throws Exception {
+        int userId = 12;
+        int uid = 190992;
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, genRandomPublicKey());
+
+        assertThat(mRecoverableKeyStoreDb.getRecoveryAgents(userId)).contains(uid);
+    }
+
+    @Test
+    public void getRecoveryAgents_returnsEmptyListIfThereAreNoAgents() throws Exception {
+        int userId = 12;
+        assertThat(mRecoverableKeyStoreDb.getRecoveryAgents(userId)).isEmpty();
+        assertThat(mRecoverableKeyStoreDb.getRecoveryAgents(userId)).isNotNull();
+    }
+
+    @Test
+    public void getRecoveryAgents_withTwoAgents() throws Exception {
+        int userId = 12;
+        int uid1 = 190992;
+        int uid2 = 190993;
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid1, genRandomPublicKey());
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid2, genRandomPublicKey());
+        List<Integer> agents = mRecoverableKeyStoreDb.getRecoveryAgents(userId);
+
+        assertThat(agents).hasSize(2);
+        assertThat(agents).contains(uid1);
+        assertThat(agents).contains(uid2);
+    }
+
+    public void setRecoverySecretTypes_emptyDefaultValue() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+                new int[]{}); // default
+    }
+
+    @Test
+    public void setRecoverySecretTypes_updateValue() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        int[] types1 = new int[]{1};
+        int[] types2 = new int[]{2};
+
+        mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1);
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+                types1);
+        mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2);
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+                types2);
+    }
+
+    @Test
+    public void setRecoverySecretTypes_withMultiElementArrays() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        int[] types1 = new int[]{11, 2000};
+        int[] types2 = new int[]{1, 2, 3};
+        int[] types3 = new int[]{};
+
+        mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1);
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+                types1);
+        mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2);
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+                types2);
+        mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types3);
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+                types3);
+    }
+
+    @Test
+    public void setRecoverySecretTypes_withDifferentUid() throws Exception {
+        int userId = 12;
+        int uid1 = 10011;
+        int uid2 = 10012;
+        int[] types1 = new int[]{1};
+        int[] types2 = new int[]{2};
+
+        mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid1, types1);
+        mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid2, types2);
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid1)).isEqualTo(
+                types1);
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid2)).isEqualTo(
+                types2);
+    }
+
+    @Test
+    public void setRecoveryServiceMetadataMethods() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+
+        PublicKey pubkey1 = genRandomPublicKey();
+        int[] types1 = new int[]{1};
+        long serverParams1 = 111L;
+
+        PublicKey pubkey2 = genRandomPublicKey();
+        int[] types2 = new int[]{2};
+        long serverParams2 = 222L;
+
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
+        mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1);
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1);
+
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+                types1);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
+                serverParams1);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey1);
+
+        // Check that the methods don't interfere with each other.
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
+        mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2);
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2);
+
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+                types2);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
+                serverParams2);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey2);
+    }
+
+    @Test
+    public void getRecoveryServicePublicKey_returnsFirstKey() throws Exception {
+        int userId = 68;
+        int uid = 12904;
+        PublicKey publicKey = genRandomPublicKey();
+
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, publicKey);
+
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId)).isEqualTo(publicKey);
+    }
+
+    @Test
+    public void setServerParameters_replaceOldValue() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        long serverParams1 = 111L;
+        long serverParams2 = 222L;
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1);
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
+                serverParams2);
+    }
+
+    @Test
+    public void getServerParameters_returnsNullIfNoValue() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+
+        PublicKey pubkey = genRandomPublicKey();
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+    }
+
+    @Test
+    public void getServerParameters_returnsInsertedValue() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        long serverParams = 123456L;
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+    }
+
+    @Test
+    public void setCounterId_defaultValueAndTwoUpdates() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        long value1 = 111L;
+        long value2 = 222L;
+        assertThat(mRecoverableKeyStoreDb.getCounterId(userId, uid)).isNull();
+
+        mRecoverableKeyStoreDb.setCounterId(userId, uid, value1);
+        assertThat(mRecoverableKeyStoreDb.getCounterId(userId, uid)).isEqualTo(
+                value1);
+
+        mRecoverableKeyStoreDb.setCounterId(userId, uid, value2);
+        assertThat(mRecoverableKeyStoreDb.getCounterId(userId, uid)).isEqualTo(
+                value2);
+    }
+
+    @Test
+    public void setSnapshotVersion_defaultValueAndTwoUpdates() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        long value1 = 111L;
+        long value2 = 222L;
+        assertThat(mRecoverableKeyStoreDb.getSnapshotVersion(userId, uid)).isNull();
+        mRecoverableKeyStoreDb.setSnapshotVersion(userId, uid, value1);
+        assertThat(mRecoverableKeyStoreDb.getSnapshotVersion(userId, uid)).isEqualTo(
+                value1);
+        mRecoverableKeyStoreDb.setSnapshotVersion(userId, uid, value2);
+        assertThat(mRecoverableKeyStoreDb.getSnapshotVersion(userId, uid)).isEqualTo(
+                value2);
+    }
+
+    @Test
+    public void setShouldCreateSnapshot_defaultValueAndTwoUpdates() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        boolean value1 = true;
+        boolean value2 = false;
+        assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isEqualTo(false);
+        mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, value1);
+        assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isEqualTo(value1);
+        mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, value2);
+        assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isEqualTo(
+                value2);
+    }
+
+    @Test
+    public void setRecoveryServiceMetadataEntry_allowsAUserToHaveTwoUids() throws Exception {
+        int userId = 12;
+        int uid1 = 10009;
+        int uid2 = 20009;
+        PublicKey pubkey = genRandomPublicKey();
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid1, pubkey);
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid2, pubkey);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid1)).isEqualTo(
+                pubkey);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid2)).isEqualTo(
+                pubkey);
+    }
+
+    @Test
+    public void setRecoveryServiceMetadataEntry_allowsTwoUsersToHaveTheSameUid() throws Exception {
+        int userId1 = 12;
+        int userId2 = 23;
+        int uid = 10009;
+        PublicKey pubkey = genRandomPublicKey();
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId1, uid, pubkey);
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId2, uid, pubkey);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId1, uid)).isEqualTo(
+                pubkey);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId2, uid)).isEqualTo(
+                pubkey);
+    }
+
+    @Test
+    public void setRecoveryServiceMetadataEntry_updatesColumnsSeparately() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        PublicKey pubkey1 = genRandomPublicKey();
+        PublicKey pubkey2 = genRandomPublicKey();
+        long serverParams = 123456L;
+
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey1);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+
+        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey1);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+
+        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+                pubkey2);
+        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+    }
+
     private static byte[] getUtf8Bytes(String s) {
         return s.getBytes(StandardCharsets.UTF_8);
     }
+
+    private static PublicKey genRandomPublicKey() throws Exception {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
+        keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"));
+        return keyPairGenerator.generateKeyPair().getPublic();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
index 6aeff28..6f93fe4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
@@ -37,6 +37,7 @@
     private static final int TEST_USER_ID = 696;
     private static final byte[] TEST_LSKF_HASH = getUtf8Bytes("lskf");
     private static final byte[] TEST_KEY_CLAIMANT = getUtf8Bytes("0000111122223333");
+    private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault params vault params");
 
     @Test
     public void size_isZeroForEmpty() {
@@ -47,7 +48,7 @@
     public void size_incrementsAfterAdd() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()));
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()));
 
         assertEquals(1, storage.size());
     }
@@ -56,7 +57,7 @@
     public void size_decrementsAfterRemove() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()));
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()));
         storage.remove(TEST_USER_ID);
 
         assertEquals(0, storage.size());
@@ -66,7 +67,7 @@
     public void remove_overwritesLskfHashMemory() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
         storage.add(TEST_USER_ID, entry);
 
         storage.remove(TEST_USER_ID);
@@ -78,7 +79,7 @@
     public void remove_overwritesKeyClaimantMemory() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
         storage.add(TEST_USER_ID, entry);
 
         storage.remove(TEST_USER_ID);
@@ -90,7 +91,7 @@
     public void destroy_overwritesLskfHashMemory() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
         storage.add(TEST_USER_ID, entry);
 
         storage.destroy();
@@ -102,7 +103,7 @@
     public void destroy_overwritesKeyClaimantMemory() {
         RecoverySessionStorage storage = new RecoverySessionStorage();
         RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
-                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture());
         storage.add(TEST_USER_ID, entry);
 
         storage.destroy();
@@ -126,6 +127,10 @@
         return Arrays.copyOf(TEST_KEY_CLAIMANT, TEST_KEY_CLAIMANT.length);
     }
 
+    private static byte[] vaultParamsFixture() {
+        return Arrays.copyOf(TEST_VAULT_PARAMS, TEST_VAULT_PARAMS.length);
+    }
+
     private static byte[] getUtf8Bytes(String s) {
         return s.getBytes(StandardCharsets.UTF_8);
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
new file mode 100644
index 0000000..2759e39
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
@@ -0,0 +1,53 @@
+package com.android.server.locksettings.recoverablekeystore.storage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverySnapshotStorageTest {
+
+    private final RecoverySnapshotStorage mRecoverySnapshotStorage = new RecoverySnapshotStorage();
+
+    @Test
+    public void get_isNullForNonExistentSnapshot() {
+        assertNull(mRecoverySnapshotStorage.get(1000));
+    }
+
+    @Test
+    public void get_returnsSetSnapshot() {
+        int userId = 1000;
+        KeyStoreRecoveryData recoveryData = new KeyStoreRecoveryData(
+                /*snapshotVersion=*/ 1,
+                new ArrayList<>(),
+                new ArrayList<>(),
+                new byte[0]);
+        mRecoverySnapshotStorage.put(userId, recoveryData);
+
+        assertEquals(recoveryData, mRecoverySnapshotStorage.get(userId));
+    }
+
+    @Test
+    public void remove_removesSnapshots() {
+        int userId = 1000;
+        KeyStoreRecoveryData recoveryData = new KeyStoreRecoveryData(
+                /*snapshotVersion=*/ 1,
+                new ArrayList<>(),
+                new ArrayList<>(),
+                new byte[0]);
+        mRecoverySnapshotStorage.put(userId, recoveryData);
+
+        mRecoverySnapshotStorage.remove(userId);
+
+        assertNull(mRecoverySnapshotStorage.get(1000));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
index 0c35fcb..c7fa62e 100644
--- a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
@@ -38,6 +38,7 @@
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
 import android.util.Log;
 
 import org.junit.AfterClass;
@@ -81,7 +82,7 @@
 
     private static final long NETWORK_CHECK_TIMEOUT_MS = 4000; // 4 sec
 
-    private static final long SCREEN_ON_DELAY_MS = 1000; // 1 sec
+    private static final long SCREEN_ON_DELAY_MS = 2000; // 2 sec
 
     private static final long BIND_SERVICE_TIMEOUT_SEC = 4;
 
@@ -214,7 +215,6 @@
         try{
             turnBatteryOff();
             setAppIdle(true);
-            SystemClock.sleep(30000);
             turnScreenOn();
             startActivityAndCheckNetworkAccess();
         } finally {
@@ -239,7 +239,6 @@
             try {
                 Log.d(TAG, testName + " Start #" + i);
                 turnScreenOn();
-                SystemClock.sleep(SCREEN_ON_DELAY_MS);
                 startActivityAndCheckNetworkAccess();
             } finally {
                 finishActivity();
@@ -287,7 +286,7 @@
     private void setAppIdle(boolean enabled) throws Exception {
         executeCommand("am set-inactive " + TEST_PKG + " " + enabled);
         assertDelayedCommandResult("am get-inactive " + TEST_PKG, "Idle=" + enabled,
-                10 /* maxTries */, 2000 /* napTimeMs */);
+                15 /* maxTries */, 2000 /* napTimeMs */);
     }
 
     private void updateRestrictBackgroundBlacklist(boolean add) throws Exception {
@@ -345,6 +344,8 @@
     private void turnScreenOn() throws Exception {
         executeCommand("input keyevent KEYCODE_WAKEUP");
         executeCommand("wm dismiss-keyguard");
+        // Wait for screen-on state to propagate through the system.
+        SystemClock.sleep(SCREEN_ON_DELAY_MS);
     }
 
     private String executeCommand(String cmd) throws IOException {
@@ -404,15 +405,38 @@
     }
 
     private static void dumpOnFailure() throws Exception {
-        Log.d(TAG, ">>> Begin network_management dump");
-        Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG,
-                TAG, executeSilentCommand("dumpsys network_management"), null);
-        Log.d(TAG, "<<< End network_management dump");
+        dump("network_management");
+        dump("netpolicy");
+        dumpUsageStats();
+    }
 
-        Log.d(TAG, ">>> Begin netpolicy dump");
-        Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG,
-                TAG, executeSilentCommand("dumpsys netpolicy"), null);
-        Log.d(TAG, "<<< End netpolicy dump");
+    private static void dumpUsageStats() throws Exception {
+        final String output = executeSilentCommand("dumpsys usagestats");
+        final StringBuilder sb = new StringBuilder();
+        final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
+        splitter.setString(output);
+        String str;
+        while (splitter.hasNext()) {
+            str = splitter.next();
+            if (str.contains("package=") && !str.contains(TEST_PKG)) {
+                continue;
+            }
+            if (str.trim().startsWith("config=") || str.trim().startsWith("time=")) {
+                continue;
+            }
+            sb.append(str).append('\n');
+        }
+        dump("usagestats", sb.toString());
+    }
+
+    private static void dump(String service) throws Exception {
+        dump(service, executeSilentCommand("dumpsys " + service));
+    }
+
+    private static void dump(String service, String dump) throws Exception {
+        Log.d(TAG, ">>> Begin dump " + service);
+        Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG, TAG, dump, null);
+        Log.d(TAG, "<<< End dump " + service);
     }
 
     private void finishActivity() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
index f3cb980..212d25d 100644
--- a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
@@ -95,41 +95,6 @@
     }
 
     @Test
-    public void testWatchlistSettings_writeSettingsToDisk() throws Exception {
-        copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
-        WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
-        settings.writeSettingsToDisk(Arrays.asList(TEST_NEW_CC_DOMAIN_CRC32),
-                Arrays.asList(TEST_NEW_CC_DOMAIN_SHA256), Arrays.asList(TEST_NEW_CC_IP_CRC32),
-                Arrays.asList(TEST_NEW_CC_IP_SHA256));
-        // Ensure old watchlist is not in memory
-        assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
-        assertFalse(settings.containsIp(TEST_CC_IP));
-        assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
-        assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
-        assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
-        assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
-        assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
-        assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
-        // Ensure new watchlist is in memory
-        assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
-        assertTrue(settings.containsIp(TEST_NEW_CC_IP));
-        // Reload settings from disk and test again
-        settings = new WatchlistSettings(mTestXmlFile);
-        // Ensure old watchlist is not in memory
-        assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
-        assertFalse(settings.containsIp(TEST_CC_IP));
-        assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
-        assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
-        assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
-        assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
-        assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
-        assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
-        // Ensure new watchlist is in memory
-        assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
-        assertTrue(settings.containsIp(TEST_NEW_CC_IP));
-    }
-
-    @Test
     public void testWatchlistSettings_writeSettingsToMemory() throws Exception {
         copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
         WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index e12a8da..cdac516 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -17,75 +17,93 @@
 package com.android.server.pm;
 
 import android.content.IIntentReceiver;
-
 import android.os.Bundle;
+import android.support.test.runner.AndroidJUnit4;
 
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.File;
 
 // runtest -c com.android.server.pm.PackageManagerServiceTest frameworks-services
-
-@SmallTest
-public class PackageManagerServiceTest extends AndroidTestCase {
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+// bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerServiceTest {
+    @Before
+    public void setUp() throws Exception {
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
     }
 
+    @Test
     public void testPackageRemoval() throws Exception {
-      class PackageSenderImpl implements PackageSender {
-        public void sendPackageBroadcast(final String action, final String pkg,
-            final Bundle extras, final int flags, final String targetPkg,
-            final IIntentReceiver finishedReceiver, final int[] userIds,
-            int[] instantUserIds) {
+        class PackageSenderImpl implements PackageSender {
+            public void sendPackageBroadcast(final String action, final String pkg,
+                    final Bundle extras, final int flags, final String targetPkg,
+                    final IIntentReceiver finishedReceiver, final int[] userIds,
+                    int[] instantUserIds) {
+            }
+
+            public void sendPackageAddedForNewUsers(String packageName,
+                    boolean sendBootComplete, boolean includeStopped, int appId,
+                    int[] userIds, int[] instantUserIds) {
+            }
+
+            @Override
+            public void notifyPackageAdded(String packageName) {
+            }
+
+            @Override
+            public void notifyPackageRemoved(String packageName) {
+            }
         }
 
-        public void sendPackageAddedForNewUsers(String packageName,
-            boolean sendBootComplete, boolean includeStopped, int appId,
-            int[] userIds, int[] instantUserIds) {
-        }
-      }
+        PackageSenderImpl sender = new PackageSenderImpl();
+        PackageSetting setting = null;
+        PackageManagerService.PackageRemovedInfo pri =
+                new PackageManagerService.PackageRemovedInfo(sender);
 
-      PackageSenderImpl sender = new PackageSenderImpl();
-      PackageSetting setting = null;
-      PackageManagerService.PackageRemovedInfo pri =
-          new PackageManagerService.PackageRemovedInfo(sender);
+        // Initial conditions: nothing there
+        Assert.assertNull(pri.removedUsers);
+        Assert.assertNull(pri.broadcastUsers);
 
-      // Initial conditions: nothing there
-      assertNull(pri.removedUsers);
-      assertNull(pri.broadcastUsers);
+        // populateUsers with nothing leaves nothing
+        pri.populateUsers(null, setting);
+        Assert.assertNull(pri.broadcastUsers);
 
-      // populateUsers with nothing leaves nothing
-      pri.populateUsers(null, setting);
-      assertNull(pri.broadcastUsers);
+        // Create a real (non-null) PackageSetting and confirm that the removed
+        // users are copied properly
+        setting = new PackageSetting("name", "realName", new File("codePath"),
+                new File("resourcePath"), "legacyNativeLibraryPathString",
+                "primaryCpuAbiString", "secondaryCpuAbiString",
+                "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0,
+                null, null);
+        pri.populateUsers(new int[] {
+                1, 2, 3, 4, 5
+        }, setting);
+        Assert.assertNotNull(pri.broadcastUsers);
+        Assert.assertEquals(5, pri.broadcastUsers.length);
+        Assert.assertNotNull(pri.instantUserIds);
+        Assert.assertEquals(0, pri.instantUserIds.length);
 
-      // Create a real (non-null) PackageSetting and confirm that the removed
-      // users are copied properly
-      setting = new PackageSetting("name", "realName", new File("codePath"),
-          new File("resourcePath"), "legacyNativeLibraryPathString",
-          "primaryCpuAbiString", "secondaryCpuAbiString",
-          "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0,
-          null, null);
-      pri.populateUsers(new int[] {1, 2, 3, 4, 5}, setting);
-      assertNotNull(pri.broadcastUsers);
-      assertEquals(5, pri.broadcastUsers.length);
+        // Exclude a user
+        pri.broadcastUsers = null;
+        final int EXCLUDED_USER_ID = 4;
+        setting.setInstantApp(true, EXCLUDED_USER_ID);
+        pri.populateUsers(new int[] {
+                1, 2, 3, EXCLUDED_USER_ID, 5
+        }, setting);
+        Assert.assertNotNull(pri.broadcastUsers);
+        Assert.assertEquals(4, pri.broadcastUsers.length);
+        Assert.assertNotNull(pri.instantUserIds);
+        Assert.assertEquals(1, pri.instantUserIds.length);
 
-      // Exclude a user
-      pri.broadcastUsers = null;
-      final int EXCLUDED_USER_ID = 4;
-      setting.setInstantApp(true, EXCLUDED_USER_ID);
-      pri.populateUsers(new int[] {1, 2, 3, EXCLUDED_USER_ID, 5}, setting);
-      assertNotNull(pri.broadcastUsers);
-      assertEquals(5 - 1, pri.broadcastUsers.length);
-
-      // TODO: test that sendApplicationHiddenForUser() actually fills in
-      // broadcastUsers
+        // TODO: test that sendApplicationHiddenForUser() actually fills in
+        // broadcastUsers
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java
index 880b77e..ff55a2b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java
@@ -205,8 +205,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                null,
-                                null,
                                 UserHandle.of(PRIMARY_USER)));
 
         verify(mContext, never())
@@ -217,33 +215,6 @@
     }
 
     @Test
-    public void startActivityAsUser_profile_successWithOption() throws Exception {
-        Bundle options = Bundle.forPair("test_key", "test_value");
-
-        mCrossProfileAppsServiceImpl.startActivityAsUser(
-                PACKAGE_ONE,
-                ACTIVITY_COMPONENT,
-                null,
-                options,
-                UserHandle.of(PROFILE_OF_PRIMARY_USER));
-
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-
-        verify(mContext)
-                .startActivityAsUser(
-                        intentCaptor.capture(),
-                        bundleCaptor.capture(),
-                        eq(UserHandle.of(PROFILE_OF_PRIMARY_USER)));
-
-        Intent intent = intentCaptor.getValue();
-        assertEquals(ACTIVITY_COMPONENT, intent.getComponent());
-
-        Bundle bundle = bundleCaptor.getValue();
-        assertEquals("test_value", bundle.getString("test_key"));
-    }
-
-    @Test
     public void startActivityAsUser_profile_notInstalled() throws Exception {
         mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
 
@@ -253,8 +224,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                null,
-                                null,
                                 UserHandle.of(PROFILE_OF_PRIMARY_USER)));
 
         verify(mContext, never())
@@ -272,8 +241,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_TWO,
                                 ACTIVITY_COMPONENT,
-                                null,
-                                null,
                                 UserHandle.of(PROFILE_OF_PRIMARY_USER)));
 
         verify(mContext, never())
@@ -293,8 +260,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                null,
-                                null,
                                 UserHandle.of(PROFILE_OF_PRIMARY_USER)));
 
         verify(mContext, never())
@@ -312,8 +277,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_ONE,
                                 new ComponentName(PACKAGE_TWO, "test"),
-                                null,
-                                null,
                                 UserHandle.of(PROFILE_OF_PRIMARY_USER)));
 
         verify(mContext, never())
@@ -331,8 +294,6 @@
                         mCrossProfileAppsServiceImpl.startActivityAsUser(
                                 PACKAGE_ONE,
                                 ACTIVITY_COMPONENT,
-                                null,
-                                null,
                                 UserHandle.of(SECONDARY_USER)));
 
         verify(mContext, never())
@@ -349,8 +310,6 @@
         mCrossProfileAppsServiceImpl.startActivityAsUser(
                 PACKAGE_ONE,
                 ACTIVITY_COMPONENT,
-                null,
-                null,
                 UserHandle.of(PRIMARY_USER));
 
         verify(mContext)
diff --git a/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java
index 2d4bc0f..029d9f1 100644
--- a/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java
+++ b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java
@@ -104,6 +104,15 @@
         return new PriorityQueue<>(mMessages);
     }
 
+    /**
+     * Optionally-overridable to allow deciphering message types
+     *
+     * @see android.util.DebugUtils#valueToString - a handy utility to use when overriding this
+     */
+    protected String messageToString(Message message) {
+        return message.toString();
+    }
+
     private void dispatch(MsgInfo msg) {
         int msgId = msg.message.what;
 
@@ -148,7 +157,7 @@
         @Override
         public String toString() {
             return "MsgInfo{" +
-                    "message=" + message +
+                    "message=" + messageToString(message) +
                     ", sendTime=" + sendTime +
                     '}';
         }
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 b62d724..7b06648 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -21,6 +21,10 @@
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import android.app.usage.UsageStatsManager;
 import android.os.FileUtils;
 import android.test.AndroidTestCase;
@@ -117,4 +121,16 @@
         assertFalse(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));
         assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_FREQUENT));
     }
+
+    public void testJobRunTime() throws Exception {
+        AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
+
+        aih.setLastJobRunTime(PACKAGE_1, USER_ID, 2000);
+        assertEquals(Long.MAX_VALUE, aih.getTimeSinceLastJobRun(PACKAGE_2, USER_ID, 0));
+        assertEquals(4000, aih.getTimeSinceLastJobRun(PACKAGE_1, USER_ID, 6000));
+
+        aih.setLastJobRunTime(PACKAGE_2, USER_ID, 6000);
+        assertEquals(1000, aih.getTimeSinceLastJobRun(PACKAGE_2, USER_ID, 7000));
+        assertEquals(5000, aih.getTimeSinceLastJobRun(PACKAGE_1, USER_ID, 7000));
+    }
 }
\ 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
index b792d82..725fb21 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -51,6 +51,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.Display;
@@ -69,6 +71,8 @@
  * Unit test for AppStandbyController.
  */
 @RunWith(AndroidJUnit4.class)
+@Presubmit
+@SmallTest
 public class AppStandbyControllerTests {
 
     private static final String PACKAGE_1 = "com.example.foo";
@@ -285,6 +289,10 @@
                 true);
     }
 
+    private void assertBucket(int bucket) {
+        assertEquals(bucket, getStandbyBucket(mController));
+    }
+
     @Test
     public void testBuckets() throws Exception {
         assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
@@ -425,4 +433,27 @@
                 REASON_PREDICTED, 2 * HOUR_MS);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
     }
+
+    @Test
+    public void testTimeout() throws Exception {
+        setChargingState(mController, false);
+
+        reportEvent(mController, USER_INTERACTION, 0);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+
+        mInjector.mElapsedRealtime = 2000;
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
+                REASON_PREDICTED, mInjector.mElapsedRealtime);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+
+        // bucketing works after timeout
+        mInjector.mElapsedRealtime = FREQUENT_THRESHOLD - 100;
+        mController.checkIdleStates(USER_ID);
+        assertBucket(STANDBY_BUCKET_WORKING_SET);
+
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
+                REASON_PREDICTED, mInjector.mElapsedRealtime);
+        assertBucket(STANDBY_BUCKET_FREQUENT);
+
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index d9ab5c8..759894b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -193,7 +193,7 @@
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation());
 
         mToken.setFillsParent(true);
-        mToken.hidden = true;
+        mToken.setHidden(true);
         mToken.sendingToBottom = true;
         // Can not specify orientation if app isn't visible even though it fills parent.
         assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation());
diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 53a5899..2bfe274 100644
--- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -110,7 +110,7 @@
         }
 
         public void notifyTransitionStarting(int transit) {
-            mListener.onAppTransitionStartingLocked(transit, null, null, null, null);
+            mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0);
         }
 
         public void notifyTransitionFinished() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index c309611..b36c7d9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -34,8 +34,10 @@
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
 import android.view.Choreographer;
 import android.view.Choreographer.FrameCallback;
 import android.view.SurfaceControl;
@@ -135,6 +137,7 @@
         assertFinishCallbackNotCalled();
     }
 
+    @FlakyTest(bugId = 71719744)
     @Test
     public void testCancel_sneakyCancelBeforeUpdate() throws Exception {
         mSurfaceAnimationRunner = new SurfaceAnimationRunner(null, () -> new ValueAnimator() {
@@ -157,9 +160,27 @@
         when(mMockAnimationSpec.getDuration()).thenReturn(200L);
         mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction,
                 this::finishedCallback);
+
+        // We need to wait for two frames: The first frame starts the animation, the second frame
+        // actually cancels the animation.
+        waitUntilNextFrame();
+        waitUntilNextFrame();
+        assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
+        verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L));
+    }
+
+    @Test
+    public void testDeferStartingAnimations() throws Exception {
+        mSurfaceAnimationRunner.deferStartingAnimations();
+        mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
+                mMockTransaction, this::finishedCallback);
+        waitUntilNextFrame();
+        assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
+        mSurfaceAnimationRunner.continueStartingAnimations();
         waitUntilNextFrame();
         assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
-        verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L));
+        mFinishCallbackLatch.await(1, SECONDS);
+        assertFinishCallbackCalled();
     }
 
     private void waitUntilNextFrame() throws Exception {
@@ -181,7 +202,7 @@
         final Animation a = new TranslateAnimation(-10, 10, 0, 0);
         a.initialize(0, 0, 0, 0);
         a.setDuration(50);
-        return new WindowAnimationSpec(a, new Point(0, 0));
+        return new WindowAnimationSpec(a, new Point(0, 0), false /* canSkipFirstFrame */);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 6f739ca..64c3037 100644
--- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -26,6 +27,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.SurfaceControl;
@@ -37,6 +39,7 @@
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -44,6 +47,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * Test class for {@link SurfaceAnimatorTest}.
@@ -61,12 +65,14 @@
 
     private SurfaceSession mSession = new SurfaceSession();
     private MyAnimatable mAnimatable;
+    private MyAnimatable mAnimatable2;
 
     @Before
     public void setUp() throws Exception {
         super.setUp();
         MockitoAnnotations.initMocks(this);
         mAnimatable = new MyAnimatable();
+        mAnimatable2 = new MyAnimatable();
     }
 
     @Test
@@ -74,15 +80,12 @@
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
-
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
-        assertNotNull(mAnimatable.mSurfaceAnimator.getAnimation());
+        assertAnimating(mAnimatable);
         verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle()));
         verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
 
         callbackCaptor.getValue().onAnimationFinished(mSpec);
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
-        assertNull(mAnimatable.mSurfaceAnimator.getAnimation());
+        assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
         assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
         // TODO: Verify reparenting once we use mPendingTransaction to reparent it back
@@ -99,8 +102,7 @@
 
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
-        assertNotNull(mAnimatable.mSurfaceAnimator.getAnimation());
+        assertAnimating(mAnimatable);
         verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
 
         // First animation was finished, but this shouldn't cancel the second animation
@@ -110,16 +112,16 @@
         // Second animation was finished
         verify(mSpec2).startAnimation(any(), any(), callbackCaptor.capture());
         callbackCaptor.getValue().onAnimationFinished(mSpec2);
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
     }
 
     @Test
     public void testCancelAnimation() throws Exception {
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertAnimating(mAnimatable);
         mAnimatable.mSurfaceAnimator.cancelAnimation();
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertNotAnimating(mAnimatable);
         verify(mSpec).onAnimationCancelled(any());
         assertTrue(mAnimatable.mFinishedCallbackCalled);
         assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
@@ -130,7 +132,7 @@
         mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         verifyZeroInteractions(mSpec);
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertAnimating(mAnimatable);
         mAnimatable.mSurfaceAnimator.endDelayingAnimationStart();
         verify(mSpec).startAnimation(any(), any(), any());
     }
@@ -141,11 +143,41 @@
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         mAnimatable.mSurfaceAnimator.cancelAnimation();
         verifyZeroInteractions(mSpec);
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
         assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
     }
 
+    @Test
+    public void testTransferAnimation() throws Exception {
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+
+        final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+                OnAnimationFinishedCallback.class);
+        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+        final SurfaceControl leash = mAnimatable.mLeash;
+
+        mAnimatable2.mSurfaceAnimator.transferAnimation(mAnimatable.mSurfaceAnimator);
+        assertNotAnimating(mAnimatable);
+        assertAnimating(mAnimatable2);
+        assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash);
+        assertFalse(mAnimatable.mPendingDestroySurfaces.contains(leash));
+        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        assertNotAnimating(mAnimatable2);
+        assertTrue(mAnimatable2.mFinishedCallbackCalled);
+        assertTrue(mAnimatable2.mPendingDestroySurfaces.contains(leash));
+    }
+
+    private void assertAnimating(MyAnimatable animatable) {
+        assertTrue(animatable.mSurfaceAnimator.isAnimating());
+        assertNotNull(animatable.mSurfaceAnimator.getAnimation());
+    }
+
+    private void assertNotAnimating(MyAnimatable animatable) {
+        assertFalse(animatable.mSurfaceAnimator.isAnimating());
+        assertNull(animatable.mSurfaceAnimator.getAnimation());
+    }
+
     private class MyAnimatable implements Animatable {
 
         final SurfaceControl mParent;
@@ -166,7 +198,7 @@
                     .build();
             mFinishedCallbackCalled = false;
             mLeash = null;
-            mSurfaceAnimator = new SurfaceAnimator(this, mFinishedCallback, sWm);
+            mSurfaceAnimator = new SurfaceAnimator(this, mFinishedCallback, Runnable::run, sWm);
         }
 
         @Override
@@ -204,6 +236,11 @@
         }
 
         @Override
+        public SurfaceControl getAnimationLeashParent() {
+            return mParent;
+        }
+
+        @Override
         public SurfaceControl getSurfaceControl() {
             return mSurface;
         }
@@ -223,8 +260,6 @@
             return 1;
         }
 
-        private final Runnable mFinishedCallback = () -> {
-            mFinishedCallbackCalled = true;
-        };
+        private final Runnable mFinishedCallback = () -> mFinishedCallbackCalled = true;
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java
new file mode 100644
index 0000000..f8db4fa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.server.wm;
+
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
+
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.ClipRectAnimation;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link WindowAnimationSpec} class.
+ *
+ *  atest FrameworksServicesTests:com.android.server.wm.WindowAnimationSpecTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowAnimationSpecTest {
+    private final SurfaceControl mSurfaceControl = mock(SurfaceControl.class);
+    private final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class);
+    private final Animation mAnimation = mock(Animation.class);
+    private final Rect mStackBounds = new Rect(0, 0, 10, 10);
+
+    @Test
+    public void testApply_clipNone() {
+        Rect windowCrop = new Rect(0, 0, 20, 20);
+        Animation a = new ClipRectAnimation(windowCrop, windowCrop);
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_NONE);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.equals(windowCrop)));
+    }
+
+    @Test
+    public void testApply_clipAfter() {
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_AFTER_ANIM);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
+        verify(mTransaction).setFinalCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.equals(mStackBounds)));
+    }
+
+    @Test
+    public void testApply_clipAfterOffsetPosition() {
+        // Stack bounds is (0, 0, 10, 10) position is (20, 40)
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation,
+                new Point(20, 40), mStackBounds, false /* canSkipFirstFrame */,
+                STACK_CLIP_AFTER_ANIM);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
+        verify(mTransaction).setFinalCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.left == 20 && rect.top == 40 && rect.right == 30
+                        && rect.bottom == 50));
+    }
+
+    @Test
+    public void testApply_clipBeforeNoAnimationBounds() {
+        // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 0, 0)
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.equals(mStackBounds)));
+    }
+
+    @Test
+    public void testApply_clipBeforeNoStackBounds() {
+        // Stack bounds is (0, 0, 0, 0) animation clip is (0, 0, 20, 20)
+        Rect windowCrop = new Rect(0, 0, 20, 20);
+        Animation a = new ClipRectAnimation(windowCrop, windowCrop);
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
+                null, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
+    }
+
+    @Test
+    public void testApply_clipBeforeSmallerAnimationClip() {
+        // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 5, 5)
+        Rect windowCrop = new Rect(0, 0, 5, 5);
+        Animation a = new ClipRectAnimation(windowCrop, windowCrop);
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.equals(windowCrop)));
+    }
+
+    @Test
+    public void testApply_clipBeforeSmallerStackClip() {
+        // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 20, 20)
+        Rect windowCrop = new Rect(0, 0, 20, 20);
+        Animation a = new ClipRectAnimation(windowCrop, windowCrop);
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.equals(mStackBounds)));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 307deb4..196b4a9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -564,6 +564,86 @@
         assertEquals(1, child2223.compareTo(child21));
     }
 
+    @Test
+    public void testPrefixOrderIndex() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+
+        final TestWindowContainer child2 = root.addChildWindow();
+
+        final TestWindowContainer child21 = child2.addChildWindow();
+        final TestWindowContainer child22 = child2.addChildWindow();
+
+        final TestWindowContainer child221 = child22.addChildWindow();
+        final TestWindowContainer child222 = child22.addChildWindow();
+        final TestWindowContainer child223 = child22.addChildWindow();
+
+        final TestWindowContainer child23 = child2.addChildWindow();
+
+        assertEquals(0, root.getPrefixOrderIndex());
+        assertEquals(1, child1.getPrefixOrderIndex());
+        assertEquals(2, child11.getPrefixOrderIndex());
+        assertEquals(3, child12.getPrefixOrderIndex());
+        assertEquals(4, child2.getPrefixOrderIndex());
+        assertEquals(5, child21.getPrefixOrderIndex());
+        assertEquals(6, child22.getPrefixOrderIndex());
+        assertEquals(7, child221.getPrefixOrderIndex());
+        assertEquals(8, child222.getPrefixOrderIndex());
+        assertEquals(9, child223.getPrefixOrderIndex());
+        assertEquals(10, child23.getPrefixOrderIndex());
+    }
+
+    @Test
+    public void testPrefixOrder_addEntireSubtree() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.build();
+        final TestWindowContainer subtree = builder.build();
+        final TestWindowContainer subtree2 = builder.build();
+
+        final TestWindowContainer child1 = subtree.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child2 = subtree2.addChildWindow();
+        final TestWindowContainer child3 = subtree2.addChildWindow();
+        subtree.addChild(subtree2, 1);
+        root.addChild(subtree, 0);
+
+        assertEquals(0, root.getPrefixOrderIndex());
+        assertEquals(1, subtree.getPrefixOrderIndex());
+        assertEquals(2, child1.getPrefixOrderIndex());
+        assertEquals(3, child11.getPrefixOrderIndex());
+        assertEquals(4, subtree2.getPrefixOrderIndex());
+        assertEquals(5, child2.getPrefixOrderIndex());
+        assertEquals(6, child3.getPrefixOrderIndex());
+    }
+
+    @Test
+    public void testPrefixOrder_remove() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+
+        final TestWindowContainer child2 = root.addChildWindow();
+
+        assertEquals(0, root.getPrefixOrderIndex());
+        assertEquals(1, child1.getPrefixOrderIndex());
+        assertEquals(2, child11.getPrefixOrderIndex());
+        assertEquals(3, child12.getPrefixOrderIndex());
+        assertEquals(4, child2.getPrefixOrderIndex());
+
+        root.removeChild(child1);
+
+        assertEquals(1, child2.getPrefixOrderIndex());
+    }
+
     /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
     private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
         private final int mLayer;
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index 67db5f4..7be203a 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import android.util.MergedConfiguration;
 import android.view.WindowManager;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,8 +44,7 @@
 /**
  * Tests for the {@link WindowState} class.
  *
- * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.wm.WindowStateTests
+ * atest FrameworksServicesTests:com.android.server.wm.WindowStateTests
  */
 @SmallTest
 @Presubmit
@@ -213,6 +211,18 @@
         testPrepareWindowToDisplayDuringRelayout(true /*wasVisible*/);
     }
 
+    @Test
+    public void testCanAffectSystemUiFlags() throws Exception {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        app.mToken.setHidden(false);
+        assertTrue(app.canAffectSystemUiFlags());
+        app.mToken.setHidden(true);
+        assertFalse(app.canAffectSystemUiFlags());
+        app.mToken.setHidden(false);
+        app.mAttrs.alpha = 0.0f;
+        assertFalse(app.canAffectSystemUiFlags());
+    }
+
     private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) {
         final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
         root.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
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 5f58744..012fc23 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
@@ -160,7 +160,6 @@
 
     /* Used so we can gain access to some protected members of the {@link WindowToken} class */
     public static class TestWindowToken extends WindowToken {
-        int adj = 0;
 
         TestWindowToken(int type, DisplayContent dc) {
             this(type, dc, false /* persistOnEmpty */);
@@ -178,11 +177,6 @@
         boolean hasWindow(WindowState w) {
             return mChildren.contains(w);
         }
-
-        @Override
-        int getAnimLayerAdjustment() {
-            return adj;
-        }
     }
 
     /* Used so we can gain access to some protected members of the {@link Task} class */
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index a1f1810..0cbda28 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -23,7 +23,6 @@
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
-import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
 
 import android.app.usage.UsageStatsManager;
 import android.os.SystemClock;
@@ -66,13 +65,7 @@
 
     // History for all users and all packages
     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;
-    private static final int FLAG_LAST_STATE = 2;
-    private static final int FLAG_PARTIAL_ACTIVE = 1;
-    private static final long PERIOD_DURATION = UsageStatsService.COMPRESS_TIME ? ONE_MINUTE
-            : 60 * ONE_MINUTE;
 
     @VisibleForTesting
     static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
@@ -89,6 +82,10 @@
     private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
     // The reason the app was put in the above bucket
     private static final String ATTR_BUCKETING_REASON = "bucketReason";
+    // The last time a job was run for this app
+    private static final String ATTR_LAST_RUN_JOB_TIME = "lastJobRunTime";
+    // The time when the forced active state can be overridden.
+    private static final String ATTR_BUCKET_TIMEOUT_TIME = "bucketTimeoutTime";
 
     // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
     private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
@@ -103,8 +100,6 @@
     private boolean mScreenOn;
 
     static class AppUsageHistory {
-        // Debug
-        final byte[] recent = new byte[HISTORY_SIZE];
         // Last used time using elapsed timebase
         long lastUsedElapsedTime;
         // Last used time using screen_on timebase
@@ -118,6 +113,13 @@
         String bucketingReason;
         // In-memory only, last bucket for which the listeners were informed
         int lastInformedBucket;
+        // The last time a job was run for this app, using elapsed timebase
+        long lastJobRunTime;
+        // When should the bucket state timeout, in elapsed timebase, if greater than
+        // lastUsedElapsedTime.
+        // This is used to keep the app in a high bucket regardless of other timeouts and
+        // predictions.
+        long bucketTimeoutTime;
     }
 
     AppIdleHistory(File storageDir, long elapsedRealtime) {
@@ -195,81 +197,47 @@
         writeScreenOnTime();
     }
 
-    public int reportUsage(String packageName, int userId, long elapsedRealtime) {
+    /**
+     * Mark the app as used and update the bucket if necessary. If there is a timeout specified
+     * that's in the future, then the usage event is temporary and keeps the app in the specified
+     * bucket at least until the timeout is reached. This can be used to keep the app in an
+     * elevated bucket for a while until some important task gets to run.
+     * @param packageName
+     * @param userId
+     * @param bucket the bucket to set the app to
+     * @param elapsedRealtime mark as used time if non-zero
+     * @param timeout set the timeout of the specified bucket, if non-zero
+     * @return
+     */
+    public int reportUsage(String packageName, int userId, int bucket, long elapsedRealtime,
+            long timeout) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, true);
 
-        shiftHistoryToNow(userHistory, elapsedRealtime);
+        if (elapsedRealtime != 0) {
+            appUsageHistory.lastUsedElapsedTime = mElapsedDuration
+                    + (elapsedRealtime - mElapsedSnapshot);
+            appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
+        }
 
-        appUsageHistory.lastUsedElapsedTime = mElapsedDuration
-                + (elapsedRealtime - mElapsedSnapshot);
-        appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
-        appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
-        if (appUsageHistory.currentBucket > STANDBY_BUCKET_ACTIVE) {
-            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
+        if (appUsageHistory.currentBucket > bucket) {
+            appUsageHistory.currentBucket = bucket;
             if (DEBUG) {
-                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory
+                        .currentBucket
                         + ", reason=" + appUsageHistory.bucketingReason);
             }
+            if (timeout > elapsedRealtime) {
+                // Convert to elapsed timebase
+                appUsageHistory.bucketTimeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot);
+            }
         }
         appUsageHistory.bucketingReason = REASON_USAGE;
 
         return appUsageHistory.currentBucket;
     }
 
-    public int reportMildUsage(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
-        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime, true);
-        if (appUsageHistory.currentBucket > STANDBY_BUCKET_WORKING_SET) {
-            appUsageHistory.currentBucket = STANDBY_BUCKET_WORKING_SET;
-            if (DEBUG) {
-                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
-                        + ", reason=" + appUsageHistory.bucketingReason);
-            }
-        }
-        // TODO: Should this be a different reason for partial usage?
-        appUsageHistory.bucketingReason = REASON_USAGE;
-
-        return appUsageHistory.currentBucket;
-    }
-
-    public void setIdle(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
-        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime, true);
-
-        shiftHistoryToNow(userHistory, elapsedRealtime);
-
-        appUsageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
-    }
-
-    private void shiftHistoryToNow(ArrayMap<String, AppUsageHistory> userHistory,
-            long elapsedRealtime) {
-        long thisPeriod = elapsedRealtime / PERIOD_DURATION;
-        // Has the period switched over? Slide all users' package histories
-        if (mLastPeriod != 0 && mLastPeriod < thisPeriod
-                && (thisPeriod - mLastPeriod) < HISTORY_SIZE - 1) {
-            int diff = (int) (thisPeriod - mLastPeriod);
-            final int NUSERS = mIdleHistory.size();
-            for (int u = 0; u < NUSERS; u++) {
-                userHistory = mIdleHistory.valueAt(u);
-                for (AppUsageHistory idleState : userHistory.values()) {
-                    // Shift left
-                    System.arraycopy(idleState.recent, diff, idleState.recent, 0,
-                            HISTORY_SIZE - diff);
-                    // Replicate last state across the diff
-                    for (int i = 0; i < diff; i++) {
-                        idleState.recent[HISTORY_SIZE - i - 1] =
-                            (byte) (idleState.recent[HISTORY_SIZE - diff - 1] & FLAG_LAST_STATE);
-                    }
-                }
-            }
-        }
-        mLastPeriod = thisPeriod;
-    }
-
     private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
         ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         if (userHistory == null) {
@@ -291,6 +259,7 @@
             appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER;
             appUsageHistory.bucketingReason = REASON_DEFAULT;
             appUsageHistory.lastInformedBucket = -1;
+            appUsageHistory.lastJobRunTime = Long.MIN_VALUE; // long long time ago
             userHistory.put(packageName, appUsageHistory);
         }
         return appUsageHistory;
@@ -338,6 +307,38 @@
         }
     }
 
+    /**
+     * Marks the last time a job was run, with the given elapsedRealtime. The time stored is
+     * based on the elapsed timebase.
+     * @param packageName
+     * @param userId
+     * @param elapsedRealtime
+     */
+    public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        appUsageHistory.lastJobRunTime = getElapsedTime(elapsedRealtime);
+    }
+
+    /**
+     * Returns the time since the last job was run for this app. This can be larger than the
+     * current elapsedRealtime, in case it happened before boot or a really large value if no jobs
+     * were ever run.
+     * @param packageName
+     * @param userId
+     * @param elapsedRealtime
+     * @return
+     */
+    public long getTimeSinceLastJobRun(String packageName, int userId, long elapsedRealtime) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        // Don't adjust the default, else it'll wrap around to a positive value
+        if (appUsageHistory.lastJobRunTime == Long.MIN_VALUE) return Long.MAX_VALUE;
+        return getElapsedTime(elapsedRealtime) - appUsageHistory.lastJobRunTime;
+    }
+
     public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory =
@@ -473,12 +474,8 @@
                                 Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
                         appUsageHistory.lastUsedScreenTime =
                                 Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
-                        String lastPredictedTimeString = parser.getAttributeValue(null,
-                                ATTR_LAST_PREDICTED_TIME);
-                        if (lastPredictedTimeString != null) {
-                            appUsageHistory.lastPredictedTime =
-                                    Long.parseLong(lastPredictedTimeString);
-                        }
+                        appUsageHistory.lastPredictedTime = getLongValue(parser,
+                                ATTR_LAST_PREDICTED_TIME, 0L);
                         String currentBucketString = parser.getAttributeValue(null,
                                 ATTR_CURRENT_BUCKET);
                         appUsageHistory.currentBucket = currentBucketString == null
@@ -486,6 +483,10 @@
                                 : Integer.parseInt(currentBucketString);
                         appUsageHistory.bucketingReason =
                                 parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
+                        appUsageHistory.lastJobRunTime = getLongValue(parser,
+                                ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE);
+                        appUsageHistory.bucketTimeoutTime = getLongValue(parser,
+                                ATTR_BUCKET_TIMEOUT_TIME, 0L);
                         if (appUsageHistory.bucketingReason == null) {
                             appUsageHistory.bucketingReason = REASON_DEFAULT;
                         }
@@ -501,6 +502,12 @@
         }
     }
 
+    private long getLongValue(XmlPullParser parser, String attrName, long defValue) {
+        String value = parser.getAttributeValue(null, attrName);
+        if (value == null) return defValue;
+        return Long.parseLong(value);
+    }
+
     public void writeAppIdleTimes(int userId) {
         FileOutputStream fos = null;
         AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
@@ -531,6 +538,14 @@
                 xml.attribute(null, ATTR_CURRENT_BUCKET,
                         Integer.toString(history.currentBucket));
                 xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason);
+                if (history.bucketTimeoutTime > 0) {
+                    xml.attribute(null, ATTR_BUCKET_TIMEOUT_TIME, Long.toString(history
+                            .bucketTimeoutTime));
+                }
+                if (history.lastJobRunTime != Long.MIN_VALUE) {
+                    xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history
+                            .lastJobRunTime));
+                }
                 xml.endTag(null, TAG_PACKAGE);
             }
 
@@ -565,6 +580,10 @@
             TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
             idpw.print(" lastPredictedTime=");
             TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw);
+            idpw.print(" bucketTimeoutTime=");
+            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketTimeoutTime, idpw);
+            idpw.print(" lastJobRunTime=");
+            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
             idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
             idpw.print(" bucket=" + appUsageHistory.currentBucket
                     + " reason=" + appUsageHistory.bucketingReason);
@@ -579,21 +598,4 @@
         idpw.println();
         idpw.decreaseIndent();
     }
-
-    public void dumpHistory(IndentingPrintWriter idpw, int userId) {
-        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        if (userHistory == null) return;
-        final int P = userHistory.size();
-        for (int p = 0; p < P; p++) {
-            final String packageName = userHistory.keyAt(p);
-            final byte[] history = userHistory.valueAt(p).recent;
-            for (int i = 0; i < HISTORY_SIZE; i++) {
-                idpw.print(history[i] == 0 ? '.' : 'A');
-            }
-            idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
-            idpw.print("  " + packageName);
-            idpw.println();
-        }
-    }
 }
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index 9b588fa..2ec218a 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -36,7 +36,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.usage.UsageStatsManager.StandbyBuckets;
 import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
 import android.appwidget.AppWidgetManager;
 import android.content.BroadcastReceiver;
@@ -82,6 +81,8 @@
 
 import java.io.File;
 import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -263,8 +264,9 @@
                 }
                 if (!packageName.equals(providerPkgName)) {
                     synchronized (mAppIdleLock) {
-                        int newBucket = mAppIdleHistory.reportMildUsage(packageName, userId,
-                                    elapsedRealtime);
+                        int newBucket = mAppIdleHistory.reportUsage(packageName, userId,
+                                STANDBY_BUCKET_ACTIVE, elapsedRealtime,
+                                elapsedRealtime + 2 * ONE_HOUR);
                         maybeInformListeners(packageName, userId, elapsedRealtime,
                                 newBucket);
                     }
@@ -406,8 +408,10 @@
                         AppIdleHistory.AppUsageHistory app =
                                 mAppIdleHistory.getAppUsageHistory(packageName,
                                 userId, elapsedRealtime);
-                        // If the bucket was forced by the developer, leave it alone
-                        if (REASON_FORCED.equals(app.bucketingReason)) {
+                        // If the bucket was forced by the developer or the app is within the
+                        // temporary active period, leave it alone.
+                        if (REASON_FORCED.equals(app.bucketingReason)
+                                || !hasBucketTimeoutPassed(app, elapsedRealtime)) {
                             continue;
                         }
                         boolean predictionLate = false;
@@ -449,6 +453,11 @@
                     - app.lastPredictedTime > PREDICTION_TIMEOUT;
     }
 
+    private boolean hasBucketTimeoutPassed(AppIdleHistory.AppUsageHistory app,
+            long elapsedRealtime) {
+        return app.bucketTimeoutTime < mAppIdleHistory.getElapsedTime(elapsedRealtime);
+    }
+
     private void maybeInformListeners(String packageName, int userId,
             long elapsedRealtime, int bucket) {
         synchronized (mAppIdleLock) {
@@ -544,11 +553,13 @@
 
                 final int newBucket;
                 if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) {
-                    newBucket = mAppIdleHistory.reportMildUsage(event.mPackage, userId,
-                            elapsedRealtime);
+                    newBucket = mAppIdleHistory.reportUsage(event.mPackage, userId,
+                            STANDBY_BUCKET_WORKING_SET,
+                            elapsedRealtime, elapsedRealtime + 2 * ONE_HOUR);
                 } else {
                     newBucket = mAppIdleHistory.reportUsage(event.mPackage, userId,
-                            elapsedRealtime);
+                            STANDBY_BUCKET_ACTIVE,
+                            elapsedRealtime, elapsedRealtime + 2 * ONE_HOUR);
                 }
 
                 maybeInformListeners(event.mPackage, userId, elapsedRealtime,
@@ -592,6 +603,19 @@
         }
     }
 
+    public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.setLastJobRunTime(packageName, userId, elapsedRealtime);
+        }
+    }
+
+    public long getTimeSinceLastJobRun(String packageName, int userId) {
+        final long elapsedRealtime = mInjector.elapsedRealtime();
+        synchronized (mAppIdleLock) {
+            return mAppIdleHistory.getTimeSinceLastJobRun(packageName, userId, elapsedRealtime);
+        }
+    }
+
     public void onUserRemoved(int userId) {
         synchronized (mAppIdleLock) {
             mAppIdleHistory.onUserRemoved(userId);
@@ -805,16 +829,27 @@
             AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName,
                     userId, elapsedRealtime);
             boolean predicted = reason != null && reason.startsWith(REASON_PREDICTED);
+
             // Don't allow changing bucket if higher than ACTIVE
             if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return;
-            // Don't allow prediction to change from or to NEVER
+
+            // Don't allow prediction to change from/to NEVER
             if ((app.currentBucket == STANDBY_BUCKET_NEVER
                     || newBucket == STANDBY_BUCKET_NEVER)
                     && predicted) {
                 return;
             }
+
             // If the bucket was forced, don't allow prediction to override
             if (app.bucketingReason.equals(REASON_FORCED) && predicted) return;
+
+            // If the bucket is required to stay in a higher state for a specified duration, don't
+            // override unless the duration has passed
+            if (predicted && app.currentBucket < newBucket
+                    && !hasBucketTimeoutPassed(app, elapsedRealtime)) {
+                return;
+            }
+
             mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
                     reason);
         }
@@ -947,7 +982,10 @@
                 final PackageInfo pi = packages.get(i);
                 String packageName = pi.packageName;
                 if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
-                    mAppIdleHistory.reportUsage(packageName, userId, elapsedRealtime);
+                    // Mark app as used for 4 hours. After that it can timeout to whatever the
+                    // past usage pattern was.
+                    mAppIdleHistory.reportUsage(packageName, userId, STANDBY_BUCKET_ACTIVE, 0,
+                            elapsedRealtime + 4 * ONE_HOUR);
                     if (isAppSpecial(packageName, UserHandle.getAppId(pi.applicationInfo.uid),
                             userId)) {
                         mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime,
@@ -967,12 +1005,6 @@
                 .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);
@@ -1278,10 +1310,10 @@
             synchronized (mAppIdleLock) {
 
                 // Default: 24 hours between paroles
-                mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
+                mAppIdleParoleIntervalMillis = mParser.getDurationMillis(KEY_PAROLE_INTERVAL,
                         COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
 
-                mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
+                mAppIdleParoleDurationMillis = mParser.getDurationMillis(KEY_PAROLE_DURATION,
                         COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
 
                 String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
@@ -1308,7 +1340,15 @@
                 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]);
+                        try {
+                            if (thresholds[i].startsWith("P") || thresholds[i].startsWith("p")) {
+                                array[i] = Duration.parse(thresholds[i]).toMillis();
+                            } else {
+                                array[i] = Long.parseLong(thresholds[i]);
+                            }
+                        } catch (NumberFormatException|DateTimeParseException e) {
+                            return defaults;
+                        }
                     }
                     return array;
                 } else {
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 82f8001..2fec20a 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -49,6 +49,7 @@
 import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
+import android.util.DataUnit;
 import android.util.Slog;
 import android.util.SparseLongArray;
 
@@ -73,7 +74,7 @@
     private static final String PROP_VERIFY_STORAGE = "fw.verify_storage";
 
     private static final long DELAY_IN_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
-    private static final long DEFAULT_QUOTA = 64 * TrafficStats.MB_IN_BYTES;
+    private static final long DEFAULT_QUOTA = DataUnit.MEBIBYTES.toBytes(64);
 
     public static class Lifecycle extends SystemService {
         private StorageStatsService mService;
@@ -167,8 +168,11 @@
     public boolean isReservedSupported(String volumeUuid, String callingPackage) {
         enforcePermission(Binder.getCallingUid(), callingPackage);
 
-        // TODO: implement as part of b/62024591
-        return false;
+        if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
+            return SystemProperties.getBoolean(StorageManager.PROP_HAS_RESERVED, false);
+        } else {
+            return false;
+        }
     }
 
     @Override
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index cdce448..463a26e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -68,8 +68,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -478,7 +476,6 @@
             IndentingPrintWriter idpw = new IndentingPrintWriter(pw, "  ");
 
             boolean checkin = false;
-            boolean history = false;
             String pkg = null;
 
             if (args != null) {
@@ -486,11 +483,6 @@
                     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");
@@ -514,9 +506,6 @@
                 } else {
                     mUserState.valueAt(i).dump(idpw, pkg);
                     idpw.println();
-                    if (history) {
-                        mAppStandby.dumpHistory(idpw, userId);
-                    }
                 }
                 mAppStandby.dumpUser(idpw, userId, pkg);
                 idpw.decreaseIndent();
@@ -1021,5 +1010,15 @@
             return UsageStatsService.this.queryUsageStats(
                     userId, intervalType, beginTime, endTime, obfuscateInstantApps);
         }
+
+        @Override
+        public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
+            mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
+        }
+
+        @Override
+        public long getTimeSinceLastJobRun(String packageName, int userId) {
+            return mAppStandby.getTimeSinceLastJobRun(packageName, userId);
+        }
     }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1b057f9..4a7072d 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -16,6 +16,9 @@
 
 package com.android.server.usb;
 
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -26,6 +29,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -38,6 +42,7 @@
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
 import android.os.BatteryManager;
+import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.Looper;
@@ -60,6 +65,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.FgThread;
+import com.android.server.LocalServices;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -75,7 +81,7 @@
 /**
  * UsbDeviceManager manages USB state in device mode.
  */
-public class UsbDeviceManager {
+public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver {
 
     private static final String TAG = "UsbDeviceManager";
     private static final boolean DEBUG = false;
@@ -97,6 +103,12 @@
     private static final String USB_STATE_PROPERTY = "sys.usb.state";
 
     /**
+     * The SharedPreference setting per user that stores the screen unlocked functions between
+     * sessions.
+     */
+    private static final String UNLOCKED_CONFIG_PREF = "usb-screen-unlocked-config-%d";
+
+    /**
      * ro.bootmode value when phone boots into usual Android.
      */
     private static final String NORMAL_BOOT = "normal";
@@ -128,6 +140,8 @@
     private static final int MSG_UPDATE_CHARGING_STATE = 9;
     private static final int MSG_UPDATE_HOST_STATE = 10;
     private static final int MSG_LOCALE_CHANGED = 11;
+    private static final int MSG_SET_SCREEN_UNLOCKED_FUNCTIONS = 12;
+    private static final int MSG_UPDATE_SCREEN_LOCK = 13;
 
     private static final int AUDIO_MODE_SOURCE = 1;
 
@@ -169,6 +183,7 @@
     private Intent mBroadcastedIntent;
     private boolean mPendingBootBroadcast;
     private static Set<Integer> sBlackListedInterfaces;
+    private SharedPreferences mSettings;
 
     static {
         sBlackListedInterfaces = new HashSet<>();
@@ -217,6 +232,31 @@
         }
     };
 
+    @Override
+    public void onKeyguardStateChanged(boolean isShowing) {
+        int userHandle = ActivityManager.getCurrentUser();
+        boolean secure = mContext.getSystemService(KeyguardManager.class)
+                .isDeviceSecure(userHandle);
+        boolean unlocking = mContext.getSystemService(UserManager.class)
+                .isUserUnlockingOrUnlocked(userHandle);
+        if (DEBUG) {
+            Slog.v(TAG, "onKeyguardStateChanged: isShowing:" + isShowing + " secure:" + secure
+                    + " unlocking:" + unlocking + " user:" + userHandle);
+        }
+        // We are unlocked when the keyguard is down or non-secure, and user storage is unlocked.
+        mHandler.sendMessage(MSG_UPDATE_SCREEN_LOCK, (isShowing && secure) || !unlocking);
+    }
+
+    @Override
+    public void onAwakeStateChanged(boolean isAwake) {
+        // ignore
+    }
+
+    /** Called when a user is unlocked. */
+    public void onUnlockUser(int userHandle) {
+        onKeyguardStateChanged(false);
+    }
+
     public UsbDeviceManager(Context context, UsbAlsaManager alsaManager,
             UsbSettingsManager settingsManager) {
         mContext = context;
@@ -303,6 +343,8 @@
     public void systemReady() {
         if (DEBUG) Slog.d(TAG, "systemReady");
 
+        LocalServices.getService(ActivityManagerInternal.class).registerScreenObserver(this);
+
         mNotificationManager = (NotificationManager)
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
 
@@ -407,6 +449,14 @@
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
     }
 
+    private SharedPreferences getPinnedSharedPrefs(Context context) {
+        final File prefsFile = new File(new File(
+                Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
+                        context.getUserId(), context.getPackageName()), "shared_prefs"),
+                UsbDeviceManager.class.getSimpleName() + ".xml");
+        return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+    }
+
     private final class UsbHandler extends Handler {
 
         // current USB state
@@ -423,11 +473,13 @@
         private UsbAccessory mCurrentAccessory;
         private int mUsbNotificationId;
         private boolean mAdbNotificationShown;
-        private int mCurrentUser = UserHandle.USER_NULL;
+        private int mCurrentUser;
         private boolean mUsbCharging;
         private String mCurrentOemFunctions;
         private boolean mHideUsbNotification;
         private boolean mSupportsAllCombinations;
+        private String mScreenUnlockedFunctions = UsbManager.USB_FUNCTION_NONE;
+        private boolean mScreenLocked;
 
         public UsbHandler(Looper looper) {
             super(looper);
@@ -449,6 +501,9 @@
                             SystemProperties.get(USB_STATE_PROPERTY));
                 }
 
+                mCurrentUser = ActivityManager.getCurrentUser();
+                mScreenLocked = true;
+
                 /*
                  * Use the normal bootmode persistent prop to maintain state of adb across
                  * all boot modes.
@@ -653,7 +708,7 @@
         private boolean trySetEnabledFunctions(String functions, boolean forceRestart) {
             if (functions == null || applyAdbFunction(functions)
                     .equals(UsbManager.USB_FUNCTION_NONE)) {
-                functions = getDefaultFunctions();
+                functions = getChargingFunctions();
             }
             functions = applyAdbFunction(functions);
 
@@ -876,6 +931,14 @@
                     mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
         }
 
+        private void setScreenUnlockedFunctions() {
+            setEnabledFunctions(mScreenUnlockedFunctions, false,
+                    UsbManager.containsFunction(mScreenUnlockedFunctions,
+                            UsbManager.USB_FUNCTION_MTP)
+                            || UsbManager.containsFunction(mScreenUnlockedFunctions,
+                            UsbManager.USB_FUNCTION_PTP));
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -895,7 +958,13 @@
                     if (mBootCompleted) {
                         if (!mConnected && !hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT)) {
                             // restore defaults when USB is disconnected
-                            setEnabledFunctions(null, !mAdbEnabled, false);
+                            if (!mScreenLocked
+                                    && !UsbManager.USB_FUNCTION_NONE.equals(
+                                    mScreenUnlockedFunctions)) {
+                                setScreenUnlockedFunctions();
+                            } else {
+                                setEnabledFunctions(null, !mAdbEnabled, false);
+                            }
                         }
                         updateUsbFunctions();
                     } else {
@@ -978,6 +1047,47 @@
                     String functions = (String) msg.obj;
                     setEnabledFunctions(functions, false, msg.arg1 == 1);
                     break;
+                case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS:
+                    mScreenUnlockedFunctions = (String) msg.obj;
+                    SharedPreferences.Editor editor = mSettings.edit();
+                    editor.putString(String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF,
+                            mCurrentUser), mScreenUnlockedFunctions);
+                    editor.commit();
+                    if (!mScreenLocked && !UsbManager.USB_FUNCTION_NONE.equals(
+                            mScreenUnlockedFunctions)) {
+                        // If the screen is unlocked, also set current functions.
+                        setScreenUnlockedFunctions();
+                    }
+                    break;
+                case MSG_UPDATE_SCREEN_LOCK:
+                    if (msg.arg1 == 1 == mScreenLocked) {
+                        break;
+                    }
+                    mScreenLocked = msg.arg1 == 1;
+                    if (mSettings == null && !mScreenLocked) {
+                        // Shared preferences aren't accessible until the user has been unlocked.
+                        mSettings = getPinnedSharedPrefs(mContext);
+                        mScreenUnlockedFunctions = mSettings.getString(
+                                String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, mCurrentUser),
+                                UsbManager.USB_FUNCTION_NONE);
+                    }
+                    if (!mBootCompleted) {
+                        break;
+                    }
+                    if (mScreenLocked) {
+                        if (!mConnected) {
+                            setEnabledFunctions(null, false, false);
+                        }
+                    } else {
+                        if (!UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions)
+                                && (UsbManager.USB_FUNCTION_ADB.equals(mCurrentFunctions)
+                                || (UsbManager.USB_FUNCTION_MTP.equals(mCurrentFunctions)
+                                        && !mUsbDataUnlocked))) {
+                            // Set the screen unlocked functions if current function is charging.
+                            setScreenUnlockedFunctions();
+                        }
+                    }
+                    break;
                 case MSG_UPDATE_USER_RESTRICTIONS:
                     // Restart the USB stack if USB transfer is enabled but no longer allowed.
                     final boolean forceRestart = mUsbDataUnlocked
@@ -1001,7 +1111,13 @@
                         updateUsbStateBroadcastIfNeeded(false);
                         mPendingBootBroadcast = false;
                     }
-                    setEnabledFunctions(null, false, false);
+
+                    if (!mScreenLocked
+                            && !UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions)) {
+                        setScreenUnlockedFunctions();
+                    } else {
+                        setEnabledFunctions(null, false, false);
+                    }
                     if (mCurrentAccessory != null) {
                         getCurrentSettings().accessoryAttached(mCurrentAccessory);
                     }
@@ -1011,16 +1127,15 @@
                     break;
                 case MSG_USER_SWITCHED: {
                     if (mCurrentUser != msg.arg1) {
-                        // Restart the USB stack and re-apply user restrictions for MTP or PTP.
-                        if (mUsbDataUnlocked
-                                && isUsbDataTransferActive()
-                                && mCurrentUser != UserHandle.USER_NULL) {
-                            Slog.v(TAG, "Current user switched to " + msg.arg1
-                                    + "; resetting USB host stack for MTP or PTP");
-                            // avoid leaking sensitive data from previous user
-                            setEnabledFunctions(null, true, false);
+                        if (DEBUG) {
+                            Slog.v(TAG, "Current user switched to " + msg.arg1);
                         }
                         mCurrentUser = msg.arg1;
+                        mScreenLocked = true;
+                        mScreenUnlockedFunctions = mSettings.getString(
+                                String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, mCurrentUser),
+                                UsbManager.USB_FUNCTION_NONE);
+                        setEnabledFunctions(null, false, false);
                     }
                     break;
                 }
@@ -1072,20 +1187,12 @@
                 titleRes = com.android.internal.R.string.usb_unsupported_audio_accessory_title;
                 id = SystemMessage.NOTE_USB_AUDIO_ACCESSORY_NOT_SUPPORTED;
             } else if (mConnected) {
-                if (!mUsbDataUnlocked) {
-                    if (mSourcePower) {
-                        titleRes = com.android.internal.R.string.usb_supplying_notification_title;
-                        id = SystemMessage.NOTE_USB_SUPPLYING;
-                    } else {
-                        titleRes = com.android.internal.R.string.usb_charging_notification_title;
-                        id = SystemMessage.NOTE_USB_CHARGING;
-                    }
-                } else if (UsbManager.containsFunction(mCurrentFunctions,
-                        UsbManager.USB_FUNCTION_MTP)) {
+                if (UsbManager.containsFunction(mCurrentFunctions,
+                        UsbManager.USB_FUNCTION_MTP) && mUsbDataUnlocked) {
                     titleRes = com.android.internal.R.string.usb_mtp_notification_title;
                     id = SystemMessage.NOTE_USB_MTP;
                 } else if (UsbManager.containsFunction(mCurrentFunctions,
-                        UsbManager.USB_FUNCTION_PTP)) {
+                        UsbManager.USB_FUNCTION_PTP) && mUsbDataUnlocked) {
                     titleRes = com.android.internal.R.string.usb_ptp_notification_title;
                     id = SystemMessage.NOTE_USB_PTP;
                 } else if (UsbManager.containsFunction(mCurrentFunctions,
@@ -1236,7 +1343,7 @@
             }
         }
 
-        private String getDefaultFunctions() {
+        private String getChargingFunctions() {
             String func = SystemProperties.get(getPersistProp(true),
                     UsbManager.USB_FUNCTION_NONE);
             // if ADB is enabled, reset functions to ADB
@@ -1253,6 +1360,8 @@
             pw.println("  mCurrentFunctions: " + mCurrentFunctions);
             pw.println("  mCurrentOemFunctions: " + mCurrentOemFunctions);
             pw.println("  mCurrentFunctionsApplied: " + mCurrentFunctionsApplied);
+            pw.println("  mScreenUnlockedFunctions: " + mScreenUnlockedFunctions);
+            pw.println("  mScreenLocked: " + mScreenLocked);
             pw.println("  mConnected: " + mConnected);
             pw.println("  mConfigured: " + mConfigured);
             pw.println("  mUsbDataUnlocked: " + mUsbDataUnlocked);
@@ -1309,6 +1418,17 @@
         mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, usbDataUnlocked);
     }
 
+    /**
+     * Sets the functions which are set when the screen is unlocked.
+     * @param functions Functions to set.
+     */
+    public void setScreenUnlockedFunctions(String functions) {
+        if (DEBUG) {
+            Slog.d(TAG, "setScreenUnlockedFunctions(" + functions + ")");
+        }
+        mHandler.sendMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS, functions);
+    }
+
     private void readOemUsbOverrideConfig() {
         String[] configList = mContext.getResources().getStringArray(
                 com.android.internal.R.array.config_oemUsbModeOverride);
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 8554cf7..1a20819 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -87,6 +87,11 @@
         public void onStopUser(int userHandle) {
             mUsbService.onStopUser(UserHandle.of(userHandle));
         }
+
+        @Override
+        public void onUnlockUser(int userHandle) {
+            mUsbService.onUnlockUser(userHandle);
+        }
     }
 
     private static final String TAG = "UsbService";
@@ -205,6 +210,13 @@
         }
     }
 
+    /** Called when a user is unlocked. */
+    public void onUnlockUser(int user) {
+        if (mDeviceManager != null) {
+            mDeviceManager.onUnlockUser(user);
+        }
+    }
+
     /* Returns a list of all currently attached USB devices (host mdoe) */
     @Override
     public void getDeviceList(Bundle devices) {
@@ -392,6 +404,23 @@
         }
     }
 
+    @Override
+    public void setScreenUnlockedFunctions(String function) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        if (!isSupportedCurrentFunction(function)) {
+            Slog.w(TAG, "Caller of setScreenUnlockedFunctions() requested unsupported USB function:"
+                    + function);
+            function = UsbManager.USB_FUNCTION_NONE;
+        }
+
+        if (mDeviceManager != null) {
+            mDeviceManager.setScreenUnlockedFunctions(function);
+        } else {
+            throw new IllegalStateException("USB device mode not supported");
+        }
+    }
+
     private static boolean isSupportedCurrentFunction(String function) {
         if (function == null) return true;
 
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index dec7b76..6af01ae 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1466,8 +1466,9 @@
         }
         Log.d(this, "createConnection, connection: %s", connection);
         if (connection == null) {
+            Log.i(this, "createConnection, implementation returned null connection.");
             connection = Connection.createFailedConnection(
-                    new DisconnectCause(DisconnectCause.ERROR));
+                    new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"));
         }
 
         connection.setTelecomCallId(callId);
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 3361b5b..83ca470 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -340,24 +340,6 @@
         return sSessionManager;
     }
 
-    private static MessageDigest sMessageDigest;
-
-    public static void initMd5Sum() {
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            public Void doInBackground(Void... args) {
-                MessageDigest md;
-                try {
-                    md = MessageDigest.getInstance("SHA-1");
-                } catch (NoSuchAlgorithmException e) {
-                    md = null;
-                }
-                sMessageDigest = md;
-                return null;
-            }
-        }.execute();
-    }
-
     public static void setTag(String tag) {
         TAG = tag;
         DEBUG = isLoggable(android.util.Log.DEBUG);
@@ -425,44 +407,13 @@
     /**
      * Redact personally identifiable information for production users.
      * If we are running in verbose mode, return the original string,
-     * and return "****" if we are running on the user build, otherwise
-     * return a SHA-1 hash of the input string.
+     * and return "***" otherwise.
      */
     public static String pii(Object pii) {
         if (pii == null || VERBOSE) {
             return String.valueOf(pii);
         }
-        return "[" + secureHash(String.valueOf(pii).getBytes()) + "]";
-    }
-
-    private static String secureHash(byte[] input) {
-        // Refrain from logging user personal information in user build.
-        if (USER_BUILD) {
-            return "****";
-        }
-
-        if (sMessageDigest != null) {
-            sMessageDigest.reset();
-            sMessageDigest.update(input);
-            byte[] result = sMessageDigest.digest();
-            return encodeHex(result);
-        } else {
-            return "Uninitialized SHA1";
-        }
-    }
-
-    private static String encodeHex(byte[] bytes) {
-        StringBuffer hex = new StringBuffer(bytes.length * 2);
-
-        for (int i = 0; i < bytes.length; i++) {
-            int byteIntValue = bytes[i] & 0xff;
-            if (byteIntValue < 0x10) {
-                hex.append("0");
-            }
-            hex.append(Integer.toString(byteIntValue, 16));
-        }
-
-        return hex.toString();
+        return "***";
     }
 
     private static String getPrefixFromObject(Object obj) {
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 15355ac..2d1fe50 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -653,7 +653,6 @@
             mContext = context;
         }
         mTelecomServiceOverride = telecomServiceImpl;
-        android.telecom.Log.initMd5Sum();
     }
 
     /**
diff --git a/telephony/java/android/telephony/Telephony.java b/telephony/java/android/provider/Telephony.java
similarity index 99%
rename from telephony/java/android/telephony/Telephony.java
rename to telephony/java/android/provider/Telephony.java
index 942ea00..e0b6f61 100644
--- a/telephony/java/android/telephony/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -2693,6 +2693,7 @@
          * but is currently only used for LTE (14) and eHRPD (13).
          * <P>Type: INTEGER</P>
          */
+        @Deprecated
         public static final String BEARER = "bearer";
 
         /**
@@ -2704,9 +2705,19 @@
          * <P>Type: INTEGER</P>
          * @hide
          */
+        @Deprecated
         public static final String BEARER_BITMASK = "bearer_bitmask";
 
         /**
+         * Radio technology (network type) bitmask.
+         * To check what values can be contained, refer to
+         * {@link android.telephony.TelephonyManager}.
+         * Bitmask for a radio tech R is (1 << (R - 1))
+         * <P>Type: INTEGER</P>
+         */
+        public static final String NETWORK_TYPE_BITMASK = "network_type_bitmask";
+
+        /**
          * MVNO type:
          * {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
          * <P>Type: TEXT</P>
diff --git a/telephony/java/android/telephony/RadioNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
similarity index 95%
rename from telephony/java/android/telephony/RadioNetworkConstants.java
rename to telephony/java/android/telephony/AccessNetworkConstants.java
index 1a9072d..fc814be 100644
--- a/telephony/java/android/telephony/RadioNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -17,24 +17,23 @@
 package android.telephony;
 
 /**
- * Contains radio access network related constants.
- * @hide
+ * Contains access network related constants.
  */
-public final class RadioNetworkConstants {
+public final class AccessNetworkConstants {
 
-    public static final class RadioAccessNetworks {
+    public static final class AccessNetworkType {
         public static final int GERAN = 1;
         public static final int UTRAN = 2;
         public static final int EUTRAN = 3;
-        /** @hide */
         public static final int CDMA2000 = 4;
+        public static final int IWLAN = 5;
     }
 
     /**
      * Frenquency bands for GERAN.
      * http://www.etsi.org/deliver/etsi_ts/145000_145099/145005/14.00.00_60/ts_145005v140000p.pdf
      */
-    public static final class GeranBands {
+    public static final class GeranBand {
         public static final int BAND_T380 = 1;
         public static final int BAND_T410 = 2;
         public static final int BAND_450 = 3;
@@ -55,7 +54,7 @@
      * Frenquency bands for UTRAN.
      * http://www.etsi.org/deliver/etsi_ts/125100_125199/125104/13.03.00_60/ts_125104v130p.pdf
      */
-    public static final class UtranBands {
+    public static final class UtranBand {
         public static final int BAND_1 = 1;
         public static final int BAND_2 = 2;
         public static final int BAND_3 = 3;
@@ -84,7 +83,7 @@
      * Frenquency bands for EUTRAN.
      * http://www.etsi.org/deliver/etsi_ts/136100_136199/136101/14.03.00_60/ts_136101v140p.pdf
      */
-    public static final class EutranBands {
+    public static final class EutranBand {
         public static final int BAND_1 = 1;
         public static final int BAND_2 = 2;
         public static final int BAND_3 = 3;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6a47d05..6b40e7f 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1724,6 +1724,15 @@
      */
     public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
 
+    /**
+     * Determines whether we should show a warning asking the user to check with their carrier
+     * on pricing when the user enabled data roaming.
+     * default to false.
+     * @hide
+     */
+    public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL =
+            "check_pricing_with_carrier_data_roaming_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -2010,6 +2019,7 @@
         sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
+        sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellIdentityCdma.aidl b/telephony/java/android/telephony/CellIdentityCdma.aidl
new file mode 100644
index 0000000..b31ad0b
--- /dev/null
+++ b/telephony/java/android/telephony/CellIdentityCdma.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/** @hide */
+package android.telephony;
+
+parcelable CellIdentityCdma;
diff --git a/telephony/java/android/telephony/CellIdentityGsm.aidl b/telephony/java/android/telephony/CellIdentityGsm.aidl
new file mode 100644
index 0000000..bcc0751
--- /dev/null
+++ b/telephony/java/android/telephony/CellIdentityGsm.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/** @hide */
+package android.telephony;
+
+parcelable CellIdentityGsm;
diff --git a/telephony/java/android/telephony/CellIdentityLte.aidl b/telephony/java/android/telephony/CellIdentityLte.aidl
new file mode 100644
index 0000000..940d170
--- /dev/null
+++ b/telephony/java/android/telephony/CellIdentityLte.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/** @hide */
+package android.telephony;
+
+parcelable CellIdentityLte;
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.aidl b/telephony/java/android/telephony/CellIdentityWcdma.aidl
new file mode 100644
index 0000000..462ce2c
--- /dev/null
+++ b/telephony/java/android/telephony/CellIdentityWcdma.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/** @hide */
+package android.telephony;
+
+parcelable CellIdentityWcdma;
diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java
index a554c69..059a2d0 100644
--- a/telephony/java/android/telephony/MbmsDownloadSession.java
+++ b/telephony/java/android/telephony/MbmsDownloadSession.java
@@ -502,8 +502,10 @@
      * Asynchronous errors through the callback may include any error not specific to the
      * streaming use-case.
      * @param request The request that specifies what should be downloaded.
+     * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+     * and some other error code otherwise.
      */
-    public void download(@NonNull DownloadRequest request) {
+    public int download(@NonNull DownloadRequest request) {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
             throw new IllegalStateException("Middleware not yet bound");
@@ -519,13 +521,16 @@
             setTempFileRootDirectory(tempRootDirectory);
         }
 
-        writeDownloadRequestToken(request);
         try {
-            downloadService.download(request);
+            int result = downloadService.download(request);
+            if (result == MbmsErrors.SUCCESS) {
+                writeDownloadRequestToken(request);
+            }
+            return result;
         } catch (RemoteException e) {
             mService.set(null);
             sIsInitialized.set(false);
-            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            return MbmsErrors.ERROR_MIDDLEWARE_LOST;
         }
     }
 
@@ -565,8 +570,10 @@
      * @param callback The callback that should be called when the middleware has information to
      *                 share on the download.
      * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
+     * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+     * and some other error code otherwise.
      */
-    public void registerStateCallback(@NonNull DownloadRequest request,
+    public int registerStateCallback(@NonNull DownloadRequest request,
             @NonNull DownloadStateCallback callback, @NonNull Handler handler) {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
@@ -583,16 +590,15 @@
                 if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                     throw new IllegalArgumentException("Unknown download request.");
                 }
-                sendErrorToApp(result, null);
-                return;
+                return result;
             }
         } catch (RemoteException e) {
             mService.set(null);
             sIsInitialized.set(false);
-            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
-            return;
+            return MbmsErrors.ERROR_MIDDLEWARE_LOST;
         }
         mInternalDownloadCallbacks.put(callback, internalCallback);
+        return MbmsErrors.SUCCESS;
     }
 
     /**
@@ -606,8 +612,10 @@
      *
      * @param request The {@link DownloadRequest} provided during registration
      * @param callback The callback provided during registration.
+     * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+     * and some other error code otherwise.
      */
-    public void unregisterStateCallback(@NonNull DownloadRequest request,
+    public int unregisterStateCallback(@NonNull DownloadRequest request,
             @NonNull DownloadStateCallback callback) {
         try {
             IMbmsDownloadService downloadService = mService.get();
@@ -617,6 +625,9 @@
 
             InternalDownloadStateCallback internalCallback =
                     mInternalDownloadCallbacks.get(callback);
+            if (internalCallback == null) {
+                throw new IllegalArgumentException("Provided callback was never registered");
+            }
 
             try {
                 int result = downloadService.unregisterStateCallback(request, internalCallback);
@@ -624,12 +635,12 @@
                     if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                         throw new IllegalArgumentException("Unknown download request.");
                     }
-                    sendErrorToApp(result, null);
+                    return result;
                 }
             } catch (RemoteException e) {
                 mService.set(null);
                 sIsInitialized.set(false);
-                sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+                return MbmsErrors.ERROR_MIDDLEWARE_LOST;
             }
         } finally {
             InternalDownloadStateCallback internalCallback =
@@ -638,6 +649,7 @@
                 internalCallback.stop();
             }
         }
+        return MbmsErrors.SUCCESS;
     }
 
     /**
@@ -647,8 +659,10 @@
      * this method will throw an {@link IllegalArgumentException}.
      *
      * @param downloadRequest The download request that you wish to cancel.
+     * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+     * and some other error code otherwise.
      */
-    public void cancelDownload(@NonNull DownloadRequest downloadRequest) {
+    public int cancelDownload(@NonNull DownloadRequest downloadRequest) {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
             throw new IllegalStateException("Middleware not yet bound");
@@ -660,16 +674,15 @@
                 if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                     throw new IllegalArgumentException("Unknown download request.");
                 }
-                sendErrorToApp(result, null);
-                return;
+            } else {
+                deleteDownloadRequestToken(downloadRequest);
             }
+            return result;
         } catch (RemoteException e) {
             mService.set(null);
             sIsInitialized.set(false);
-            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
-            return;
+            return MbmsErrors.ERROR_MIDDLEWARE_LOST;
         }
-        deleteDownloadRequestToken(downloadRequest);
     }
 
     /**
diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java
index f15fde8..a277212 100644
--- a/telephony/java/android/telephony/NetworkScan.java
+++ b/telephony/java/android/telephony/NetworkScan.java
@@ -19,50 +19,92 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.annotation.IntDef;
 import android.util.Log;
 
 import com.android.internal.telephony.ITelephony;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
- * Allows applications to request the system to perform a network scan.
- *
- * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} will
- * receive a NetworkScan which contains the callback method to stop the scan requested.
- * @hide
+ * The caller of
+ * {@link TelephonyManager#requestNetworkScan(NetworkScanRequest, NetworkScanCallback)}
+ * will receive an instance of {@link NetworkScan}, which contains a callback method
+ * {@link #stop()} for stopping the in-progress scan.
  */
 public class NetworkScan {
 
-    public static final String TAG = "NetworkScan";
+    private static final String TAG = "NetworkScan";
 
     // Below errors are mapped from RadioError which is returned from RIL. We will consolidate
     // RadioErrors during the mapping if those RadioErrors mean no difference to the users.
+
+    /**
+     * Defines acceptable values of scan error code.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ERROR_MODEM_ERROR, ERROR_INVALID_SCAN, ERROR_MODEM_UNAVAILABLE, ERROR_UNSUPPORTED,
+            ERROR_RADIO_INTERFACE_ERROR, ERROR_INVALID_SCANID, ERROR_INTERRUPTED})
+    public @interface ScanErrorCode {}
+
+    /**
+     * The RIL has successfully performed the network scan.
+     */
     public static final int SUCCESS = 0;                    // RadioError:NONE
+
+    /**
+     * The scan has failed due to some modem errors.
+     */
     public static final int ERROR_MODEM_ERROR = 1;          // RadioError:RADIO_NOT_AVAILABLE
                                                             // RadioError:NO_MEMORY
                                                             // RadioError:INTERNAL_ERR
                                                             // RadioError:MODEM_ERR
                                                             // RadioError:OPERATION_NOT_ALLOWED
+
+    /**
+     * The parameters of the scan is invalid.
+     */
     public static final int ERROR_INVALID_SCAN = 2;         // RadioError:INVALID_ARGUMENTS
-    public static final int ERROR_MODEM_BUSY = 3;           // RadioError:DEVICE_IN_USE
+
+    /**
+     * The modem can not perform the scan because it is doing something else.
+     */
+    public static final int ERROR_MODEM_UNAVAILABLE = 3;    // RadioError:DEVICE_IN_USE
+
+    /**
+     * The modem does not support the request scan.
+     */
     public static final int ERROR_UNSUPPORTED = 4;          // RadioError:REQUEST_NOT_SUPPORTED
 
+
     // Below errors are generated at the Telephony.
-    public static final int ERROR_RIL_ERROR = 10000;        // Nothing or only exception is
-                                                            // returned from RIL.
-    public static final int ERROR_INVALID_SCANID = 10001;   // The scanId is invalid. The user is
-                                                            // either trying to stop a scan which
-                                                            // does not exist or started by others.
-    public static final int ERROR_INTERRUPTED = 10002;      // Scan was interrupted by another scan
-                                                            // with higher priority.
+
+    /**
+     * The RIL returns nothing or exceptions.
+     */
+    public static final int ERROR_RADIO_INTERFACE_ERROR = 10000;
+
+    /**
+     * The scan ID is invalid. The user is either trying to stop a scan which does not exist
+     * or started by others.
+     */
+    public static final int ERROR_INVALID_SCANID = 10001;
+
+    /**
+     * The scan has been interrupted by another scan with higher priority.
+     */
+    public static final int ERROR_INTERRUPTED = 10002;
+
     private final int mScanId;
     private final int mSubId;
 
     /**
      * Stops the network scan
      *
-     * This is the callback method to stop an ongoing scan. When user requests a new scan,
-     * a NetworkScan object will be returned, and the user can stop the scan by calling this
-     * method.
+     * Use this method to stop an ongoing scan. When user requests a new scan, a {@link NetworkScan}
+     * object will be returned, and the user can stop the scan by calling this method.
      */
     public void stop() throws RemoteException {
         try {
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
index 9674c93..9726569 100644
--- a/telephony/java/android/telephony/NetworkScanRequest.java
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -16,11 +16,14 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Defines a request to peform a network scan.
@@ -28,7 +31,6 @@
  * This class defines whether the network scan will be performed only once or periodically until
  * cancelled, when the scan is performed periodically, the time interval is not controlled by the
  * user but defined by the modem vendor.
- * @hide
  */
 public final class NetworkScanRequest implements Parcelable {
 
@@ -54,6 +56,14 @@
     /** @hide */
     public static final int MAX_INCREMENTAL_PERIODICITY_SEC = 10;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        SCAN_TYPE_ONE_SHOT,
+        SCAN_TYPE_PERIODIC,
+    })
+    public @interface ScanType {}
+
     /** Performs the scan only once */
     public static final int SCAN_TYPE_ONE_SHOT = 0;
     /**
@@ -65,21 +75,21 @@
     public static final int SCAN_TYPE_PERIODIC = 1;
 
     /** Defines the type of the scan. */
-    public int scanType;
+    private int mScanType;
 
     /**
      * Search periodicity (in seconds).
      * Expected range for the input is [5s - 300s]
-     * This value must be less than or equal to maxSearchTime
+     * This value must be less than or equal to mMaxSearchTime
      */
-    public int searchPeriodicity;
+    private int mSearchPeriodicity;
 
     /**
      * 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;
+    private int mMaxSearchTime;
 
     /**
      * Indicates whether the modem should report incremental
@@ -87,18 +97,18 @@
      * FALSE – Incremental results are not reported.
      * TRUE (default) – Incremental results are reported
      */
-    public boolean incrementalResults;
+    private boolean mIncrementalResults;
 
     /**
      * 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
+     * This value must be less than or equal to mMaxSearchTime
      */
-    public int incrementalResultsPeriodicity;
+    private int mIncrementalResultsPeriodicity;
 
     /** Describes the radio access technologies with bands or channels that need to be scanned. */
-    public RadioAccessSpecifier[] specifiers;
+    private RadioAccessSpecifier[] mSpecifiers;
 
     /**
      * Describes the List of PLMN ids (MCC-MNC)
@@ -107,20 +117,24 @@
      * 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;
+    private ArrayList<String> mMccMncs;
 
     /**
-     * Creates a new NetworkScanRequest with scanType and network specifiers
+     * Creates a new NetworkScanRequest with mScanType and network mSpecifiers
      *
-     * @param scanType The type of the scan
+     * @param scanType The type of the scan, can be either one shot or periodic
      * @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 searchPeriodicity The modem will restart the scan every searchPeriodicity seconds if
+     *                          no network has been found, until it reaches the maxSearchTime. Only
+     *                          valid when scan type is periodic scan.
+     * @param maxSearchTime Maximum duration of the 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)
+     *                                      report incremental results to the client (in seconds),
+     *                                      only valid when incrementalResults is true
+     * @param mccMncs Describes the list of PLMN ids (MCC-MNC), once any network in the list has
+     *                been found, the scan will be terminated by the modem.
      */
     public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers,
                     int searchPeriodicity,
@@ -128,17 +142,65 @@
                     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;
+        this.mScanType = scanType;
+        if (specifiers != null) {
+            this.mSpecifiers = specifiers.clone();
         } else {
-            this.mccMncs = new ArrayList<>();
+            this.mSpecifiers = null;
         }
+        this.mSearchPeriodicity = searchPeriodicity;
+        this.mMaxSearchTime = maxSearchTime;
+        this.mIncrementalResults = incrementalResults;
+        this.mIncrementalResultsPeriodicity = incrementalResultsPeriodicity;
+        if (mMccMncs != null) {
+            this.mMccMncs = (ArrayList<String>) mccMncs.clone();
+        } else {
+            this.mMccMncs = new ArrayList<>();
+        }
+    }
+
+    /** Returns the type of the scan. */
+    @ScanType
+    public int getScanType() {
+        return mScanType;
+    }
+
+    /** Returns the search periodicity in seconds. */
+    public int getSearchPeriodicity() {
+        return mSearchPeriodicity;
+    }
+
+    /** Returns maximum duration of the periodic search in seconds. */
+    public int getMaxSearchTime() {
+        return mMaxSearchTime;
+    }
+
+    /**
+     * Returns whether incremental result is enabled.
+     * FALSE – Incremental results is not enabled.
+     * TRUE – Incremental results is reported.
+     */
+    public boolean getIncrementalResults() {
+        return mIncrementalResults;
+    }
+
+    /** Returns the periodicity in seconds of incremental results. */
+    public int getIncrementalResultsPeriodicity() {
+        return mIncrementalResultsPeriodicity;
+    }
+
+    /** Returns the radio access technologies with bands or channels that need to be scanned. */
+    public RadioAccessSpecifier[] getSpecifiers() {
+        return mSpecifiers == null ? null : mSpecifiers.clone();
+    }
+
+    /**
+     * Returns the List of PLMN ids (MCC-MNC) for early termination of scan.
+     * 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.
+     */
+    public ArrayList<String> getPlmns() {
+        return (ArrayList<String>) mMccMncs.clone();
     }
 
     @Override
@@ -148,26 +210,26 @@
 
     @Override
     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);
+        dest.writeInt(mScanType);
+        dest.writeParcelableArray(mSpecifiers, flags);
+        dest.writeInt(mSearchPeriodicity);
+        dest.writeInt(mMaxSearchTime);
+        dest.writeBoolean(mIncrementalResults);
+        dest.writeInt(mIncrementalResultsPeriodicity);
+        dest.writeStringList(mMccMncs);
     }
 
     private NetworkScanRequest(Parcel in) {
-        scanType = in.readInt();
-        specifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
+        mScanType = in.readInt();
+        mSpecifiers = (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);
+        mSearchPeriodicity = in.readInt();
+        mMaxSearchTime = in.readInt();
+        mIncrementalResults = in.readBoolean();
+        mIncrementalResultsPeriodicity = in.readInt();
+        mMccMncs = new ArrayList<>();
+        in.readStringList(mMccMncs);
     }
 
     @Override
@@ -184,25 +246,25 @@
             return false;
         }
 
-        return (scanType == nsr.scanType
-                && Arrays.equals(specifiers, nsr.specifiers)
-                && searchPeriodicity == nsr.searchPeriodicity
-                && maxSearchTime == nsr.maxSearchTime
-                && incrementalResults == nsr.incrementalResults
-                && incrementalResultsPeriodicity == nsr.incrementalResultsPeriodicity
-                && (((mccMncs != null)
-                && mccMncs.equals(nsr.mccMncs))));
+        return (mScanType == nsr.mScanType
+                && Arrays.equals(mSpecifiers, nsr.mSpecifiers)
+                && mSearchPeriodicity == nsr.mSearchPeriodicity
+                && mMaxSearchTime == nsr.mMaxSearchTime
+                && mIncrementalResults == nsr.mIncrementalResults
+                && mIncrementalResultsPeriodicity == nsr.mIncrementalResultsPeriodicity
+                && (((mMccMncs != null)
+                && mMccMncs.equals(nsr.mMccMncs))));
     }
 
     @Override
     public int hashCode () {
-        return ((scanType * 31)
-                + (Arrays.hashCode(specifiers)) * 37
-                + (searchPeriodicity * 41)
-                + (maxSearchTime * 43)
-                + ((incrementalResults == true? 1 : 0) * 47)
-                + (incrementalResultsPeriodicity * 53)
-                + (mccMncs.hashCode() * 59));
+        return ((mScanType * 31)
+                + (Arrays.hashCode(mSpecifiers)) * 37
+                + (mSearchPeriodicity * 41)
+                + (mMaxSearchTime * 43)
+                + ((mIncrementalResults == true? 1 : 0) * 47)
+                + (mIncrementalResultsPeriodicity * 53)
+                + (mMccMncs.hashCode() * 59));
     }
 
     public static final Creator<NetworkScanRequest> CREATOR =
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java
index 33ce8b4..81e7ed0 100644
--- a/telephony/java/android/telephony/RadioAccessSpecifier.java
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.java
@@ -25,34 +25,40 @@
  * Describes a particular radio access network to be scanned.
  *
  * The scan can be performed on either bands or channels for a specific radio access network type.
- * @hide
  */
 public final class RadioAccessSpecifier implements Parcelable {
 
     /**
      * The radio access network that needs to be scanned
      *
-     * See {@link RadioNetworkConstants.RadioAccessNetworks} for details.
+     * This parameter must be provided or else the scan will be rejected.
+     *
+     * See {@link AccessNetworkConstants.AccessNetworkType} for details.
      */
-    public int radioAccessNetwork;
+    private int mRadioAccessNetwork;
 
     /**
      * The frequency bands that need to be scanned
      *
-     * bands must be used together with radioAccessNetwork
+     * When no specific bands are specified (empty array or null), all the frequency bands
+     * supported by the modem will be scanned.
      *
-     * See {@link RadioNetworkConstants} for details.
+     * See {@link AccessNetworkConstants} for details.
      */
-    public int[] bands;
+    private int[] mBands;
 
     /**
      * The frequency channels that need to be scanned
      *
-     * channels must be used together with radioAccessNetwork
+     * When any specific channels are provided for scan, the corresponding frequency bands that
+     * contains those channels must also be provided, or else the channels will be ignored.
      *
-     * See {@link RadioNetworkConstants.RadioAccessNetworks} for details.
+     * When no specific channels are specified (empty array or null), all the frequency channels
+     * supported by the modem will be scanned.
+     *
+     * See {@link AccessNetworkConstants} for details.
      */
-    public int[] channels;
+    private int[] mChannels;
 
     /**
     * Creates a new RadioAccessSpecifier with radio network, bands and channels
@@ -65,9 +71,42 @@
     * @param channels the frequency bands to be scanned
     */
     public RadioAccessSpecifier(int ran, int[] bands, int[] channels) {
-        this.radioAccessNetwork = ran;
-        this.bands = bands;
-        this.channels = channels;
+        this.mRadioAccessNetwork = ran;
+        if (bands != null) {
+            this.mBands = bands.clone();
+        } else {
+            this.mBands = null;
+        }
+        if (channels != null) {
+            this.mChannels = channels.clone();
+        } else {
+            this.mChannels = null;
+        }
+    }
+
+    /**
+     * Returns the radio access network that needs to be scanned.
+     *
+     * The returned value is define in {@link AccessNetworkConstants.AccessNetworkType};
+     */
+    public int getRadioAccessNetwork() {
+        return mRadioAccessNetwork;
+    }
+
+    /**
+     * Returns the frequency bands that need to be scanned.
+     *
+     * The returned value is defined in either of {@link AccessNetworkConstants.GeranBand},
+     * {@link AccessNetworkConstants.UtranBand} and {@link AccessNetworkConstants.EutranBand}, and
+     * it depends on the returned value of {@link #getRadioAccessNetwork()}.
+     */
+    public int[] getBands() {
+        return mBands == null ? null : mBands.clone();
+    }
+
+    /** Returns the frequency channels that need to be scanned. */
+    public int[] getChannels() {
+        return mChannels == null ? null : mChannels.clone();
     }
 
     public static final Parcelable.Creator<RadioAccessSpecifier> CREATOR =
@@ -90,15 +129,15 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(radioAccessNetwork);
-        dest.writeIntArray(bands);
-        dest.writeIntArray(channels);
+        dest.writeInt(mRadioAccessNetwork);
+        dest.writeIntArray(mBands);
+        dest.writeIntArray(mChannels);
     }
 
     private RadioAccessSpecifier(Parcel in) {
-        radioAccessNetwork = in.readInt();
-        bands = in.createIntArray();
-        channels = in.createIntArray();
+        mRadioAccessNetwork = in.readInt();
+        mBands = in.createIntArray();
+        mChannels = in.createIntArray();
     }
 
     @Override
@@ -115,15 +154,15 @@
             return false;
         }
 
-        return (radioAccessNetwork == ras.radioAccessNetwork
-                && Arrays.equals(bands, ras.bands)
-                && Arrays.equals(channels, ras.channels));
+        return (mRadioAccessNetwork == ras.mRadioAccessNetwork
+                && Arrays.equals(mBands, ras.mBands)
+                && Arrays.equals(mChannels, ras.mChannels));
     }
 
     @Override
     public int hashCode () {
-        return ((radioAccessNetwork * 31)
-                + (Arrays.hashCode(bands) * 37)
-                + (Arrays.hashCode(channels)) * 39);
+        return ((mRadioAccessNetwork * 31)
+                + (Arrays.hashCode(mBands) * 37)
+                + (Arrays.hashCode(mChannels)) * 39);
     }
 }
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 116e711..d4b4b88 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -16,12 +16,15 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Contains phone state and service related information.
  *
@@ -105,6 +108,31 @@
     /** @hide */
     public static final int RIL_REG_STATE_UNKNOWN_EMERGENCY_CALL_ENABLED = 14;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "RIL_RADIO_TECHNOLOGY_" },
+            value = {
+                    RIL_RADIO_TECHNOLOGY_UNKNOWN,
+                    RIL_RADIO_TECHNOLOGY_GPRS,
+                    RIL_RADIO_TECHNOLOGY_EDGE,
+                    RIL_RADIO_TECHNOLOGY_UMTS,
+                    RIL_RADIO_TECHNOLOGY_IS95A,
+                    RIL_RADIO_TECHNOLOGY_IS95B,
+                    RIL_RADIO_TECHNOLOGY_1xRTT,
+                    RIL_RADIO_TECHNOLOGY_EVDO_0,
+                    RIL_RADIO_TECHNOLOGY_EVDO_A,
+                    RIL_RADIO_TECHNOLOGY_HSDPA,
+                    RIL_RADIO_TECHNOLOGY_HSUPA,
+                    RIL_RADIO_TECHNOLOGY_HSPA,
+                    RIL_RADIO_TECHNOLOGY_EVDO_B,
+                    RIL_RADIO_TECHNOLOGY_EHRPD,
+                    RIL_RADIO_TECHNOLOGY_LTE,
+                    RIL_RADIO_TECHNOLOGY_HSPAP,
+                    RIL_RADIO_TECHNOLOGY_GSM,
+                    RIL_RADIO_TECHNOLOGY_TD_SCDMA,
+                    RIL_RADIO_TECHNOLOGY_IWLAN,
+                    RIL_RADIO_TECHNOLOGY_LTE_CA})
+    public @interface RilRadioTechnology {}
     /**
      * Available radio technologies for GSM, UMTS and CDMA.
      * Duplicates the constants from hardware/radio/include/ril.h
@@ -162,6 +190,12 @@
      */
     public static final int RIL_RADIO_TECHNOLOGY_LTE_CA = 19;
 
+    /**
+     * Number of radio technologies for GSM, UMTS and CDMA.
+     * @hide
+     */
+    private static final int NEXT_RIL_RADIO_TECHNOLOGY = 20;
+
     /** @hide */
     public static final int RIL_RADIO_CDMA_TECHNOLOGY_BITMASK =
             (1 << (RIL_RADIO_TECHNOLOGY_IS95A - 1))
@@ -216,6 +250,11 @@
      */
     public static final int ROAMING_TYPE_INTERNATIONAL = 3;
 
+    /**
+     * Unknown ID. Could be returned by {@link #getNetworkId()} or {@link #getSystemId()}
+     */
+    public static final int UNKNOWN_ID = -1;
+
     private int mVoiceRoamingType;
     private int mDataRoamingType;
     private String mVoiceOperatorAlphaLong;
@@ -1153,7 +1192,8 @@
         return getRilDataRadioTechnology();
     }
 
-    private int rilRadioTechnologyToNetworkType(int rt) {
+    /** @hide */
+    public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rt) {
         switch(rt) {
         case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS:
             return TelephonyManager.NETWORK_TYPE_GPRS;
@@ -1212,12 +1252,20 @@
         return this.mCssIndicator ? 1 : 0;
     }
 
-    /** @hide */
+    /**
+     * Get the CDMA NID (Network Identification Number), a number uniquely identifying a network
+     * within a wireless system. (Defined in 3GPP2 C.S0023 3.4.8)
+     * @return The CDMA NID or {@link #UNKNOWN_ID} if not available.
+     */
     public int getNetworkId() {
         return this.mNetworkId;
     }
 
-    /** @hide */
+    /**
+     * Get the CDMA SID (System Identification Number), a number uniquely identifying a wireless
+     * system. (Defined in 3GPP2 C.S0023 3.4.8)
+     * @return The CDMA SID or {@link #UNKNOWN_ID} if not available.
+     */
     public int getSystemId() {
         return this.mSystemId;
     }
@@ -1300,6 +1348,34 @@
         return bearerBitmask;
     }
 
+    /** @hide */
+    public static int convertNetworkTypeBitmaskToBearerBitmask(int networkTypeBitmask) {
+        if (networkTypeBitmask == 0) {
+            return 0;
+        }
+        int bearerBitmask = 0;
+        for (int bearerInt = 0; bearerInt < NEXT_RIL_RADIO_TECHNOLOGY; bearerInt++) {
+            if (bitmaskHasTech(networkTypeBitmask, rilRadioTechnologyToNetworkType(bearerInt))) {
+                bearerBitmask |= getBitmaskForTech(bearerInt);
+            }
+        }
+        return bearerBitmask;
+    }
+
+    /** @hide */
+    public static int convertBearerBitmaskToNetworkTypeBitmask(int bearerBitmask) {
+        if (bearerBitmask == 0) {
+            return 0;
+        }
+        int networkTypeBitmask = 0;
+        for (int bearerInt = 0; bearerInt < NEXT_RIL_RADIO_TECHNOLOGY; bearerInt++) {
+            if (bitmaskHasTech(bearerBitmask, bearerInt)) {
+                networkTypeBitmask |= getBitmaskForTech(rilRadioTechnologyToNetworkType(bearerInt));
+            }
+        }
+        return networkTypeBitmask;
+    }
+
     /**
      * Returns a merged ServiceState consisting of the base SS with voice settings from the
      * voice SS. The voice SS is only used if it is IN_SERVICE (otherwise the base SS is returned).
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 2d5f4c8..5d88cf0 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -344,6 +344,7 @@
      * </p>
      *
      * <p>Requires Permission:
+     * {@link android.Manifest.permission#SEND_SMS} and
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
      * privileges.
      * </p>
@@ -351,6 +352,10 @@
      * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
      */
     @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+            android.Manifest.permission.SEND_SMS
+    })
     public void sendTextMessageWithoutPersisting(
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1e6abf2..2396a9e 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -17,12 +17,14 @@
 package android.telephony;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.net.INetworkPolicyManager;
@@ -32,11 +34,14 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
 import android.util.DisplayMetrics;
+
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.ISub;
 import com.android.internal.telephony.ITelephonyRegistry;
 import com.android.internal.telephony.PhoneConstants;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -430,6 +435,26 @@
             = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED";
 
     /**
+     * Activity Action: Display UI for managing the billing relationship plans
+     * between a carrier and a specific subscriber.
+     * <p>
+     * Carrier apps are encouraged to implement this activity, and the OS will
+     * provide an affordance to quickly enter this activity, typically via
+     * Settings. This affordance will only be shown when the carrier app is
+     * actively providing subscription plan information via
+     * {@link #setSubscriptionPlans(int, List)}.
+     * <p>
+     * Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to indicate which subscription
+     * the user is interested in.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_MANAGE_SUBSCRIPTION_PLANS
+            = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS";
+
+    /**
      * Integer extra used with {@link #ACTION_DEFAULT_SUBSCRIPTION_CHANGED} and
      * {@link #ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED} to indicate the subscription
      * which has changed.
@@ -437,6 +462,7 @@
     public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
 
     private final Context mContext;
+    private final INetworkPolicyManager mNetworkPolicy;
 
     /**
      * A listener class for monitoring changes to {@link SubscriptionInfo} records.
@@ -509,22 +535,21 @@
     }
 
     /** @hide */
-    public SubscriptionManager(Context context) {
+    public SubscriptionManager(Context context) throws ServiceNotFoundException {
         if (DBG) logd("SubscriptionManager created");
         mContext = context;
+        mNetworkPolicy = INetworkPolicyManager.Stub
+                .asInterface(ServiceManager.getServiceOrThrow(Context.NETWORK_POLICY_SERVICE));
     }
 
     /**
-     * Get an instance of the SubscriptionManager from the Context.
-     * This invokes {@link android.content.Context#getSystemService
-     * Context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)}.
-     *
-     * @param context to use.
-     * @return SubscriptionManager instance
+     * @deprecated developers should always obtain references directly from
+     *             {@link Context#getSystemService(Class)}.
      */
+    @Deprecated
     public static SubscriptionManager from(Context context) {
-        return (SubscriptionManager) context.getSystemService(
-                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        return (SubscriptionManager) context
+                .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
     }
 
     /**
@@ -1622,11 +1647,9 @@
      */
     @SystemApi
     public @NonNull List<SubscriptionPlan> getSubscriptionPlans(int subId) {
-        final INetworkPolicyManager npm = INetworkPolicyManager.Stub
-                .asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
         try {
             SubscriptionPlan[] subscriptionPlans =
-                    npm.getSubscriptionPlans(subId, mContext.getOpPackageName());
+                    mNetworkPolicy.getSubscriptionPlans(subId, mContext.getOpPackageName());
             return subscriptionPlans == null
                     ? Collections.emptyList() : Arrays.asList(subscriptionPlans);
         } catch (RemoteException e) {
@@ -1654,13 +1677,52 @@
      */
     @SystemApi
     public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) {
-        final INetworkPolicyManager npm = INetworkPolicyManager.Stub
-                .asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
         try {
-            npm.setSubscriptionPlans(subId, plans.toArray(new SubscriptionPlan[plans.size()]),
-                    mContext.getOpPackageName());
+            mNetworkPolicy.setSubscriptionPlans(subId,
+                    plans.toArray(new SubscriptionPlan[plans.size()]), mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /** @hide */
+    private String getSubscriptionPlansOwner(int subId) {
+        try {
+            return mNetworkPolicy.getSubscriptionPlansOwner(subId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Create an {@link Intent} that will launch towards the carrier app that is
+     * currently defining the billing relationship plan through
+     * {@link #setSubscriptionPlans(int, List)}.
+     *
+     * @return ready to launch Intent targeted towards the carrier app, or
+     *         {@code null} if no carrier app is defined, or if the defined
+     *         carrier app provides no management activity.
+     * @hide
+     */
+    public @Nullable Intent createManageSubscriptionIntent(int subId) {
+        // Bail if no owner
+        final String owner = getSubscriptionPlansOwner(subId);
+        if (owner == null) return null;
+
+        // Bail if no plans
+        final List<SubscriptionPlan> plans = getSubscriptionPlans(subId);
+        if (plans.isEmpty()) return null;
+
+        final Intent intent = new Intent(ACTION_MANAGE_SUBSCRIPTION_PLANS);
+        intent.setPackage(owner);
+        intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId);
+
+        // Bail if not implemented
+        if (mContext.getPackageManager().queryIntentActivities(intent,
+                PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
+            return null;
+        }
+
+        return intent;
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9e77992..af5b190 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3155,6 +3155,7 @@
      * Initial SIM activation state, unknown. Not set by any carrier apps.
      * @hide
      */
+    @SystemApi
     public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0;
 
     /**
@@ -3165,12 +3166,14 @@
      * @see #SIM_ACTIVATION_STATE_RESTRICTED
      * @hide
      */
+    @SystemApi
     public static final int SIM_ACTIVATION_STATE_ACTIVATING = 1;
 
     /**
      * Indicate SIM has been successfully activated with full service
      * @hide
      */
+    @SystemApi
     public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2;
 
     /**
@@ -3180,6 +3183,7 @@
      * deactivated sim state and set it back to activated after successfully run activation service.
      * @hide
      */
+    @SystemApi
     public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3;
 
     /**
@@ -3187,14 +3191,47 @@
      * note this is currently available for data activation state. For example out of byte sim.
      * @hide
      */
+    @SystemApi
     public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4;
 
+    /** @hide */
+    @IntDef({
+            SIM_ACTIVATION_STATE_UNKNOWN,
+            SIM_ACTIVATION_STATE_ACTIVATING,
+            SIM_ACTIVATION_STATE_ACTIVATED,
+            SIM_ACTIVATION_STATE_DEACTIVATED,
+            SIM_ACTIVATION_STATE_RESTRICTED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SimActivationState{}
+
+     /**
+      * Sets the voice activation state
+      *
+      * <p>Requires Permission:
+      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+      * Or the calling app has carrier privileges.
+      *
+      * @param activationState The voice activation state
+      * @see #SIM_ACTIVATION_STATE_UNKNOWN
+      * @see #SIM_ACTIVATION_STATE_ACTIVATING
+      * @see #SIM_ACTIVATION_STATE_ACTIVATED
+      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+      * @see #hasCarrierPrivileges
+      * @hide
+      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setVoiceActivationState(@SimActivationState int activationState) {
+        setVoiceActivationState(getSubId(), activationState);
+    }
+
     /**
      * Sets the voice activation state for the given subscriber.
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
-     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     * Or the calling app has carrier privileges.
      *
      * @param subId The subscription id.
      * @param activationState The voice activation state of the given subscriber.
@@ -3202,24 +3239,48 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATING
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #hasCarrierPrivileges
      * @hide
      */
-    public void setVoiceActivationState(int subId, int activationState) {
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setVoiceActivationState(int subId, @SimActivationState int activationState) {
         try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null)
-                telephony.setVoiceActivationState(subId, activationState);
-        } catch (RemoteException ex) {
-        } catch (NullPointerException ex) {
-        }
+           ITelephony telephony = getITelephony();
+           if (telephony != null)
+               telephony.setVoiceActivationState(subId, activationState);
+       } catch (RemoteException ex) {
+       } catch (NullPointerException ex) {
+       }
+    }
+
+    /**
+     * Sets the data activation state
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     *
+     * @param activationState The data activation state
+     * @see #SIM_ACTIVATION_STATE_UNKNOWN
+     * @see #SIM_ACTIVATION_STATE_ACTIVATING
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @see #hasCarrierPrivileges
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setDataActivationState(@SimActivationState int activationState) {
+        setDataActivationState(getSubId(), activationState);
     }
 
     /**
      * Sets the data activation state for the given subscriber.
      *
      * <p>Requires Permission:
-     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE}
-     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges.
      *
      * @param subId The subscription id.
      * @param activationState The data activation state of the given subscriber.
@@ -3228,9 +3289,11 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
      * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @see #hasCarrierPrivileges
      * @hide
      */
-    public void setDataActivationState(int subId, int activationState) {
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setDataActivationState(int subId, @SimActivationState int activationState) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
@@ -3241,8 +3304,33 @@
     }
 
     /**
+     * Returns the voice activation state
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     *
+     * @return voiceActivationState
+     * @see #SIM_ACTIVATION_STATE_UNKNOWN
+     * @see #SIM_ACTIVATION_STATE_ACTIVATING
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #hasCarrierPrivileges
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @SimActivationState int getVoiceActivationState() {
+        return getVoiceActivationState(getSubId());
+    }
+
+    /**
      * Returns the voice activation state for the given subscriber.
      *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     *
      * @param subId The subscription id.
      *
      * @return voiceActivationState for the given subscriber
@@ -3250,10 +3338,11 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATING
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #hasCarrierPrivileges
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public int getVoiceActivationState(int subId) {
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @SimActivationState int getVoiceActivationState(int subId) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
@@ -3265,8 +3354,34 @@
     }
 
     /**
+     * Returns the data activation state
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     *
+     * @return dataActivationState for the given subscriber
+     * @see #SIM_ACTIVATION_STATE_UNKNOWN
+     * @see #SIM_ACTIVATION_STATE_ACTIVATING
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @see #hasCarrierPrivileges
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @SimActivationState int getDataActivationState() {
+        return getDataActivationState(getSubId());
+    }
+
+    /**
      * Returns the data activation state for the given subscriber.
      *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     *
      * @param subId The subscription id.
      *
      * @return dataActivationState for the given subscriber
@@ -3275,10 +3390,11 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
      * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @see #hasCarrierPrivileges
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public int getDataActivationState(int subId) {
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @SimActivationState int getDataActivationState(int subId) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
@@ -4849,15 +4965,14 @@
      * Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
-     *
-     * @hide
-     * TODO: Add an overload that takes no args.
      */
-    public void setNetworkSelectionModeAutomatic(int subId) {
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setNetworkSelectionModeAutomatic() {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null)
-                telephony.setNetworkSelectionModeAutomatic(subId);
+            if (telephony != null) {
+                telephony.setNetworkSelectionModeAutomatic(getSubId());
+            }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "setNetworkSelectionModeAutomatic RemoteException", ex);
         } catch (NullPointerException ex) {
@@ -4905,9 +5020,9 @@
      *
      * @param request Contains all the RAT with bands/channels that need to be scanned.
      * @param callback Returns network scan results or errors.
-     * @return A NetworkScan obj which contains a callback which can stop the scan.
-     * @hide
+     * @return A NetworkScan obj which contains a callback which can be used to stop the scan.
      */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public NetworkScan requestNetworkScan(
             NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) {
         synchronized (this) {
@@ -4926,15 +5041,20 @@
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
-     * @hide
-     * TODO: Add an overload that takes no args.
+     * @param operatorNumeric the PLMN ID of the network to select.
+     * @param persistSelection whether the selection will persist until reboot. If true, only allows
+     * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume
+     * normal network selection next time.
+     * @return true on success; false on any failure.
      */
-    public boolean setNetworkSelectionModeManual(int subId, OperatorInfo operator,
-            boolean persistSelection) {
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public boolean setNetworkSelectionModeManual(String operatorNumeric, boolean persistSelection) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.setNetworkSelectionModeManual(subId, operator, persistSelection);
+            if (telephony != null) {
+                return telephony.setNetworkSelectionModeManual(
+                        getSubId(), operatorNumeric, persistSelection);
+            }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "setNetworkSelectionModeManual RemoteException", ex);
         } catch (NullPointerException ex) {
@@ -4959,8 +5079,9 @@
     public boolean setPreferredNetworkType(int subId, int networkType) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null)
+            if (telephony != null) {
                 return telephony.setPreferredNetworkType(subId, networkType);
+            }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex);
         } catch (NullPointerException ex) {
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 7bcdcdc..c182e34 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -38,7 +38,6 @@
 
 /**
  * Manages the radio access network scan requests and callbacks.
- * @hide
  */
 public final class TelephonyScanManager {
 
@@ -55,7 +54,8 @@
     public static final int CALLBACK_SCAN_COMPLETE = 3;
 
     /**
-     * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should
+     * The caller of
+     * {@link TelephonyManager#requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should
      * implement and provide this callback so that the scan results or errors can be returned.
      */
     public static abstract class NetworkScanCallback {
@@ -75,8 +75,10 @@
          *
          * This callback will be called whenever there is any error about the scan, and the scan
          * will be terminated. onComplete() will NOT be called.
+         *
+         * @param error Error code when the scan is failed, as defined in {@link NetworkScan}.
          */
-        public void onError(int error) {}
+        public void onError(@NetworkScan.ScanErrorCode int error) {}
     }
 
     private static class NetworkScanInfo {
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index e42a758..3937201 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -32,6 +32,7 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Describes a single UICC access rule according to the GlobalPlatform Secure Element Access Control
@@ -205,6 +206,21 @@
     }
 
     @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        UiccAccessRule that = (UiccAccessRule) obj;
+        return Arrays.equals(mCertificateHash, that.mCertificateHash)
+                && Objects.equals(mPackageName, that.mPackageName)
+                && mAccessType == that.mAccessType;
+    }
+
+    @Override
     public String toString() {
         return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " +
                 mPackageName + " access: " + mAccessType;
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 973df31..2534327 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -15,8 +15,10 @@
  */
 package android.telephony.euicc;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -29,6 +31,9 @@
 
 import com.android.internal.telephony.euicc.IEuiccController;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * EuiccManager is the application interface to eUICCs, or eSIMs/embedded SIMs.
  *
@@ -55,6 +60,20 @@
     public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
             "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
 
+
+    /**
+     * Broadcast Action: The eUICC OTA status is changed.
+     * <p class="note">
+     * Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+     *
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system.
+     * TODO(b/35851809): Make this a SystemApi.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_OTA_STATUS_CHANGED
+            = "android.telephony.euicc.action.OTA_STATUS_CHANGED";
+
     /**
      * Intent action to provision an embedded subscription.
      *
@@ -167,6 +186,35 @@
      */
     public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon";
 
+    /**
+     * Euicc OTA update status which can be got by {@link #getOtaStatus}
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"EUICC_OTA_"}, value = {
+            EUICC_OTA_IN_PROGRESS,
+            EUICC_OTA_FAILED,
+            EUICC_OTA_SUCCEEDED,
+            EUICC_OTA_NOT_NEEDED,
+            EUICC_OTA_STATUS_UNAVAILABLE
+
+    })
+    public @interface OtaStatus{}
+
+    /**
+     * An OTA is in progress. During this time, the eUICC is not available and the user may lose
+     * network access.
+     */
+    public static final int EUICC_OTA_IN_PROGRESS = 1;
+    /** The OTA update failed. */
+    public static final int EUICC_OTA_FAILED = 2;
+    /** The OTA update finished successfully. */
+    public static final int EUICC_OTA_SUCCEEDED = 3;
+    /** The OTA update not needed since current eUICC OS is latest. */
+    public static final int EUICC_OTA_NOT_NEEDED = 4;
+    /** The OTA status is unavailable since eUICC service is unavailable. */
+    public static final int EUICC_OTA_STATUS_UNAVAILABLE = 5;
+
     private final Context mContext;
 
     /** @hide */
@@ -211,6 +259,26 @@
     }
 
     /**
+     * Returns the current status of eUICC OTA.
+     *
+     * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+     *
+     * @return the status of eUICC OTA. If {@link #isEnabled()} is false or the eUICC is not ready,
+     *     {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned.
+     * TODO(b/35851809): Make this a SystemApi.
+     */
+    public int getOtaStatus() {
+        if (!isEnabled()) {
+            return EUICC_OTA_STATUS_UNAVAILABLE;
+        }
+        try {
+            return getIEuiccController().getOtaStatus();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Attempt to download the given {@link DownloadableSubscription}.
      *
      * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission,
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index 8529f52..f78e7a6 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -51,8 +51,8 @@
     /** @hide */
     public ServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales,
             String newServiceId, Date start, Date end) {
-        if (newNames == null || newNames.isEmpty() || TextUtils.isEmpty(newClassName)
-                || newLocales == null || newLocales.isEmpty() || TextUtils.isEmpty(newServiceId)
+        if (newNames == null || newClassName == null
+                || newLocales == null || newServiceId == null
                 || start == null || end == null) {
             throw new IllegalArgumentException("Bad ServiceInfo construction");
         }
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
index 6ad54c1..4f6f68c 100644
--- a/telephony/java/com/android/ims/ImsReasonInfo.java
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -104,6 +104,9 @@
     // MT : No action from user after alerting the call
     public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203;
 
+    //Call was blocked by call barring
+    public static final int CODE_CALL_BARRED = 240;
+
     //Call failures for FDN
     public static final int CODE_FDN_BLOCKED = 241;
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 8ea53a5..416146f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -846,13 +846,13 @@
      * Ask the radio to connect to the input network and change selection mode to manual.
      *
      * @param subId the id of the subscription.
-     * @param operatorInfo the operator to attach to.
-     * @param persistSelection should the selection persist till reboot or its
-     *        turned off? Will also result in notification being not shown to
-     *        the user if the signal is lost.
+     * @param operatorNumeric the PLMN of the operator to attach to.
+     * @param persistSelection Whether the selection will persist until reboot. If true, only allows
+     * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume
+     * normal network selection next time.
      * @return true if the request suceeded.
      */
-    boolean setNetworkSelectionModeManual(int subId, in OperatorInfo operator,
+    boolean setNetworkSelectionModeManual(int subId, in String operatorNumeric,
             boolean persistSelection);
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index e2d25b8..f804cb0 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -417,6 +417,8 @@
     int RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION = 141;
     int RIL_REQUEST_START_NETWORK_SCAN = 142;
     int RIL_REQUEST_STOP_NETWORK_SCAN = 143;
+    int RIL_REQUEST_GET_SLOT_STATUS = 144;
+    int RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING = 145;
 
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
 
@@ -471,4 +473,5 @@
     int RIL_UNSOL_MODEM_RESTART = 1047;
     int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048;
     int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049;
+    int RIL_UNSOL_ICC_SLOT_STATUS = 1050;
 }
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index b3fc90d..0a0ad90 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -30,6 +30,7 @@
     oneway void getDefaultDownloadableSubscriptionList(
         String callingPackage, in PendingIntent callbackIntent);
     String getEid();
+    int getOtaStatus();
     oneway void downloadSubscription(in DownloadableSubscription subscription,
         boolean switchAfterDownload, String callingPackage, in PendingIntent callbackIntent);
     EuiccInfo getEuiccInfo();
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 30c9af1..0088962 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -63,6 +63,26 @@
     jarjar_rules: "jarjar-rules.txt",
 }
 
+// Build the android.test.base-minus-junit library
+// ===============================================
+// This contains the android.test classes from android.test.base plus
+// the com.android.internal.util.Predicate[s] classes. This is only
+// intended for inclusion in the android.test.legacy static library and
+// must not be used elsewhere.
+java_library_static {
+    name: "android.test.base-minus-junit",
+
+    srcs: [
+        "src/android/**/*.java",
+        "src/com/**/*.java",
+    ],
+
+    sdk_version: "current",
+    libs: [
+        "junit",
+    ],
+}
+
 // Build the legacy-android-test library
 // =====================================
 // This contains the android.test classes that were in Android API level 25,
diff --git a/test-base/Android.mk b/test-base/Android.mk
index 5e5d040..8613854 100644
--- a/test-base/Android.mk
+++ b/test-base/Android.mk
@@ -112,17 +112,6 @@
 
 endif  # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
 
-# Build the legacy.test.stubs library
-# ===================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := legacy.test.stubs
-LOCAL_SDK_VERSION := current
-
-LOCAL_STATIC_JAVA_LIBRARIES := android.test.base.stubs
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
 ifeq ($(HOST_OS),linux)
 # Build the legacy-performance-test-hostdex library
 # =================================================
diff --git a/test-mock/api/android-test-mock-current.txt b/test-mock/api/android-test-mock-current.txt
index 48a1f80..07acfef 100644
--- a/test-mock/api/android-test-mock-current.txt
+++ b/test-mock/api/android-test-mock-current.txt
@@ -1,5 +1,9 @@
 package android.test.mock {
 
+  public deprecated class MockAccountManager {
+    method public static android.accounts.AccountManager newMockAccountManager(android.content.Context);
+  }
+
   public deprecated class MockApplication extends android.app.Application {
     ctor public MockApplication();
   }
@@ -9,6 +13,7 @@
     ctor public MockContentProvider(android.content.Context);
     ctor public MockContentProvider(android.content.Context, java.lang.String, java.lang.String, android.content.pm.PathPermission[]);
     method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>);
+    method public static deprecated void attachInfoForTesting(android.content.ContentProvider, android.content.Context, android.content.pm.ProviderInfo);
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public java.lang.String getType(android.net.Uri);
     method public android.net.Uri insert(android.net.Uri, android.content.ContentValues);
@@ -289,5 +294,9 @@
     method public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
   }
 
+  public deprecated class MockService {
+    method public static <T extends android.app.Service> void attachForTesting(android.app.Service, android.content.Context, java.lang.String, android.app.Application);
+  }
+
 }
 
diff --git a/test-mock/src/android/test/mock/MockAccountManager.java b/test-mock/src/android/test/mock/MockAccountManager.java
new file mode 100644
index 0000000..c9b4c7b
--- /dev/null
+++ b/test-mock/src/android/test/mock/MockAccountManager.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 android.test.mock;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OnAccountsUpdateListener;
+import android.accounts.OperationCanceledException;
+import android.content.Context;
+import android.os.Handler;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A mock {@link android.accounts.AccountManager} class.
+ *
+ * <p>Provided for use by {@code android.test.IsolatedContext}.
+ *
+ * @deprecated Use a mocking framework like <a href="https://github.com/mockito/mockito">Mockito</a>.
+ * New tests should be written using the
+ * <a href="{@docRoot}
+ * tools/testing-support-library/index.html">Android Testing Support Library</a>.
+ */
+@Deprecated
+public class MockAccountManager {
+
+    /**
+     * Create a new mock {@link AccountManager} instance.
+     *
+     * @param context the {@link Context} to which the returned object belongs.
+     * @return the new instance.
+     */
+    public static AccountManager newMockAccountManager(Context context) {
+        return new MockAccountManagerImpl(context);
+    }
+
+    private MockAccountManager() {
+    }
+
+    private static class MockAccountManagerImpl extends AccountManager {
+
+        MockAccountManagerImpl(Context context) {
+            super(context, null /* IAccountManager */, null /* handler */);
+        }
+
+        public void addOnAccountsUpdatedListener(OnAccountsUpdateListener listener,
+                Handler handler, boolean updateImmediately) {
+            // do nothing
+        }
+
+        public Account[] getAccounts() {
+            return new Account[] {};
+        }
+
+        public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
+                final String type, final String[] features,
+                AccountManagerCallback<Account[]> callback, Handler handler) {
+            return new MockAccountManagerFuture<Account[]>(new Account[0]);
+        }
+
+        public String blockingGetAuthToken(Account account, String authTokenType,
+                boolean notifyAuthFailure)
+                throws OperationCanceledException, IOException, AuthenticatorException {
+            return null;
+        }
+    }
+
+    /**
+     * A very simple AccountManagerFuture class
+     * that returns what ever was passed in
+     */
+    private static class MockAccountManagerFuture<T>
+            implements AccountManagerFuture<T> {
+
+        T mResult;
+
+        MockAccountManagerFuture(T result) {
+            mResult = result;
+        }
+
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            return false;
+        }
+
+        public boolean isCancelled() {
+            return false;
+        }
+
+        public boolean isDone() {
+            return true;
+        }
+
+        public T getResult()
+                throws OperationCanceledException, IOException, AuthenticatorException {
+            return mResult;
+        }
+
+        public T getResult(long timeout, TimeUnit unit)
+                throws OperationCanceledException, IOException, AuthenticatorException {
+            return getResult();
+        }
+    }
+}
diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java
index d5f3ce8..b917fbd 100644
--- a/test-mock/src/android/test/mock/MockContentProvider.java
+++ b/test-mock/src/android/test/mock/MockContentProvider.java
@@ -277,4 +277,21 @@
     public final IContentProvider getIContentProvider() {
         return mIContentProvider;
     }
+
+    /**
+     * Like {@link #attachInfo(Context, android.content.pm.ProviderInfo)}, but for use
+     * when directly instantiating the provider for testing.
+     *
+     * <p>Provided for use by {@code android.test.ProviderTestCase2} and
+     * {@code android.test.RenamingDelegatingContext}.
+     *
+     * @deprecated Use a mocking framework like <a href="https://github.com/mockito/mockito">Mockito</a>.
+     * New tests should be written using the
+     * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
+     */
+    @Deprecated
+    public static void attachInfoForTesting(
+            ContentProvider provider, Context context, ProviderInfo providerInfo) {
+        provider.attachInfoForTesting(context, providerInfo);
+    }
 }
diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java
index ce8019f..7207ebc 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -1090,15 +1090,6 @@
      * @hide
      */
     @Override
-    public void installPackage(Uri packageURI, PackageInstallObserver observer,
-            int flags, String installerPackageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @hide
-     */
-    @Override
     public void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, int targetUserId,
             int flags) {
         throw new UnsupportedOperationException();
diff --git a/test-mock/src/android/test/mock/MockService.java b/test-mock/src/android/test/mock/MockService.java
new file mode 100644
index 0000000..dbba4f3
--- /dev/null
+++ b/test-mock/src/android/test/mock/MockService.java
@@ -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.
+ */
+package android.test.mock;
+
+import android.app.Application;
+import android.app.Service;
+import android.content.Context;
+
+/**
+ * A mock {@link android.app.Service} class.
+ *
+ * <p>Provided for use by {@code android.test.ServiceTestCase}.
+ *
+ * @deprecated Use a mocking framework like <a href="https://github.com/mockito/mockito">Mockito</a>.
+ * New tests should be written using the
+ * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
+ */
+@Deprecated
+public class MockService {
+
+    public static <T extends Service> void attachForTesting(Service service, Context context,
+            String serviceClassName,
+            Application application) {
+        service.attach(
+                context,
+                null,               // ActivityThread not actually used in Service
+                serviceClassName,
+                null,               // token not needed when not talking with the activity manager
+                application,
+                null                // mocked services don't talk with the activity manager
+        );
+    }
+
+    private MockService() {
+    }
+}
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 706f636..f5c2bc6 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -117,5 +117,20 @@
 
 endif  # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
 
+# Build the android.test.legacy library
+# =====================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android.test.legacy
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src/android)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_LIBRARIES := android.test.mock.stubs junit
+LOCAL_STATIC_JAVA_LIBRARIES := android.test.base-minus-junit
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
 # additionally, build unit tests in a separate .apk
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/test-runner/src/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java
index 0b77c00..6e4c41e 100644
--- a/test-runner/src/android/test/IsolatedContext.java
+++ b/test-runner/src/android/test/IsolatedContext.java
@@ -17,12 +17,6 @@
 package android.test;
 
 import android.accounts.AccountManager;
-import android.accounts.AccountManagerCallback;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OnAccountsUpdateListener;
-import android.accounts.OperationCanceledException;
-import android.accounts.Account;
 import android.content.ContextWrapper;
 import android.content.ContentResolver;
 import android.content.Intent;
@@ -32,12 +26,10 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.Uri;
-import android.os.Handler;
+import android.test.mock.MockAccountManager;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
 import java.util.List;
 
 
@@ -52,7 +44,7 @@
 public class IsolatedContext extends ContextWrapper {
 
     private ContentResolver mResolver;
-    private final MockAccountManager mMockAccountManager;
+    private final AccountManager mMockAccountManager;
 
     private List<Intent> mBroadcastIntents = new ArrayList<>();
 
@@ -60,7 +52,7 @@
             ContentResolver resolver, Context targetContext) {
         super(targetContext);
         mResolver = resolver;
-        mMockAccountManager = new MockAccountManager();
+        mMockAccountManager = MockAccountManager.newMockAccountManager(IsolatedContext.this);
     }
 
     /** Returns the list of intents that were broadcast since the last call to this method. */
@@ -123,71 +115,6 @@
         return null;
     }
 
-    private class MockAccountManager extends AccountManager {
-        public MockAccountManager() {
-            super(IsolatedContext.this, null /* IAccountManager */, null /* handler */);
-        }
-
-        public void addOnAccountsUpdatedListener(OnAccountsUpdateListener listener,
-                Handler handler, boolean updateImmediately) {
-            // do nothing
-        }
-
-        public Account[] getAccounts() {
-            return new Account[]{};
-        }
-
-        public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
-                final String type, final String[] features,
-                AccountManagerCallback<Account[]> callback, Handler handler) {
-            return new MockAccountManagerFuture<Account[]>(new Account[0]);
-        }
-
-        public String blockingGetAuthToken(Account account, String authTokenType,
-                boolean notifyAuthFailure)
-                throws OperationCanceledException, IOException, AuthenticatorException {
-            return null;
-        }
-
-
-        /**
-         * A very simple AccountManagerFuture class
-         * that returns what ever was passed in
-         */
-        private class MockAccountManagerFuture<T>
-                implements AccountManagerFuture<T> {
-
-            T mResult;
-
-            public MockAccountManagerFuture(T result) {
-                mResult = result;
-            }
-
-            public boolean cancel(boolean mayInterruptIfRunning) {
-                return false;
-            }
-
-            public boolean isCancelled() {
-                return false;
-            }
-
-            public boolean isDone() {
-                return true;
-            }
-
-            public T getResult()
-                    throws OperationCanceledException, IOException, AuthenticatorException {
-                return mResult;
-            }
-
-            public T getResult(long timeout, TimeUnit unit)
-                    throws OperationCanceledException, IOException, AuthenticatorException {
-                return getResult();
-            }
-        }
-
-    }
-
     @Override
     public File getFilesDir() {
         return new File("/dev/null");
diff --git a/test-runner/src/android/test/ProviderTestCase2.java b/test-runner/src/android/test/ProviderTestCase2.java
index 1fa633e..be18b53 100644
--- a/test-runner/src/android/test/ProviderTestCase2.java
+++ b/test-runner/src/android/test/ProviderTestCase2.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.pm.ProviderInfo;
 import android.content.res.Resources;
+import android.test.mock.MockContentProvider;
 import android.test.mock.MockContext;
 import android.test.mock.MockContentResolver;
 import android.database.DatabaseUtils;
@@ -152,7 +153,7 @@
         T instance = providerClass.newInstance();
         ProviderInfo providerInfo = new ProviderInfo();
         providerInfo.authority = authority;
-        instance.attachInfoForTesting(context, providerInfo);
+        MockContentProvider.attachInfoForTesting(instance, context, providerInfo);
         return instance;
     }
 
diff --git a/test-runner/src/android/test/RenamingDelegatingContext.java b/test-runner/src/android/test/RenamingDelegatingContext.java
index fd33321..10ccebc 100644
--- a/test-runner/src/android/test/RenamingDelegatingContext.java
+++ b/test-runner/src/android/test/RenamingDelegatingContext.java
@@ -21,6 +21,7 @@
 import android.content.ContentProvider;
 import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
+import android.test.mock.MockContentProvider;
 import android.util.Log;
 
 import java.io.File;
@@ -71,7 +72,7 @@
         if (allowAccessToExistingFilesAndDbs) {
             mContext.makeExistingFilesAndDbsAccessible();
         }
-        mProvider.attachInfoForTesting(mContext, null);
+        MockContentProvider.attachInfoForTesting(mProvider, mContext, null);
         return mProvider;
     }
 
diff --git a/test-runner/src/android/test/ServiceTestCase.java b/test-runner/src/android/test/ServiceTestCase.java
index c8ff0f9..cd54955 100644
--- a/test-runner/src/android/test/ServiceTestCase.java
+++ b/test-runner/src/android/test/ServiceTestCase.java
@@ -23,6 +23,7 @@
 import android.os.IBinder;
 import android.test.mock.MockApplication;
 
+import android.test.mock.MockService;
 import java.util.Random;
 
 /**
@@ -163,14 +164,8 @@
         if (getApplication() == null) {
             setApplication(new MockApplication());
         }
-        mService.attach(
-                getContext(),
-                null,               // ActivityThread not actually used in Service
-                mServiceClass.getName(),
-                null,               // token not needed when not talking with the activity manager
-                getApplication(),
-                null                // mocked services don't talk with the activity manager
-                );
+        MockService.attachForTesting(
+                mService, getContext(), mServiceClass.getName(), getApplication());
 
         assertNotNull(mService);
 
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk
index 088f322..6e0d58a 100644
--- a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk
@@ -40,6 +40,6 @@
 
 LOCAL_CFLAGS += -Wall -Wextra -Werror -Wno-unused-parameter
 
-LOCAL_NDK_STL_VARIANT := stlport_static
+LOCAL_NDK_STL_VARIANT := c++_static
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
new file mode 100644
index 0000000..7187a37
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -0,0 +1,49 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+# Build a tiny library that the test app can dynamically load
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := DexLoggerTestLibrary
+LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/dcl)
+
+include $(BUILD_JAVA_LIBRARY)
+
+dexloggertest_jar := $(LOCAL_BUILT_MODULE)
+
+
+# Build the test app itself
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    truth-prebuilt \
+
+# This gets us the javalib.jar built by DexLoggerTestLibrary above.
+LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar)
+
+include $(BUILD_PACKAGE)
diff --git a/tests/DexLoggerIntegrationTests/AndroidManifest.xml b/tests/DexLoggerIntegrationTests/AndroidManifest.xml
new file mode 100644
index 0000000..a847e8f
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.dexloggertest">
+
+    <!-- Tests feature introduced in P (27) -->
+    <uses-sdk
+        android:minSdkVersion="27"
+        android:targetSdkVersion="27" />
+
+    <uses-permission android:name="android.permission.READ_LOGS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.dexloggertest"
+        android:label="Integration test for DexLogger" />
+</manifest>
diff --git a/tests/DexLoggerIntegrationTests/AndroidTest.xml b/tests/DexLoggerIntegrationTests/AndroidTest.xml
new file mode 100644
index 0000000..8ed19f8
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs DexLogger Integration Tests">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="DexLoggerIntegrationTests.apk"/>
+        <option name="cleanup-apks" value="true"/>
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="DexLoggerIntegrationTests"/>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.frameworks.dexloggertest"/>
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java b/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java
new file mode 100644
index 0000000..e995a26
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java
@@ -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 com.android.dcl;
+
+/** Dummy class which is built into a jar purely so we can pass it to DexClassLoader. */
+public final class Simple {
+    public Simple() {}
+}
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
new file mode 100644
index 0000000..d8b3b20
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -0,0 +1,154 @@
+/*
+ * 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.pm.dex;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.util.EventLog;
+import dalvik.system.DexClassLoader;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.List;
+
+/**
+ * Integration tests for {@link com.android.server.pm.dex.DexLogger}.
+ *
+ * The setup for the test dynamically loads code in a jar extracted
+ * from our assets (a secondary dex file).
+ *
+ * We then use adb to trigger secondary dex file reconcilation (and
+ * wait for it to complete). As a side-effect of this DexLogger should
+ * be notified of the file and should log the hash of the file's name
+ * and content.  We verify that this message appears in the event log.
+ *
+ * Run with "atest DexLoggerIntegrationTests".
+ */
+@LargeTest
+@RunWith(JUnit4.class)
+public final class DexLoggerIntegrationTests {
+
+    private static final String PACKAGE_NAME = "com.android.frameworks.dexloggertest";
+
+    // Event log tag used for SNET related events
+    private static final int SNET_TAG = 0x534e4554;
+    // Subtag used to distinguish dynamic code loading events
+    private static final String DCL_SUBTAG = "dcl";
+
+    // Obtained via "echo -n copied.jar | sha256sum"
+    private static final String EXPECTED_NAME_HASH =
+            "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
+
+    private static String expectedContentHash;
+
+    @BeforeClass
+    public static void setUpAll() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        MessageDigest hasher = MessageDigest.getInstance("SHA-256");
+
+        // Copy the jar from our Java resources to a private data directory
+        File privateCopy = new File(context.getDir("jars", Context.MODE_PRIVATE), "copied.jar");
+        Class<?> thisClass = DexLoggerIntegrationTests.class;
+        try (InputStream input = thisClass.getResourceAsStream("/javalib.jar");
+                OutputStream output = new FileOutputStream(privateCopy)) {
+            byte[] buffer = new byte[1024];
+            while (true) {
+                int numRead = input.read(buffer);
+                if (numRead < 0) {
+                    break;
+                }
+                output.write(buffer, 0, numRead);
+                hasher.update(buffer, 0, numRead);
+            }
+        }
+
+        // Remember the SHA-256 of the file content to check that it is the same as
+        // the value we see logged.
+        Formatter formatter = new Formatter();
+        for (byte b : hasher.digest()) {
+            formatter.format("%02X", b);
+        }
+        expectedContentHash = formatter.toString();
+
+        // Feed the jar to a class loader and make sure it contains what we expect.
+        ClassLoader loader =
+                new DexClassLoader(
+                    privateCopy.toString(), null, null, context.getClass().getClassLoader());
+        loader.loadClass("com.android.dcl.Simple");
+    }
+
+    @Test
+    public void testDexLoggerReconcileGeneratesEvents() throws Exception {
+        int[] tagList = new int[] { SNET_TAG };
+        List<EventLog.Event> events = new ArrayList<>();
+
+        // There may already be events in the event log - figure out the most recent one
+        EventLog.readEvents(tagList, events);
+        long previousEventNanos =
+                events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
+        events.clear();
+
+        Process process = Runtime.getRuntime().exec(
+            "cmd package reconcile-secondary-dex-files " + PACKAGE_NAME);
+        int exitCode = process.waitFor();
+        assertThat(exitCode).isEqualTo(0);
+
+        int myUid = android.os.Process.myUid();
+        String expectedMessage = EXPECTED_NAME_HASH + " " + expectedContentHash;
+
+        EventLog.readEvents(tagList, events);
+        boolean found = false;
+        for (EventLog.Event event : events) {
+            if (event.getTimeNanos() <= previousEventNanos) {
+                continue;
+            }
+            Object[] data = (Object[]) event.getData();
+
+            // We only care about DCL events that we generated.
+            String subTag = (String) data[0];
+            if (!DCL_SUBTAG.equals(subTag)) {
+                continue;
+            }
+            int uid = (int) data[1];
+            if (uid != myUid) {
+                continue;
+            }
+
+            String message = (String) data[2];
+            assertThat(message).isEqualTo(expectedMessage);
+            found = true;
+        }
+
+        assertThat(found).isTrue();
+    }
+}
diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
index f7ce2c7..8d8fc84 100644
--- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
+++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
@@ -38,7 +38,7 @@
             public Engine onCreateEngine() {
                 return new Engine() {
                     @Override
-                    public void onAmbientModeChanged(boolean inAmbientMode) {
+                    public void onAmbientModeChanged(boolean inAmbientMode, boolean animated) {
                         ambientModeChangedCount[0]++;
                     }
                 };
@@ -47,12 +47,12 @@
         WallpaperService.Engine engine = service.onCreateEngine();
         engine.setCreated(true);
 
-        engine.doAmbientModeChanged(false);
+        engine.doAmbientModeChanged(false, false);
         assertFalse("ambient mode should be false", engine.isInAmbientMode());
         assertEquals("onAmbientModeChanged should have been called",
                 ambientModeChangedCount[0], 1);
 
-        engine.doAmbientModeChanged(true);
+        engine.doAmbientModeChanged(true, false);
         assertTrue("ambient mode should be false", engine.isInAmbientMode());
         assertEquals("onAmbientModeChanged should have been called",
                 ambientModeChangedCount[0], 2);
diff --git a/tests/JankBench/Android.mk b/tests/JankBench/Android.mk
new file mode 100644
index 0000000..12568a0
--- /dev/null
+++ b/tests/JankBench/Android.mk
@@ -0,0 +1,38 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MANIFEST_FILE := app/src/main/AndroidManifest.xml
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_USE_AAPT2 := true
+
+# omit gradle 'build' dir
+LOCAL_SRC_FILES := $(call all-java-files-under,app/src/main/java)
+
+# use appcompat/support lib from the tree, so improvements/
+# regressions are reflected in test data
+LOCAL_RESOURCE_DIR := \
+    $(LOCAL_PATH)/app/src/main/res \
+
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    android-support-design \
+    android-support-v4 \
+    android-support-v7-appcompat \
+    android-support-v7-cardview \
+    android-support-v7-recyclerview \
+    android-support-v17-leanback \
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    apache-commons-math \
+    junit
+
+
+LOCAL_PACKAGE_NAME := JankBench
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java b/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java
new file mode 100644
index 0000000..79aff90
--- /dev/null
+++ b/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+/*
+ * 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.benchmark;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+    public ApplicationTest() {
+        super(Application.class);
+    }
+}
diff --git a/tests/JankBench/app/src/main/AndroidManifest.xml b/tests/JankBench/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..58aa66f
--- /dev/null
+++ b/tests/JankBench/app/src/main/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  ~
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.benchmark">
+
+    <uses-sdk android:minSdkVersion="24" />
+
+    <android:uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <android:uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <android:uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".app.HomeActivity"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".app.RunLocalBenchmarksActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.benchmark.ACTION_BENCHMARK" />
+            </intent-filter>
+
+            <meta-data
+                android:name="com.android.benchmark.benchmark_group"
+                android:resource="@xml/benchmark" />
+        </activity>
+        <activity android:name=".ui.ListViewScrollActivity" />
+        <activity android:name=".ui.ImageListViewScrollActivity" />
+        <activity android:name=".ui.ShadowGridActivity" />
+        <activity android:name=".ui.TextScrollActivity" />
+        <activity android:name=".ui.EditTextInputActivity" />
+        <activity android:name=".synthetic.MemoryActivity" />
+        <activity android:name=".ui.FullScreenOverdrawActivity"></activity>
+        <activity android:name=".ui.BitmapUploadActivity"></activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java
new file mode 100644
index 0000000..b0a97ae0
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java
@@ -0,0 +1,40 @@
+/*
+ * 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.benchmark.app;
+
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.benchmark.R;
+
+/**
+ * Fragment for the Benchmark dashboard
+ */
+public class BenchmarkDashboardFragment extends Fragment {
+
+    public BenchmarkDashboardFragment() {
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_dashboard, container, false);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java
new file mode 100644
index 0000000..7419b30
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java
@@ -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.
+ *
+ */
+
+package com.android.benchmark.app;
+
+import android.graphics.Typeface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import com.android.benchmark.registry.BenchmarkGroup;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.R;
+
+/**
+ *
+ */
+public class BenchmarkListAdapter extends BaseExpandableListAdapter {
+
+    private final LayoutInflater mInflater;
+    private final BenchmarkRegistry mRegistry;
+
+    BenchmarkListAdapter(LayoutInflater inflater,
+                         BenchmarkRegistry registry) {
+        mInflater = inflater;
+        mRegistry = registry;
+    }
+
+    @Override
+    public int getGroupCount() {
+        return mRegistry.getGroupCount();
+    }
+
+    @Override
+    public int getChildrenCount(int groupPosition) {
+        return mRegistry.getBenchmarkCount(groupPosition);
+    }
+
+    @Override
+    public Object getGroup(int groupPosition) {
+        return mRegistry.getBenchmarkGroup(groupPosition);
+    }
+
+    @Override
+    public Object getChild(int groupPosition, int childPosition) {
+        BenchmarkGroup benchmarkGroup = mRegistry.getBenchmarkGroup(groupPosition);
+
+        if (benchmarkGroup != null) {
+           return benchmarkGroup.getBenchmarks()[childPosition];
+        }
+
+        return null;
+    }
+
+    @Override
+    public long getGroupId(int groupPosition) {
+        return groupPosition;
+    }
+
+    @Override
+    public long getChildId(int groupPosition, int childPosition) {
+        return childPosition;
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return false;
+    }
+
+    @Override
+    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
+        BenchmarkGroup group = (BenchmarkGroup) getGroup(groupPosition);
+        if (convertView == null) {
+            convertView = mInflater.inflate(R.layout.benchmark_list_group_row, null);
+        }
+
+        TextView title = (TextView) convertView.findViewById(R.id.group_name);
+        title.setTypeface(null, Typeface.BOLD);
+        title.setText(group.getTitle());
+        return convertView;
+    }
+
+    @Override
+    public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+                             View convertView, ViewGroup parent) {
+        BenchmarkGroup.Benchmark benchmark =
+                (BenchmarkGroup.Benchmark) getChild(groupPosition, childPosition);
+        if (convertView == null) {
+            convertView = mInflater.inflate(R.layout.benchmark_list_item, null);
+        }
+
+        TextView name = (TextView) convertView.findViewById(R.id.benchmark_name);
+        name.setText(benchmark.getName());
+        CheckBox enabledBox = (CheckBox) convertView.findViewById(R.id.benchmark_enable_checkbox);
+        enabledBox.setOnClickListener(benchmark);
+        enabledBox.setChecked(benchmark.isEnabled());
+
+        return convertView;
+    }
+
+    @Override
+    public boolean isChildSelectable(int groupPosition, int childPosition) {
+        return true;
+    }
+
+    public int getChildrenHeight() {
+        // TODO
+        return 1024;
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
new file mode 100644
index 0000000..79bafd6
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
@@ -0,0 +1,150 @@
+/*
+ * 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.benchmark.app;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ExpandableListView;
+import android.widget.Toast;
+
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.R;
+import com.android.benchmark.results.GlobalResultsStore;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class HomeActivity extends AppCompatActivity implements Button.OnClickListener {
+
+    private FloatingActionButton mStartButton;
+    private BenchmarkRegistry mRegistry;
+    private Queue<Intent> mRunnableBenchmarks;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_home);
+
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+
+        mStartButton = (FloatingActionButton) findViewById(R.id.start_button);
+        mStartButton.setActivated(true);
+        mStartButton.setOnClickListener(this);
+
+        mRegistry = new BenchmarkRegistry(this);
+
+        mRunnableBenchmarks = new LinkedList<>();
+
+        ExpandableListView listView = (ExpandableListView) findViewById(R.id.test_list);
+        BenchmarkListAdapter adapter =
+                new BenchmarkListAdapter(LayoutInflater.from(this), mRegistry);
+        listView.setAdapter(adapter);
+
+        adapter.notifyDataSetChanged();
+        ViewGroup.LayoutParams layoutParams = listView.getLayoutParams();
+        layoutParams.height = 2048;
+        listView.setLayoutParams(layoutParams);
+        listView.requestLayout();
+        System.out.println(System.getProperties().stringPropertyNames());
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.menu_main, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        int id = item.getItemId();
+
+        //noinspection SimplifiableIfStatement
+        if (id == R.id.action_settings) {
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... voids) {
+                    try {
+                        HomeActivity.this.runOnUiThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                Toast.makeText(HomeActivity.this, "Exporting...", Toast.LENGTH_LONG).show();
+                            }
+                        });
+                        GlobalResultsStore.getInstance(HomeActivity.this).exportToCsv();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Void aVoid) {
+                    HomeActivity.this.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            Toast.makeText(HomeActivity.this, "Done", Toast.LENGTH_LONG).show();
+                        }
+                    });
+                }
+            }.execute();
+
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void onClick(View v) {
+        final int groupCount = mRegistry.getGroupCount();
+        for (int i = 0; i < groupCount; i++) {
+
+            Intent intent = mRegistry.getBenchmarkGroup(i).getIntent();
+            if (intent != null) {
+                mRunnableBenchmarks.add(intent);
+            }
+        }
+
+        handleNextBenchmark();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+    }
+
+    private void handleNextBenchmark() {
+        Intent nextIntent = mRunnableBenchmarks.peek();
+        startActivityForResult(nextIntent, 0);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java
new file mode 100644
index 0000000..1c82d6d
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java
@@ -0,0 +1,224 @@
+/*
+ * 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.benchmark.app;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.*;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.benchmark.R;
+
+
+/**
+ * TODO: document your custom view class.
+ */
+public class PerfTimeline extends View {
+    private String mExampleString; // TODO: use a default from R.string...
+    private int mExampleColor = Color.RED; // TODO: use a default from R.color...
+    private float mExampleDimension = 300; // TODO: use a default from R.dimen...
+
+    private TextPaint mTextPaint;
+    private float mTextWidth;
+    private float mTextHeight;
+
+    private Paint mPaintBaseLow;
+    private Paint mPaintBaseHigh;
+    private Paint mPaintValue;
+
+
+    public float[] mLinesLow;
+    public float[] mLinesHigh;
+    public float[] mLinesValue;
+
+    public PerfTimeline(Context context) {
+        super(context);
+        init(null, 0);
+    }
+
+    public PerfTimeline(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(attrs, 0);
+    }
+
+    public PerfTimeline(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(attrs, defStyle);
+    }
+
+    private void init(AttributeSet attrs, int defStyle) {
+        // Load attributes
+        final TypedArray a = getContext().obtainStyledAttributes(
+                attrs, R.styleable.PerfTimeline, defStyle, 0);
+
+        mExampleString = "xx";//a.getString(R.styleable.PerfTimeline_exampleString, "xx");
+        mExampleColor = a.getColor(R.styleable.PerfTimeline_exampleColor, mExampleColor);
+        // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with
+        // values that should fall on pixel boundaries.
+        mExampleDimension = a.getDimension(
+                R.styleable.PerfTimeline_exampleDimension,
+                mExampleDimension);
+
+        a.recycle();
+
+        // Set up a default TextPaint object
+        mTextPaint = new TextPaint();
+        mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+        mTextPaint.setTextAlign(Paint.Align.LEFT);
+
+        // Update TextPaint and text measurements from attributes
+        invalidateTextPaintAndMeasurements();
+
+        mPaintBaseLow = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaintBaseLow.setStyle(Paint.Style.FILL);
+        mPaintBaseLow.setColor(0xff000000);
+
+        mPaintBaseHigh = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaintBaseHigh.setStyle(Paint.Style.FILL);
+        mPaintBaseHigh.setColor(0x7f7f7f7f);
+
+        mPaintValue = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaintValue.setStyle(Paint.Style.FILL);
+        mPaintValue.setColor(0x7fff0000);
+
+    }
+
+    private void invalidateTextPaintAndMeasurements() {
+        mTextPaint.setTextSize(mExampleDimension);
+        mTextPaint.setColor(mExampleColor);
+        mTextWidth = mTextPaint.measureText(mExampleString);
+
+        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
+        mTextHeight = fontMetrics.bottom;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // TODO: consider storing these as member variables to reduce
+        // allocations per draw cycle.
+        int paddingLeft = getPaddingLeft();
+        int paddingTop = getPaddingTop();
+        int paddingRight = getPaddingRight();
+        int paddingBottom = getPaddingBottom();
+
+        int contentWidth = getWidth() - paddingLeft - paddingRight;
+        int contentHeight = getHeight() - paddingTop - paddingBottom;
+
+        // Draw the text.
+        //canvas.drawText(mExampleString,
+        //        paddingLeft + (contentWidth - mTextWidth) / 2,
+        //        paddingTop + (contentHeight + mTextHeight) / 2,
+        //        mTextPaint);
+
+
+
+
+        // Draw the shadow
+        //RectF rf = new RectF(10.f, 10.f, 100.f, 100.f);
+        //canvas.drawOval(rf, mShadowPaint);
+
+        if (mLinesLow != null) {
+            canvas.drawLines(mLinesLow, mPaintBaseLow);
+        }
+        if (mLinesHigh != null) {
+            canvas.drawLines(mLinesHigh, mPaintBaseHigh);
+        }
+        if (mLinesValue != null) {
+            canvas.drawLines(mLinesValue, mPaintValue);
+        }
+
+
+/*
+        // Draw the pie slices
+        for (int i = 0; i < mData.size(); ++i) {
+            Item it = mData.get(i);
+            mPiePaint.setShader(it.mShader);
+            canvas.drawArc(mBounds,
+                    360 - it.mEndAngle,
+                    it.mEndAngle - it.mStartAngle,
+                    true, mPiePaint);
+        }
+*/
+        // Draw the pointer
+        //canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
+        //canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
+    }
+
+    /**
+     * Gets the example string attribute value.
+     *
+     * @return The example string attribute value.
+     */
+    public String getExampleString() {
+        return mExampleString;
+    }
+
+    /**
+     * Sets the view's example string attribute value. In the example view, this string
+     * is the text to draw.
+     *
+     * @param exampleString The example string attribute value to use.
+     */
+    public void setExampleString(String exampleString) {
+        mExampleString = exampleString;
+        invalidateTextPaintAndMeasurements();
+    }
+
+    /**
+     * Gets the example color attribute value.
+     *
+     * @return The example color attribute value.
+     */
+    public int getExampleColor() {
+        return mExampleColor;
+    }
+
+    /**
+     * Sets the view's example color attribute value. In the example view, this color
+     * is the font color.
+     *
+     * @param exampleColor The example color attribute value to use.
+     */
+    public void setExampleColor(int exampleColor) {
+        mExampleColor = exampleColor;
+        invalidateTextPaintAndMeasurements();
+    }
+
+    /**
+     * Gets the example dimension attribute value.
+     *
+     * @return The example dimension attribute value.
+     */
+    public float getExampleDimension() {
+        return mExampleDimension;
+    }
+
+    /**
+     * Sets the view's example dimension attribute value. In the example view, this dimension
+     * is the font size.
+     *
+     * @param exampleDimension The example dimension attribute value to use.
+     */
+    public void setExampleDimension(float exampleDimension) {
+        mExampleDimension = exampleDimension;
+        invalidateTextPaintAndMeasurements();
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
new file mode 100644
index 0000000..7641d00
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
@@ -0,0 +1,415 @@
+/*
+ * 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.benchmark.app;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkGroup;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.results.GlobalResultsStore;
+import com.android.benchmark.results.UiBenchmarkResult;
+import com.android.benchmark.synthetic.MemoryActivity;
+import com.android.benchmark.ui.BitmapUploadActivity;
+import com.android.benchmark.ui.EditTextInputActivity;
+import com.android.benchmark.ui.FullScreenOverdrawActivity;
+import com.android.benchmark.ui.ImageListViewScrollActivity;
+import com.android.benchmark.ui.ListViewScrollActivity;
+import com.android.benchmark.ui.ShadowGridActivity;
+import com.android.benchmark.ui.TextScrollActivity;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class RunLocalBenchmarksActivity extends AppCompatActivity {
+
+    public static final int RUN_COUNT = 5;
+
+    private ArrayList<LocalBenchmark> mBenchmarksToRun;
+    private int mBenchmarkCursor;
+    private int mCurrentRunId;
+    private boolean mFinish;
+
+    private Handler mHandler = new Handler();
+
+    private static final int[] ALL_TESTS = new int[] {
+            R.id.benchmark_list_view_scroll,
+            R.id.benchmark_image_list_view_scroll,
+            R.id.benchmark_shadow_grid,
+            R.id.benchmark_text_high_hitrate,
+            R.id.benchmark_text_low_hitrate,
+            R.id.benchmark_edit_text_input,
+            R.id.benchmark_overdraw,
+    };
+
+    public static class LocalBenchmarksList extends ListFragment {
+        private ArrayList<LocalBenchmark> mBenchmarks;
+        private int mRunId;
+
+        public void setBenchmarks(ArrayList<LocalBenchmark> benchmarks) {
+            mBenchmarks = benchmarks;
+        }
+
+        public void setRunId(int id) {
+            mRunId = id;
+        }
+
+        @Override
+        public void onListItemClick(ListView l, View v, int position, long id) {
+            if (getActivity().findViewById(R.id.list_fragment_container) != null) {
+                FragmentManager fm = getActivity().getSupportFragmentManager();
+                UiResultsFragment resultsView = new UiResultsFragment();
+                String testName = BenchmarkRegistry.getBenchmarkName(v.getContext(),
+                        mBenchmarks.get(position).id);
+                resultsView.setRunInfo(testName, mRunId);
+                FragmentTransaction fragmentTransaction = fm.beginTransaction();
+                fragmentTransaction.replace(R.id.list_fragment_container, resultsView);
+                fragmentTransaction.addToBackStack(null);
+                fragmentTransaction.commit();
+            }
+        }
+    }
+
+
+    private class LocalBenchmark {
+        int id;
+        int runCount = 0;
+        int totalCount = 0;
+        ArrayList<String> mResultsUri = new ArrayList<>();
+
+        LocalBenchmark(int id, int runCount) {
+            this.id = id;
+            this.runCount = 0;
+            this.totalCount = runCount;
+        }
+
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_running_list);
+
+        initLocalBenchmarks(getIntent());
+
+        if (findViewById(R.id.list_fragment_container) != null) {
+            FragmentManager fm = getSupportFragmentManager();
+            LocalBenchmarksList listView = new LocalBenchmarksList();
+            listView.setListAdapter(new LocalBenchmarksListAdapter(LayoutInflater.from(this)));
+            listView.setBenchmarks(mBenchmarksToRun);
+            listView.setRunId(mCurrentRunId);
+            fm.beginTransaction().add(R.id.list_fragment_container, listView).commit();
+        }
+
+        TextView scoreView = (TextView) findViewById(R.id.score_text_view);
+        scoreView.setText("Running tests!");
+    }
+
+    private int translateBenchmarkIndex(int index) {
+        if (index >= 0 && index < ALL_TESTS.length) {
+            return ALL_TESTS[index];
+        }
+
+        return -1;
+    }
+
+    private void initLocalBenchmarks(Intent intent) {
+        mBenchmarksToRun = new ArrayList<>();
+        int[] enabledIds = intent.getIntArrayExtra(BenchmarkGroup.BENCHMARK_EXTRA_ENABLED_TESTS);
+        int runCount = intent.getIntExtra(BenchmarkGroup.BENCHMARK_EXTRA_RUN_COUNT, RUN_COUNT);
+        mFinish = intent.getBooleanExtra(BenchmarkGroup.BENCHMARK_EXTRA_FINISH, false);
+
+        if (enabledIds == null) {
+            // run all tests
+            enabledIds = ALL_TESTS;
+        }
+
+        StringBuilder idString = new StringBuilder();
+        idString.append(runCount);
+        idString.append(System.currentTimeMillis());
+
+        for (int i = 0; i < enabledIds.length; i++) {
+            int id = enabledIds[i];
+            System.out.println("considering " + id);
+            if (!isValidBenchmark(id)) {
+                System.out.println("not valid " + id);
+                id = translateBenchmarkIndex(id);
+                System.out.println("got out " + id);
+                System.out.println("expected: " + R.id.benchmark_overdraw);
+            }
+
+            if (isValidBenchmark(id)) {
+                int localRunCount = runCount;
+                if (isCompute(id)) {
+                    localRunCount = 1;
+                }
+                mBenchmarksToRun.add(new LocalBenchmark(id, localRunCount));
+                idString.append(id);
+            }
+        }
+
+        mBenchmarkCursor = 0;
+        mCurrentRunId = idString.toString().hashCode();
+    }
+
+    private boolean isCompute(int id) {
+        switch (id) {
+            case R.id.benchmark_cpu_gflops:
+            case R.id.benchmark_cpu_heat_soak:
+            case R.id.benchmark_memory_bandwidth:
+            case R.id.benchmark_memory_latency:
+            case R.id.benchmark_power_management:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isValidBenchmark(int benchmarkId) {
+        switch (benchmarkId) {
+            case R.id.benchmark_list_view_scroll:
+            case R.id.benchmark_image_list_view_scroll:
+            case R.id.benchmark_shadow_grid:
+            case R.id.benchmark_text_high_hitrate:
+            case R.id.benchmark_text_low_hitrate:
+            case R.id.benchmark_edit_text_input:
+            case R.id.benchmark_overdraw:
+            case R.id.benchmark_memory_bandwidth:
+            case R.id.benchmark_memory_latency:
+            case R.id.benchmark_power_management:
+            case R.id.benchmark_cpu_heat_soak:
+            case R.id.benchmark_cpu_gflops:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                runNextBenchmark();
+            }
+        }, 1000);
+    }
+
+    private void computeOverallScore() {
+        final TextView scoreView = (TextView) findViewById(R.id.score_text_view);
+        scoreView.setText("Computing score...");
+        new AsyncTask<Void, Void, Integer>()  {
+            @Override
+            protected Integer doInBackground(Void... voids) {
+                GlobalResultsStore gsr =
+                        GlobalResultsStore.getInstance(RunLocalBenchmarksActivity.this);
+                ArrayList<Double> testLevelScores = new ArrayList<>();
+                final SummaryStatistics stats = new SummaryStatistics();
+                for (LocalBenchmark b : mBenchmarksToRun) {
+                    HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults =
+                            gsr.loadDetailedResults(mCurrentRunId);
+                    for (ArrayList<UiBenchmarkResult> testResult : detailedResults.values()) {
+                        for (UiBenchmarkResult res : testResult) {
+                            int score = res.getScore();
+                            if (score == 0) {
+                                score = 1;
+                            }
+                            stats.addValue(score);
+                        }
+
+                        testLevelScores.add(stats.getGeometricMean());
+                        stats.clear();
+                    }
+
+                }
+
+                for (double score : testLevelScores) {
+                    stats.addValue(score);
+                }
+
+                return (int)Math.round(stats.getGeometricMean());
+            }
+
+            @Override
+            protected void onPostExecute(Integer score) {
+                TextView view = (TextView)
+                        RunLocalBenchmarksActivity.this.findViewById(R.id.score_text_view);
+                view.setText("Score: " + score);
+            }
+        }.execute();
+    }
+
+    private void runNextBenchmark() {
+        LocalBenchmark benchmark = mBenchmarksToRun.get(mBenchmarkCursor);
+        boolean runAgain = false;
+
+        if (benchmark.runCount < benchmark.totalCount) {
+            runBenchmarkForId(mBenchmarksToRun.get(mBenchmarkCursor).id, benchmark.runCount++);
+        } else if (mBenchmarkCursor + 1 < mBenchmarksToRun.size()) {
+            mBenchmarkCursor++;
+            benchmark = mBenchmarksToRun.get(mBenchmarkCursor);
+            runBenchmarkForId(benchmark.id, benchmark.runCount++);
+        } else if (runAgain) {
+            mBenchmarkCursor = 0;
+            initLocalBenchmarks(getIntent());
+
+            runBenchmarkForId(mBenchmarksToRun.get(mBenchmarkCursor).id, benchmark.runCount);
+        } else if (mFinish) {
+            finish();
+        } else {
+            Log.i("BENCH", "BenchmarkDone!");
+            computeOverallScore();
+        }
+    }
+
+    private void runBenchmarkForId(int id, int iteration) {
+        Intent intent;
+        int syntheticTestId = -1;
+
+        System.out.println("iteration: " + iteration);
+
+        switch (id) {
+            case R.id.benchmark_list_view_scroll:
+                intent = new Intent(getApplicationContext(), ListViewScrollActivity.class);
+                break;
+            case R.id.benchmark_image_list_view_scroll:
+                intent = new Intent(getApplicationContext(), ImageListViewScrollActivity.class);
+                break;
+            case R.id.benchmark_shadow_grid:
+                intent = new Intent(getApplicationContext(), ShadowGridActivity.class);
+                break;
+            case R.id.benchmark_text_high_hitrate:
+                intent = new Intent(getApplicationContext(), TextScrollActivity.class);
+                intent.putExtra(TextScrollActivity.EXTRA_HIT_RATE, 80);
+                intent.putExtra(BenchmarkRegistry.EXTRA_ID, id);
+                break;
+            case R.id.benchmark_text_low_hitrate:
+                intent = new Intent(getApplicationContext(), TextScrollActivity.class);
+                intent.putExtra(TextScrollActivity.EXTRA_HIT_RATE, 20);
+                intent.putExtra(BenchmarkRegistry.EXTRA_ID, id);
+                break;
+            case R.id.benchmark_edit_text_input:
+                intent = new Intent(getApplicationContext(), EditTextInputActivity.class);
+                break;
+            case R.id.benchmark_overdraw:
+                intent = new Intent(getApplicationContext(), BitmapUploadActivity.class);
+                break;
+            case R.id.benchmark_memory_bandwidth:
+                syntheticTestId = 0;
+                intent = new Intent(getApplicationContext(), MemoryActivity.class);
+                intent.putExtra("test", syntheticTestId);
+                break;
+            case R.id.benchmark_memory_latency:
+                syntheticTestId = 1;
+                intent = new Intent(getApplicationContext(), MemoryActivity.class);
+                intent.putExtra("test", syntheticTestId);
+                break;
+            case R.id.benchmark_power_management:
+                syntheticTestId = 2;
+                intent = new Intent(getApplicationContext(), MemoryActivity.class);
+                intent.putExtra("test", syntheticTestId);
+                break;
+            case R.id.benchmark_cpu_heat_soak:
+                syntheticTestId = 3;
+                intent = new Intent(getApplicationContext(), MemoryActivity.class);
+                intent.putExtra("test", syntheticTestId);
+                break;
+            case R.id.benchmark_cpu_gflops:
+                syntheticTestId = 4;
+                intent = new Intent(getApplicationContext(), MemoryActivity.class);
+                intent.putExtra("test", syntheticTestId);
+                break;
+
+            default:
+               intent = null;
+        }
+
+        if (intent != null) {
+            intent.putExtra("com.android.benchmark.RUN_ID", mCurrentRunId);
+            intent.putExtra("com.android.benchmark.ITERATION", iteration);
+            startActivityForResult(intent, id & 0xffff, null);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case R.id.benchmark_shadow_grid:
+            case R.id.benchmark_list_view_scroll:
+            case R.id.benchmark_image_list_view_scroll:
+            case R.id.benchmark_text_high_hitrate:
+            case R.id.benchmark_text_low_hitrate:
+            case R.id.benchmark_edit_text_input:
+                break;
+            default:
+        }
+    }
+
+    class LocalBenchmarksListAdapter extends BaseAdapter {
+
+        private final LayoutInflater mInflater;
+
+        LocalBenchmarksListAdapter(LayoutInflater inflater) {
+            mInflater = inflater;
+        }
+
+        @Override
+        public int getCount() {
+            return mBenchmarksToRun.size();
+        }
+
+        @Override
+        public Object getItem(int i) {
+            return mBenchmarksToRun.get(i);
+        }
+
+        @Override
+        public long getItemId(int i) {
+            return mBenchmarksToRun.get(i).id;
+        }
+
+        @Override
+        public View getView(int i, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.running_benchmark_list_item, null);
+            }
+
+            TextView name = (TextView) convertView.findViewById(R.id.benchmark_name);
+            name.setText(BenchmarkRegistry.getBenchmarkName(
+                    RunLocalBenchmarksActivity.this, mBenchmarksToRun.get(i).id));
+            return convertView;
+        }
+
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java
new file mode 100644
index 0000000..56e94d5
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java
@@ -0,0 +1,167 @@
+/*
+ * 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.benchmark.app;
+
+import android.annotation.TargetApi;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ListFragment;
+import android.util.Log;
+import android.view.FrameMetrics;
+import android.widget.SimpleAdapter;
+
+import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkGroup;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.results.GlobalResultsStore;
+import com.android.benchmark.results.UiBenchmarkResult;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URI;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+@TargetApi(24)
+public class UiResultsFragment extends ListFragment {
+    private static final String TAG = "UiResultsFragment";
+    private static final int NUM_FIELDS = 20;
+
+    private ArrayList<UiBenchmarkResult> mResults = new ArrayList<>();
+
+    private AsyncTask<Void, Void, ArrayList<Map<String, String>>> mLoadScoresTask =
+            new AsyncTask<Void, Void, ArrayList<Map<String, String>>>() {
+        @Override
+        protected ArrayList<Map<String, String>> doInBackground(Void... voids) {
+            String[] data;
+            if (mResults.size() == 0 || mResults.get(0) == null) {
+                data = new String[] {
+                        "No metrics reported", ""
+                };
+            } else {
+                data = new String[NUM_FIELDS * (1 + mResults.size()) + 2];
+                SummaryStatistics stats = new SummaryStatistics();
+                int totalFrameCount = 0;
+                double totalAvgFrameDuration = 0;
+                double total99FrameDuration = 0;
+                double total95FrameDuration = 0;
+                double total90FrameDuration = 0;
+                double totalLongestFrame = 0;
+                double totalShortestFrame = 0;
+
+                for (int i = 0; i < mResults.size(); i++) {
+                    int start = (i * NUM_FIELDS) + + NUM_FIELDS;
+                    data[(start++)] = "Iteration";
+                    data[(start++)] = "" + i;
+                    data[(start++)] = "Total Frames";
+                    int currentFrameCount = mResults.get(i).getTotalFrameCount();
+                    totalFrameCount += currentFrameCount;
+                    data[(start++)] = Integer.toString(currentFrameCount);
+                    data[(start++)] = "Average frame duration:";
+                    double currentAvgFrameDuration = mResults.get(i).getAverage(FrameMetrics.TOTAL_DURATION);
+                    totalAvgFrameDuration += currentAvgFrameDuration;
+                    data[(start++)] = String.format("%.2f", currentAvgFrameDuration);
+                    data[(start++)] = "Frame duration 99th:";
+                    double current99FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 99);
+                    total99FrameDuration += current99FrameDuration;
+                    data[(start++)] = String.format("%.2f", current99FrameDuration);
+                    data[(start++)] = "Frame duration 95th:";
+                    double current95FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 95);
+                    total95FrameDuration += current95FrameDuration;
+                    data[(start++)] = String.format("%.2f", current95FrameDuration);
+                    data[(start++)] = "Frame duration 90th:";
+                    double current90FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 90);
+                    total90FrameDuration += current90FrameDuration;
+                    data[(start++)] = String.format("%.2f", current90FrameDuration);
+                    data[(start++)] = "Longest frame:";
+                    double longestFrame = mResults.get(i).getMaximum(FrameMetrics.TOTAL_DURATION);
+                    if (totalLongestFrame == 0 || longestFrame > totalLongestFrame) {
+                        totalLongestFrame = longestFrame;
+                    }
+                    data[(start++)] = String.format("%.2f", longestFrame);
+                    data[(start++)] = "Shortest frame:";
+                    double shortestFrame = mResults.get(i).getMinimum(FrameMetrics.TOTAL_DURATION);
+                    if (totalShortestFrame == 0 || totalShortestFrame > shortestFrame) {
+                        totalShortestFrame = shortestFrame;
+                    }
+                    data[(start++)] = String.format("%.2f", shortestFrame);
+                    data[(start++)] = "Score:";
+                    double score = mResults.get(i).getScore();
+                    stats.addValue(score);
+                    data[(start++)] = String.format("%.2f", score);
+                    data[(start++)] = "==============";
+                    data[(start++)] = "============================";
+                };
+
+                int start = 0;
+                data[0] = "Overall: ";
+                data[1] = "";
+                data[(start++)] = "Total Frames";
+                data[(start++)] = Integer.toString(totalFrameCount);
+                data[(start++)] = "Average frame duration:";
+                data[(start++)] = String.format("%.2f", totalAvgFrameDuration / mResults.size());
+                data[(start++)] = "Frame duration 99th:";
+                data[(start++)] = String.format("%.2f", total99FrameDuration / mResults.size());
+                data[(start++)] = "Frame duration 95th:";
+                data[(start++)] = String.format("%.2f", total95FrameDuration / mResults.size());
+                data[(start++)] = "Frame duration 90th:";
+                data[(start++)] = String.format("%.2f", total90FrameDuration / mResults.size());
+                data[(start++)] = "Longest frame:";
+                data[(start++)] = String.format("%.2f", totalLongestFrame);
+                data[(start++)] = "Shortest frame:";
+                data[(start++)] = String.format("%.2f", totalShortestFrame);
+                data[(start++)] = "Score:";
+                data[(start++)] = String.format("%.2f", stats.getGeometricMean());
+                data[(start++)] = "==============";
+                data[(start++)] = "============================";
+            }
+
+            ArrayList<Map<String, String>> dataMap = new ArrayList<>();
+            for (int i = 0; i < data.length - 1; i += 2) {
+                HashMap<String, String> map = new HashMap<>();
+                map.put("name", data[i]);
+                map.put("value", data[i + 1]);
+                dataMap.add(map);
+            }
+
+            return dataMap;
+        }
+
+        @Override
+        protected void onPostExecute(ArrayList<Map<String, String>> dataMap) {
+            setListAdapter(new SimpleAdapter(getActivity(), dataMap, R.layout.results_list_item,
+                    new String[] {"name", "value"}, new int[] { R.id.result_name, R.id.result_value }));
+            setListShown(true);
+        }
+    };
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        setListShown(false);
+        mLoadScoresTask.execute();
+    }
+
+    public void setRunInfo(String name, int runId) {
+        mResults = GlobalResultsStore.getInstance(getActivity()).loadTestResults(name, runId);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java
new file mode 100644
index 0000000..d91e579
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java
@@ -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.
+ *
+ */
+
+package com.android.benchmark.registry;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents the category of a particular benchmark.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@IntDef({BenchmarkCategory.GENERIC, BenchmarkCategory.UI, BenchmarkCategory.COMPUTE})
+@interface BenchmarkCategory {
+    int GENERIC = 0;
+    int UI = 1;
+    int COMPUTE = 2;
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java
new file mode 100644
index 0000000..4cb7716
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java
@@ -0,0 +1,151 @@
+/*
+ * 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.benchmark.registry;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.view.View;
+import android.widget.CheckBox;
+
+/**
+ * Logical grouping of benchmarks
+ */
+public class BenchmarkGroup {
+    public static final String BENCHMARK_EXTRA_ENABLED_TESTS =
+            "com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS";
+
+    public static final String BENCHMARK_EXTRA_RUN_COUNT =
+            "com.android.benchmark.EXTRA_RUN_COUNT";
+    public static final String BENCHMARK_EXTRA_FINISH = "com.android.benchmark.FINISH_WHEN_DONE";
+
+    public static class Benchmark implements CheckBox.OnClickListener {
+        /** The name of this individual benchmark test */
+        private final String mName;
+
+        /** The category of this individual benchmark test */
+        private final @BenchmarkCategory int mCategory;
+
+        /** Human-readable description of the benchmark */
+        private final String mDescription;
+
+        private final int mId;
+
+        private boolean mEnabled;
+
+        Benchmark(int id, String name, @BenchmarkCategory int category, String description) {
+            mId = id;
+            mName = name;
+            mCategory = category;
+            mDescription = description;
+            mEnabled = true;
+        }
+
+        public boolean isEnabled() { return mEnabled; }
+
+        public void setEnabled(boolean enabled) { mEnabled = enabled; }
+
+        public int getId() { return mId; }
+
+        public String getDescription() { return mDescription; }
+
+        public @BenchmarkCategory int getCategory() { return mCategory; }
+
+        public String getName() { return mName; }
+
+        @Override
+        public void onClick(View view) {
+            setEnabled(((CheckBox)view).isChecked());
+        }
+    }
+
+    /**
+     * Component for this benchmark group.
+     */
+    private final ComponentName mComponentName;
+
+    /**
+     * Benchmark title, showed in the {@link android.widget.ListView}
+     */
+    private final String mTitle;
+
+    /**
+     * List of all benchmarks exported by this group
+     */
+    private final Benchmark[] mBenchmarks;
+
+    /**
+     * The intent to launch the benchmark
+     */
+    private final Intent mIntent;
+
+    /** Human-readable description of the benchmark group */
+    private final String mDescription;
+
+    BenchmarkGroup(ComponentName componentName, String title,
+                   String description, Benchmark[] benchmarks, Intent intent) {
+        mComponentName = componentName;
+        mTitle = title;
+        mBenchmarks = benchmarks;
+        mDescription = description;
+        mIntent = intent;
+    }
+
+    public Intent getIntent() {
+        int[] enabledBenchmarksIds = getEnabledBenchmarksIds();
+        if (enabledBenchmarksIds.length != 0) {
+            mIntent.putExtra(BENCHMARK_EXTRA_ENABLED_TESTS, enabledBenchmarksIds);
+            return mIntent;
+        }
+
+        return null;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public Benchmark[] getBenchmarks() {
+        return mBenchmarks;
+    }
+
+    public String getDescription() {
+        return mDescription;
+    }
+
+    private int[] getEnabledBenchmarksIds() {
+        int enabledBenchmarkCount = 0;
+        for (int i = 0; i < mBenchmarks.length; i++) {
+            if (mBenchmarks[i].isEnabled()) {
+                enabledBenchmarkCount++;
+            }
+        }
+
+        int writeIndex = 0;
+        int[] enabledBenchmarks = new int[enabledBenchmarkCount];
+        for (int i = 0; i < mBenchmarks.length; i++) {
+            if (mBenchmarks[i].isEnabled()) {
+                enabledBenchmarks[writeIndex++] = mBenchmarks[i].getId();
+            }
+        }
+
+        return enabledBenchmarks;
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
new file mode 100644
index 0000000..89c6aed
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
@@ -0,0 +1,236 @@
+/*
+ * 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.benchmark.registry;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.benchmark.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ */
+public class BenchmarkRegistry {
+
+    /** Metadata key for benchmark XML data */
+    private static final String BENCHMARK_GROUP_META_KEY =
+            "com.android.benchmark.benchmark_group";
+
+    /** Intent action specifying an activity that runs a single benchmark test. */
+    private static final String ACTION_BENCHMARK = "com.android.benchmark.ACTION_BENCHMARK";
+    public static final String EXTRA_ID = "com.android.benchmark.EXTRA_ID";
+
+    private static final String TAG_BENCHMARK_GROUP = "com.android.benchmark.BenchmarkGroup";
+    private static final String TAG_BENCHMARK = "com.android.benchmark.Benchmark";
+
+    private List<BenchmarkGroup> mGroups;
+
+    private final Context mContext;
+
+    public BenchmarkRegistry(Context context) {
+        mContext = context;
+        mGroups = new ArrayList<>();
+        loadBenchmarks();
+    }
+
+    private Intent getIntentFromInfo(ActivityInfo inf) {
+        Intent intent = new Intent();
+        intent.setClassName(inf.packageName, inf.name);
+        return intent;
+    }
+
+    public void loadBenchmarks() {
+        Intent intent = new Intent(ACTION_BENCHMARK);
+        intent.setPackage(mContext.getPackageName());
+
+        PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent,
+                PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+        for (ResolveInfo inf : resolveInfos) {
+            List<BenchmarkGroup> groups = parseBenchmarkGroup(inf.activityInfo);
+            if (groups != null) {
+                mGroups.addAll(groups);
+            }
+        }
+    }
+
+    private boolean seekToTag(XmlPullParser parser, String tag)
+            throws XmlPullParserException, IOException {
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
+            eventType = parser.next();
+        }
+        return eventType != XmlPullParser.END_DOCUMENT && tag.equals(parser.getName());
+    }
+
+    @BenchmarkCategory int getCategory(int category) {
+        switch (category) {
+            case BenchmarkCategory.COMPUTE:
+                return BenchmarkCategory.COMPUTE;
+            case BenchmarkCategory.UI:
+                return BenchmarkCategory.UI;
+            default:
+                return BenchmarkCategory.GENERIC;
+        }
+    }
+
+    private List<BenchmarkGroup> parseBenchmarkGroup(ActivityInfo activityInfo) {
+        PackageManager pm = mContext.getPackageManager();
+
+        ComponentName componentName = new ComponentName(
+                activityInfo.packageName, activityInfo.name);
+
+        SparseArray<List<BenchmarkGroup.Benchmark>> benchmarks = new SparseArray<>();
+        String groupName, groupDescription;
+        try (XmlResourceParser parser = activityInfo.loadXmlMetaData(pm, BENCHMARK_GROUP_META_KEY)) {
+
+            if (!seekToTag(parser, TAG_BENCHMARK_GROUP)) {
+                return null;
+            }
+
+            Resources res = pm.getResourcesForActivity(componentName);
+            AttributeSet attributeSet = Xml.asAttributeSet(parser);
+            TypedArray groupAttribs = res.obtainAttributes(attributeSet, R.styleable.BenchmarkGroup);
+
+            groupName = groupAttribs.getString(R.styleable.BenchmarkGroup_name);
+            groupDescription = groupAttribs.getString(R.styleable.BenchmarkGroup_description);
+            groupAttribs.recycle();
+            parser.next();
+
+            while (seekToTag(parser, TAG_BENCHMARK)) {
+                TypedArray benchAttribs =
+                        res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Benchmark);
+                int id = benchAttribs.getResourceId(R.styleable.Benchmark_id, -1);
+                String testName = benchAttribs.getString(R.styleable.Benchmark_name);
+                String testDescription = benchAttribs.getString(R.styleable.Benchmark_description);
+                int testCategory = benchAttribs.getInt(R.styleable.Benchmark_category,
+                        BenchmarkCategory.GENERIC);
+                int category = getCategory(testCategory);
+                BenchmarkGroup.Benchmark benchmark = new BenchmarkGroup.Benchmark(
+                        id, testName, category, testDescription);
+                List<BenchmarkGroup.Benchmark> benches = benchmarks.get(category);
+                if (benches == null) {
+                    benches = new ArrayList<>();
+                    benchmarks.append(category, benches);
+                }
+
+                benches.add(benchmark);
+
+                benchAttribs.recycle();
+                parser.next();
+            }
+        } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException e) {
+            return null;
+        }
+
+        List<BenchmarkGroup> result = new ArrayList<>();
+        Intent testIntent = getIntentFromInfo(activityInfo);
+        for (int i = 0; i < benchmarks.size(); i++) {
+            int cat = benchmarks.keyAt(i);
+            List<BenchmarkGroup.Benchmark> thisGroup = benchmarks.get(cat);
+            BenchmarkGroup.Benchmark[] benchmarkArray =
+                    new BenchmarkGroup.Benchmark[thisGroup.size()];
+            thisGroup.toArray(benchmarkArray);
+            result.add(new BenchmarkGroup(componentName,
+                    groupName + " - " + getCategoryString(cat), groupDescription, benchmarkArray,
+                    testIntent));
+        }
+
+        return result;
+    }
+
+    public int getGroupCount() {
+        return mGroups.size();
+    }
+
+    public int getBenchmarkCount(int benchmarkIndex) {
+        BenchmarkGroup group = getBenchmarkGroup(benchmarkIndex);
+        if (group != null) {
+            return group.getBenchmarks().length;
+        }
+        return 0;
+    }
+
+    public BenchmarkGroup getBenchmarkGroup(int benchmarkIndex) {
+        if (benchmarkIndex >= mGroups.size()) {
+            return null;
+        }
+
+        return mGroups.get(benchmarkIndex);
+    }
+
+    public static String getCategoryString(int category) {
+        switch (category) {
+            case BenchmarkCategory.UI:
+                return "UI";
+            case BenchmarkCategory.COMPUTE:
+                return "Compute";
+            case BenchmarkCategory.GENERIC:
+                return "Generic";
+            default:
+                return "";
+        }
+    }
+
+    public static String getBenchmarkName(Context context, int benchmarkId) {
+        switch (benchmarkId) {
+            case R.id.benchmark_list_view_scroll:
+                return context.getString(R.string.list_view_scroll_name);
+            case R.id.benchmark_image_list_view_scroll:
+                return context.getString(R.string.image_list_view_scroll_name);
+            case R.id.benchmark_shadow_grid:
+                return context.getString(R.string.shadow_grid_name);
+            case R.id.benchmark_text_high_hitrate:
+                return context.getString(R.string.text_high_hitrate_name);
+            case R.id.benchmark_text_low_hitrate:
+                return context.getString(R.string.text_low_hitrate_name);
+            case R.id.benchmark_edit_text_input:
+                return context.getString(R.string.edit_text_input_name);
+            case R.id.benchmark_memory_bandwidth:
+                return context.getString(R.string.memory_bandwidth_name);
+            case R.id.benchmark_memory_latency:
+                return context.getString(R.string.memory_latency_name);
+            case R.id.benchmark_power_management:
+                return context.getString(R.string.power_management_name);
+            case R.id.benchmark_cpu_heat_soak:
+                return context.getString(R.string.cpu_heat_soak_name);
+            case R.id.benchmark_cpu_gflops:
+                return context.getString(R.string.cpu_gflops_name);
+            case R.id.benchmark_overdraw:
+                return context.getString(R.string.overdraw_name);
+            default:
+                return "Some Benchmark";
+        }
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java b/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java
new file mode 100644
index 0000000..5d0cba2
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java
@@ -0,0 +1,389 @@
+/*
+ * 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.benchmark.results;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.view.FrameMetrics;
+import android.widget.Toast;
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+
+public class GlobalResultsStore extends SQLiteOpenHelper {
+    private static final int VERSION = 2;
+
+    private static GlobalResultsStore sInstance;
+    private static final String UI_RESULTS_TABLE = "ui_results";
+
+    private final Context mContext;
+
+    private GlobalResultsStore(Context context) {
+        super(context, "BenchmarkResults", null, VERSION);
+        mContext = context;
+    }
+
+    public static GlobalResultsStore getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new GlobalResultsStore(context.getApplicationContext());
+        }
+
+        return sInstance;
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase sqLiteDatabase) {
+        sqLiteDatabase.execSQL("CREATE TABLE " + UI_RESULTS_TABLE + " (" +
+                " _id INTEGER PRIMARY KEY AUTOINCREMENT," +
+                " name TEXT," +
+                " run_id INTEGER," +
+                " iteration INTEGER," +
+                " timestamp TEXT,"  +
+                " unknown_delay REAL," +
+                " input REAL," +
+                " animation REAL," +
+                " layout REAL," +
+                " draw REAL," +
+                " sync REAL," +
+                " command_issue REAL," +
+                " swap_buffers REAL," +
+                " total_duration REAL," +
+                " jank_frame BOOLEAN, " +
+                " device_charging INTEGER);");
+    }
+
+    public void storeRunResults(String testName, int runId, int iteration,
+                                UiBenchmarkResult result) {
+        SQLiteDatabase db = getWritableDatabase();
+        db.beginTransaction();
+
+        try {
+            String date = DateFormat.getDateTimeInstance().format(new Date());
+            int jankIndexIndex = 0;
+            int[] sortedJankIndices = result.getSortedJankFrameIndices();
+            int totalFrameCount = result.getTotalFrameCount();
+            for (int frameIdx = 0; frameIdx < totalFrameCount; frameIdx++) {
+                ContentValues cv = new ContentValues();
+                cv.put("name", testName);
+                cv.put("run_id", runId);
+                cv.put("iteration", iteration);
+                cv.put("timestamp", date);
+                cv.put("unknown_delay",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.UNKNOWN_DELAY_DURATION));
+                cv.put("input",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.INPUT_HANDLING_DURATION));
+                cv.put("animation",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.ANIMATION_DURATION));
+                cv.put("layout",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.LAYOUT_MEASURE_DURATION));
+                cv.put("draw",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.DRAW_DURATION));
+                cv.put("sync",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.SYNC_DURATION));
+                cv.put("command_issue",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.COMMAND_ISSUE_DURATION));
+                cv.put("swap_buffers",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.SWAP_BUFFERS_DURATION));
+                cv.put("total_duration",
+                        result.getMetricAtIndex(frameIdx, FrameMetrics.TOTAL_DURATION));
+                if (jankIndexIndex < sortedJankIndices.length &&
+                        sortedJankIndices[jankIndexIndex] == frameIdx) {
+                    jankIndexIndex++;
+                    cv.put("jank_frame", true);
+                } else {
+                    cv.put("jank_frame", false);
+                }
+                db.insert(UI_RESULTS_TABLE, null, cv);
+            }
+            db.setTransactionSuccessful();
+            Toast.makeText(mContext, "Score: " + result.getScore()
+                    + " Jank: " + (100 * sortedJankIndices.length) / (float) totalFrameCount + "%",
+                    Toast.LENGTH_LONG).show();
+        } finally {
+            db.endTransaction();
+        }
+
+    }
+
+    public ArrayList<UiBenchmarkResult> loadTestResults(String testName, int runId) {
+        SQLiteDatabase db = getReadableDatabase();
+        ArrayList<UiBenchmarkResult> resultList = new ArrayList<>();
+        try {
+            String[] columnsToQuery = new String[] {
+                    "name",
+                    "run_id",
+                    "iteration",
+                    "unknown_delay",
+                    "input",
+                    "animation",
+                    "layout",
+                    "draw",
+                    "sync",
+                    "command_issue",
+                    "swap_buffers",
+                    "total_duration",
+            };
+
+            Cursor cursor = db.query(
+                    UI_RESULTS_TABLE, columnsToQuery, "run_id=? AND name=?",
+                    new String[] { Integer.toString(runId), testName }, null, null, "iteration");
+
+            double[] values = new double[columnsToQuery.length - 3];
+
+            while (cursor.moveToNext()) {
+                int iteration = cursor.getInt(cursor.getColumnIndexOrThrow("iteration"));
+
+                values[0] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("unknown_delay"));
+                values[1] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("input"));
+                values[2] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("animation"));
+                values[3] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("layout"));
+                values[4] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("draw"));
+                values[5] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("sync"));
+                values[6] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("command_issue"));
+                values[7] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("swap_buffers"));
+                values[8] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("total_duration"));
+
+                UiBenchmarkResult iterationResult;
+                if (resultList.size() == iteration) {
+                    iterationResult = new UiBenchmarkResult(values);
+                    resultList.add(iteration, iterationResult);
+                } else {
+                    iterationResult = resultList.get(iteration);
+                    iterationResult.update(values);
+                }
+            }
+
+            cursor.close();
+        } finally {
+            db.close();
+        }
+
+        int total = resultList.get(0).getTotalFrameCount();
+        for (int i = 0; i < total; i++) {
+            System.out.println(""+ resultList.get(0).getMetricAtIndex(0, FrameMetrics.TOTAL_DURATION));
+        }
+
+        return resultList;
+    }
+
+    public HashMap<String, ArrayList<UiBenchmarkResult>> loadDetailedResults(int runId) {
+        SQLiteDatabase db = getReadableDatabase();
+        HashMap<String, ArrayList<UiBenchmarkResult>> results = new HashMap<>();
+        try {
+            String[] columnsToQuery = new String[] {
+                    "name",
+                    "run_id",
+                    "iteration",
+                    "unknown_delay",
+                    "input",
+                    "animation",
+                    "layout",
+                    "draw",
+                    "sync",
+                    "command_issue",
+                    "swap_buffers",
+                    "total_duration",
+            };
+
+            Cursor cursor = db.query(
+                    UI_RESULTS_TABLE, columnsToQuery, "run_id=?",
+                    new String[] { Integer.toString(runId) }, null, null, "name, iteration");
+
+            double[] values = new double[columnsToQuery.length - 3];
+            while (cursor.moveToNext()) {
+                int iteration = cursor.getInt(cursor.getColumnIndexOrThrow("iteration"));
+                String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
+                ArrayList<UiBenchmarkResult> resultList = results.get(name);
+                if (resultList == null) {
+                    resultList = new ArrayList<>();
+                    results.put(name, resultList);
+                }
+
+                values[0] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("unknown_delay"));
+                values[1] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("input"));
+                values[2] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("animation"));
+                values[3] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("layout"));
+                values[4] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("draw"));
+                values[5] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("sync"));
+                values[6] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("command_issue"));
+                values[7] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("swap_buffers"));
+                values[8] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("total_duration"));
+                values[8] = cursor.getDouble(
+                        cursor.getColumnIndexOrThrow("total_duration"));
+
+                UiBenchmarkResult iterationResult;
+                if (resultList.size() == iteration) {
+                    iterationResult = new UiBenchmarkResult(values);
+                    resultList.add(iterationResult);
+                } else {
+                    iterationResult = resultList.get(iteration);
+                    iterationResult.update(values);
+                }
+            }
+
+            cursor.close();
+        } finally {
+            db.close();
+        }
+
+        return results;
+    }
+
+    public void exportToCsv() throws IOException {
+        String path = mContext.getFilesDir() + "/results-" + System.currentTimeMillis() + ".csv";
+        SQLiteDatabase db = getReadableDatabase();
+
+        // stats across metrics for each run and each test
+        HashMap<String, DescriptiveStatistics> stats = new HashMap<>();
+
+        Cursor runIdCursor = db.query(
+                UI_RESULTS_TABLE, new String[] { "run_id" }, null, null, "run_id", null, null);
+
+        while (runIdCursor.moveToNext()) {
+
+            int runId = runIdCursor.getInt(runIdCursor.getColumnIndexOrThrow("run_id"));
+            HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults =
+                    loadDetailedResults(runId);
+
+            writeRawResults(runId, detailedResults);
+
+            DescriptiveStatistics overall = new DescriptiveStatistics();
+            try (FileWriter writer = new FileWriter(path, true)) {
+                writer.write("Run ID, " + runId + "\n");
+                writer.write("Test, Iteration, Score, Jank Penalty, Consistency Bonus, 95th, " +
+                        "90th\n");
+                for (String testName : detailedResults.keySet()) {
+                    ArrayList<UiBenchmarkResult> results = detailedResults.get(testName);
+                    DescriptiveStatistics scoreStats = new DescriptiveStatistics();
+                    DescriptiveStatistics jankPenalty = new DescriptiveStatistics();
+                    DescriptiveStatistics consistencyBonus = new DescriptiveStatistics();
+                    for (int i = 0; i < results.size(); i++) {
+                        UiBenchmarkResult result = results.get(i);
+                        int score = result.getScore();
+                        scoreStats.addValue(score);
+                        overall.addValue(score);
+                        jankPenalty.addValue(result.getJankPenalty());
+                        consistencyBonus.addValue(result.getConsistencyBonus());
+
+                        writer.write(testName);
+                        writer.write(",");
+                        writer.write("" + i);
+                        writer.write(",");
+                        writer.write("" + score);
+                        writer.write(",");
+                        writer.write("" + result.getJankPenalty());
+                        writer.write(",");
+                        writer.write("" + result.getConsistencyBonus());
+                        writer.write(",");
+                        writer.write(Double.toString(
+                                result.getPercentile(FrameMetrics.TOTAL_DURATION, 95)));
+                        writer.write(",");
+                        writer.write(Double.toString(
+                                result.getPercentile(FrameMetrics.TOTAL_DURATION, 90)));
+                        writer.write("\n");
+                    }
+
+                    writer.write("Score CV," +
+                            (100 * scoreStats.getStandardDeviation()
+                                    / scoreStats.getMean()) + "%\n");
+                    writer.write("Jank Penalty CV, " +
+                            (100 * jankPenalty.getStandardDeviation()
+                                    / jankPenalty.getMean()) + "%\n");
+                    writer.write("Consistency Bonus CV, " +
+                            (100 * consistencyBonus.getStandardDeviation()
+                                    / consistencyBonus.getMean()) + "%\n");
+                    writer.write("\n");
+                }
+
+                writer.write("Overall Score CV,"  +
+                        (100 * overall.getStandardDeviation() / overall.getMean()) + "%\n");
+                writer.flush();
+            }
+        }
+
+        runIdCursor.close();
+    }
+
+    private void writeRawResults(int runId,
+                                 HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults) {
+        StringBuilder path = new StringBuilder();
+        path.append(mContext.getFilesDir());
+        path.append("/");
+        path.append(Integer.toString(runId));
+        path.append(".csv");
+        try (FileWriter writer = new FileWriter(path.toString())) {
+            for (String test : detailedResults.keySet()) {
+                writer.write("Test, " + test + "\n");
+                writer.write("iteration, unknown delay, input, animation, layout, draw, sync, " +
+                        "command issue, swap buffers\n");
+                ArrayList<UiBenchmarkResult> runs = detailedResults.get(test);
+                for (int i = 0; i < runs.size(); i++) {
+                    UiBenchmarkResult run = runs.get(i);
+                    for (int j = 0; j < run.getTotalFrameCount(); j++) {
+                        writer.write(Integer.toString(i) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.UNKNOWN_DELAY_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.INPUT_HANDLING_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.ANIMATION_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.LAYOUT_MEASURE_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.DRAW_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.SYNC_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.COMMAND_ISSUE_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.SWAP_BUFFERS_DURATION) + "," +
+                                run.getMetricAtIndex(j, FrameMetrics.TOTAL_DURATION) + "\n");
+                    }
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int currentVersion) {
+        if (oldVersion < VERSION) {
+            sqLiteDatabase.execSQL("ALTER TABLE "
+                    + UI_RESULTS_TABLE + " ADD COLUMN timestamp TEXT;");
+        }
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java b/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java
new file mode 100644
index 0000000..da6e05a
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java
@@ -0,0 +1,245 @@
+/*
+ * 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.benchmark.results;
+
+import android.annotation.TargetApi;
+import android.view.FrameMetrics;
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility for storing and analyzing UI benchmark results.
+ */
+@TargetApi(24)
+public class UiBenchmarkResult {
+    private static final int BASE_SCORE = 100;
+    private static final int ZERO_SCORE_TOTAL_DURATION_MS = 32;
+    private static final int JANK_PENALTY_THRESHOLD_MS = 12;
+    private static final int ZERO_SCORE_ABOVE_THRESHOLD_MS =
+            ZERO_SCORE_TOTAL_DURATION_MS - JANK_PENALTY_THRESHOLD_MS;
+    private static final double JANK_PENALTY_PER_MS_ABOVE_THRESHOLD =
+            BASE_SCORE / (double)ZERO_SCORE_ABOVE_THRESHOLD_MS;
+    private static final int CONSISTENCY_BONUS_MAX = 100;
+
+    private static final int METRIC_WAS_JANKY = -1;
+
+    private static final int[] METRICS = new int[] {
+            FrameMetrics.UNKNOWN_DELAY_DURATION,
+            FrameMetrics.INPUT_HANDLING_DURATION,
+            FrameMetrics.ANIMATION_DURATION,
+            FrameMetrics.LAYOUT_MEASURE_DURATION,
+            FrameMetrics.DRAW_DURATION,
+            FrameMetrics.SYNC_DURATION,
+            FrameMetrics.COMMAND_ISSUE_DURATION,
+            FrameMetrics.SWAP_BUFFERS_DURATION,
+            FrameMetrics.TOTAL_DURATION,
+    };
+    public static final int FRAME_PERIOD_MS = 16;
+
+    private final DescriptiveStatistics[] mStoredStatistics;
+
+    public UiBenchmarkResult(List<FrameMetrics> instances) {
+        mStoredStatistics = new DescriptiveStatistics[METRICS.length];
+        insertMetrics(instances);
+    }
+
+    public UiBenchmarkResult(double[] values) {
+        mStoredStatistics = new DescriptiveStatistics[METRICS.length];
+        insertValues(values);
+    }
+
+    public void update(List<FrameMetrics> instances) {
+        insertMetrics(instances);
+    }
+
+    public void update(double[] values) {
+        insertValues(values);
+    }
+
+    public double getAverage(int id) {
+        int pos = getMetricPosition(id);
+        return mStoredStatistics[pos].getMean();
+    }
+
+    public double getMinimum(int id) {
+        int pos = getMetricPosition(id);
+        return mStoredStatistics[pos].getMin();
+    }
+
+    public double getMaximum(int id) {
+        int pos = getMetricPosition(id);
+        return mStoredStatistics[pos].getMax();
+    }
+
+    public int getMaximumIndex(int id) {
+        int pos = getMetricPosition(id);
+        double[] storedMetrics = mStoredStatistics[pos].getValues();
+        int maxIdx = 0;
+        for (int i = 0; i < storedMetrics.length; i++) {
+            if (storedMetrics[i] >= storedMetrics[maxIdx]) {
+                maxIdx = i;
+            }
+        }
+
+        return maxIdx;
+    }
+
+    public double getMetricAtIndex(int index, int metricId) {
+        return mStoredStatistics[getMetricPosition(metricId)].getElement(index);
+    }
+
+    public double getPercentile(int id, int percentile) {
+        if (percentile > 100) percentile = 100;
+        if (percentile < 0) percentile = 0;
+
+        int metricPos = getMetricPosition(id);
+        return mStoredStatistics[metricPos].getPercentile(percentile);
+    }
+
+    public int getTotalFrameCount() {
+        if (mStoredStatistics.length == 0) {
+            return 0;
+        }
+
+        return (int) mStoredStatistics[0].getN();
+    }
+
+    public int getScore() {
+        SummaryStatistics badFramesStats = new SummaryStatistics();
+
+        int totalFrameCount = getTotalFrameCount();
+        for (int i = 0; i < totalFrameCount; i++) {
+            double totalDuration = getMetricAtIndex(i, FrameMetrics.TOTAL_DURATION);
+            if (totalDuration >= 12) {
+                badFramesStats.addValue(totalDuration);
+            }
+        }
+
+        int length = getSortedJankFrameIndices().length;
+        double jankFrameCount = 100 * length / (double) totalFrameCount;
+
+        System.out.println("Mean: " + badFramesStats.getMean() + " JankP: " + jankFrameCount
+                + " StdDev: " + badFramesStats.getStandardDeviation() +
+                " Count Bad: " + badFramesStats.getN() + " Count Jank: " + length);
+
+        return (int) Math.round(
+                (badFramesStats.getMean()) * jankFrameCount * badFramesStats.getStandardDeviation());
+    }
+
+    public int getJankPenalty() {
+        double total95th = mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)]
+                .getPercentile(95);
+        System.out.println("95: " + total95th);
+        double aboveThreshold = total95th - JANK_PENALTY_THRESHOLD_MS;
+        if (aboveThreshold <= 0) {
+            return 0;
+        }
+
+        if (aboveThreshold > ZERO_SCORE_ABOVE_THRESHOLD_MS) {
+            return BASE_SCORE;
+        }
+
+        return (int) Math.ceil(JANK_PENALTY_PER_MS_ABOVE_THRESHOLD * aboveThreshold);
+    }
+
+    public int getConsistencyBonus() {
+        DescriptiveStatistics totalDurationStats =
+                mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)];
+
+        double standardDeviation = totalDurationStats.getStandardDeviation();
+        if (standardDeviation == 0) {
+            return CONSISTENCY_BONUS_MAX;
+        }
+
+        // 1 / CV of the total duration.
+        double bonus = totalDurationStats.getMean() / standardDeviation;
+        return (int) Math.min(Math.round(bonus), CONSISTENCY_BONUS_MAX);
+    }
+
+    public int[] getSortedJankFrameIndices() {
+        ArrayList<Integer> jankFrameIndices = new ArrayList<>();
+        boolean tripleBuffered = false;
+        int totalFrameCount = getTotalFrameCount();
+        int totalDurationPos = getMetricPosition(FrameMetrics.TOTAL_DURATION);
+
+        for (int i = 0; i < totalFrameCount; i++) {
+            double thisDuration = mStoredStatistics[totalDurationPos].getElement(i);
+            if (!tripleBuffered) {
+                if (thisDuration > FRAME_PERIOD_MS) {
+                    tripleBuffered = true;
+                    jankFrameIndices.add(i);
+                }
+            } else {
+                if (thisDuration > 2 * FRAME_PERIOD_MS) {
+                    tripleBuffered = false;
+                    jankFrameIndices.add(i);
+                }
+            }
+        }
+
+        int[] res = new int[jankFrameIndices.size()];
+        int i = 0;
+        for (Integer index : jankFrameIndices) {
+            res[i++] = index;
+        }
+        return res;
+    }
+
+    private int getMetricPosition(int id) {
+        for (int i = 0; i < METRICS.length; i++) {
+            if (id == METRICS[i]) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    private void insertMetrics(List<FrameMetrics> instances) {
+        for (FrameMetrics frame : instances) {
+            for (int i = 0; i < METRICS.length; i++) {
+                DescriptiveStatistics stats = mStoredStatistics[i];
+                if (stats == null) {
+                    stats = new DescriptiveStatistics();
+                    mStoredStatistics[i] = stats;
+                }
+
+                mStoredStatistics[i].addValue(frame.getMetric(METRICS[i]) / (double) 1000000);
+            }
+        }
+    }
+
+    private void insertValues(double[] values) {
+        if (values.length != METRICS.length) {
+            throw new IllegalArgumentException("invalid values array");
+        }
+
+        for (int i = 0; i < values.length; i++) {
+            DescriptiveStatistics stats = mStoredStatistics[i];
+            if (stats == null) {
+                stats = new DescriptiveStatistics();
+                mStoredStatistics[i] = stats;
+            }
+
+            mStoredStatistics[i].addValue(values[i]);
+        }
+    }
+ }
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java
new file mode 100644
index 0000000..aba16d5
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java
@@ -0,0 +1,137 @@
+/*
+ * 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.benchmark.synthetic;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.benchmark.R;
+import com.android.benchmark.app.PerfTimeline;
+
+import junit.framework.Test;
+
+
+public class MemoryActivity extends Activity {
+    private TextView mTextStatus;
+    private TextView mTextMin;
+    private TextView mTextMax;
+    private TextView mTextTypical;
+    private PerfTimeline mTimeline;
+
+    TestInterface mTI;
+    int mActiveTest;
+
+    private class SyntheticTestCallback extends TestInterface.TestResultCallback {
+        @Override
+        void onTestResult(int command, float result) {
+            Intent resultIntent = new Intent();
+            resultIntent.putExtra("com.android.benchmark.synthetic.TEST_RESULT", result);
+            setResult(RESULT_OK, resultIntent);
+            finish();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_memory);
+
+        mTextStatus = (TextView) findViewById(R.id.textView_status);
+        mTextMin = (TextView) findViewById(R.id.textView_min);
+        mTextMax = (TextView) findViewById(R.id.textView_max);
+        mTextTypical = (TextView) findViewById(R.id.textView_typical);
+
+        mTimeline = (PerfTimeline) findViewById(R.id.mem_timeline);
+
+        mTI = new TestInterface(mTimeline, 2, new SyntheticTestCallback());
+        mTI.mTextMax = mTextMax;
+        mTI.mTextMin = mTextMin;
+        mTI.mTextStatus = mTextStatus;
+        mTI.mTextTypical = mTextTypical;
+
+        mTimeline.mLinesLow = mTI.mLinesLow;
+        mTimeline.mLinesHigh = mTI.mLinesHigh;
+        mTimeline.mLinesValue = mTI.mLinesValue;
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Intent i = getIntent();
+        mActiveTest = i.getIntExtra("test", 0);
+
+        switch (mActiveTest) {
+            case 0:
+                mTI.runMemoryBandwidth();
+                break;
+            case 1:
+                mTI.runMemoryLatency();
+                break;
+            case 2:
+                mTI.runPowerManagement();
+                break;
+            case 3:
+                mTI.runCPUHeatSoak();
+                break;
+            case 4:
+                mTI.runCPUGFlops();
+                break;
+            default:
+                break;
+
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.menu_memory, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        int id = item.getItemId();
+
+        //noinspection SimplifiableIfStatement
+        if (id == R.id.action_settings) {
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void onCpuBandwidth(View v) {
+
+
+    }
+
+
+
+
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java
new file mode 100644
index 0000000..8f083a2
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java
@@ -0,0 +1,451 @@
+/*
+ * 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.benchmark.synthetic;
+
+import android.view.View;
+import android.widget.TextView;
+
+import org.apache.commons.math.stat.StatUtils;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+
+public class TestInterface {
+    native long nInit(long options);
+    native long nDestroy(long b);
+    native float nGetData(long b, float[] data);
+    native boolean nRunPowerManagementTest(long b, long options);
+    native boolean nRunCPUHeatSoakTest(long b, long options);
+
+    native boolean nMemTestStart(long b);
+    native float nMemTestBandwidth(long b, long size);
+    native float nMemTestLatency(long b, long size);
+    native void nMemTestEnd(long b);
+
+    native float nGFlopsTest(long b, long opt);
+
+    public static class TestResultCallback {
+        void onTestResult(int command, float result) { }
+    }
+
+    static {
+        System.loadLibrary("nativebench");
+    }
+
+    float[] mLinesLow;
+    float[] mLinesHigh;
+    float[] mLinesValue;
+    TextView mTextStatus;
+    TextView mTextMin;
+    TextView mTextMax;
+    TextView mTextTypical;
+
+    private View mViewToUpdate;
+
+    private LooperThread mLT;
+
+    TestInterface(View v, int runtimeSeconds, TestResultCallback callback) {
+        int buckets = runtimeSeconds * 1000;
+        mLinesLow = new float[buckets * 4];
+        mLinesHigh = new float[buckets * 4];
+        mLinesValue = new float[buckets * 4];
+        mViewToUpdate = v;
+
+        mLT = new LooperThread(this, callback);
+        mLT.start();
+    }
+
+    static class LooperThread extends Thread {
+        public static final int CommandExit = 1;
+        public static final int TestPowerManagement = 2;
+        public static final int TestMemoryBandwidth = 3;
+        public static final int TestMemoryLatency = 4;
+        public static final int TestHeatSoak = 5;
+        public static final int TestGFlops = 6;
+
+        private volatile boolean mRun = true;
+        private TestInterface mTI;
+        private TestResultCallback mCallback;
+
+        Queue<Integer> mCommandQueue = new LinkedList<Integer>();
+
+        LooperThread(TestInterface ti, TestResultCallback callback) {
+            super("BenchmarkTestThread");
+            mTI = ti;
+            mCallback = callback;
+        }
+
+        void runCommand(int command) {
+            Integer i = Integer.valueOf(command);
+
+            synchronized (this) {
+                mCommandQueue.add(i);
+                notifyAll();
+            }
+        }
+
+        public void run() {
+            long b = mTI.nInit(0);
+            if (b == 0) {
+                return;
+            }
+
+            while (mRun) {
+                int command = 0;
+                synchronized (this) {
+                    if (mCommandQueue.isEmpty()) {
+                        try {
+                            wait();
+                        } catch (InterruptedException e) {
+                        }
+                    }
+
+                    if (!mCommandQueue.isEmpty()) {
+                        command = mCommandQueue.remove();
+                    }
+                }
+
+                switch (command) {
+                    case CommandExit:
+                        mRun = false;
+                        break;
+                    case TestPowerManagement:
+                        float score = mTI.testPowerManagement(b);
+                        mCallback.onTestResult(command, 0);
+                        break;
+                    case TestMemoryBandwidth:
+                        mTI.testCPUMemoryBandwidth(b);
+                        break;
+                    case TestMemoryLatency:
+                        mTI.testCPUMemoryLatency(b);
+                        break;
+                    case TestHeatSoak:
+                        mTI.testCPUHeatSoak(b);
+                        break;
+                    case TestGFlops:
+                        mTI.testCPUGFlops(b);
+                        break;
+
+                }
+
+                //mViewToUpdate.post(new Runnable() {
+                  //  public void run() {
+                   //     mViewToUpdate.invalidate();
+                    //}
+                //});
+            }
+
+            mTI.nDestroy(b);
+        }
+
+        void exit() {
+            mRun = false;
+        }
+    }
+
+    void postTextToView(TextView v, String s) {
+        final TextView tv = v;
+        final String ts = s;
+
+        v.post(new Runnable() {
+            public void run() {
+                tv.setText(ts);
+            }
+        });
+
+    }
+
+    float calcAverage(float[] data) {
+        float total = 0.f;
+        for (int ct=0; ct < data.length; ct++) {
+            total += data[ct];
+        }
+        return total / data.length;
+    }
+
+    void makeGraph(float[] data, float[] lines) {
+        for (int ct = 0; ct < data.length; ct++) {
+            lines[ct * 4 + 0] = (float)ct;
+            lines[ct * 4 + 1] = 500.f - data[ct];
+            lines[ct * 4 + 2] = (float)ct;
+            lines[ct * 4 + 3] = 500.f;
+        }
+    }
+
+    float testPowerManagement(long b) {
+        float[] dat = new float[mLinesLow.length / 4];
+        postTextToView(mTextStatus, "Running single-threaded");
+        nRunPowerManagementTest(b, 1);
+        nGetData(b, dat);
+        makeGraph(dat, mLinesLow);
+        mViewToUpdate.postInvalidate();
+        float avgMin = calcAverage(dat);
+
+        postTextToView(mTextMin, "Single threaded " + avgMin + " per second");
+
+        postTextToView(mTextStatus, "Running multi-threaded");
+        nRunPowerManagementTest(b, 4);
+        nGetData(b, dat);
+        makeGraph(dat, mLinesHigh);
+        mViewToUpdate.postInvalidate();
+        float avgMax = calcAverage(dat);
+        postTextToView(mTextMax, "Multi threaded " + avgMax + " per second");
+
+        postTextToView(mTextStatus, "Running typical");
+        nRunPowerManagementTest(b, 0);
+        nGetData(b, dat);
+        makeGraph(dat, mLinesValue);
+        mViewToUpdate.postInvalidate();
+        float avgTypical = calcAverage(dat);
+
+        float ofIdeal = avgTypical / (avgMax + avgMin) * 200.f;
+        postTextToView(mTextTypical, String.format("Typical mix (50/50) %%%2.0f of ideal", ofIdeal));
+        return ofIdeal * (avgMax + avgMin);
+    }
+
+    float testCPUHeatSoak(long b) {
+        float[] dat = new float[1000];
+        postTextToView(mTextStatus, "Running heat soak test");
+        for (int t = 0; t < 1000; t++) {
+            mLinesLow[t * 4 + 0] = (float)t;
+            mLinesLow[t * 4 + 1] = 498.f;
+            mLinesLow[t * 4 + 2] = (float)t;
+            mLinesLow[t * 4 + 3] = 500.f;
+        }
+
+        float peak = 0.f;
+        float total = 0.f;
+        float dThroughput = 0;
+        float prev = 0;
+        SummaryStatistics stats = new SummaryStatistics();
+        for (int t = 0; t < 1000; t++) {
+            nRunCPUHeatSoakTest(b, 1);
+            nGetData(b, dat);
+
+            float p = calcAverage(dat);
+            if (prev != 0) {
+                dThroughput += (prev - p);
+            }
+
+            prev = p;
+
+            mLinesLow[t * 4 + 1] = 499.f - p;
+            if (peak < p) {
+                peak = p;
+            }
+            for (float f : dat) {
+                stats.addValue(f);
+            }
+
+            total += p;
+
+            mViewToUpdate.postInvalidate();
+            postTextToView(mTextMin, "Peak " + peak + " per second");
+            postTextToView(mTextMax, "Current " + p + " per second");
+            postTextToView(mTextTypical, "Average " + (total / (t + 1)) + " per second");
+        }
+
+
+        float decreaseOverTime = dThroughput / 1000;
+
+        System.out.println("dthroughput/dt: " + decreaseOverTime);
+
+        float score = (float) (stats.getMean() / (stats.getStandardDeviation() * decreaseOverTime));
+
+        postTextToView(mTextStatus, "Score: " + score);
+        return score;
+    }
+
+    void testCPUMemoryBandwidth(long b) {
+        int[] sizeK = {1, 2, 3, 4, 5, 6, 7,
+                    8, 10, 12, 14, 16, 20, 24, 28,
+                    32, 40, 48, 56, 64, 80, 96, 112,
+                    128, 160, 192, 224, 256, 320, 384, 448,
+                    512, 640, 768, 896, 1024, 1280, 1536, 1792,
+                    2048, 2560, 3584, 4096, 5120, 6144, 7168,
+                    8192, 10240, 12288, 14336, 16384
+        };
+        final int subSteps = 15;
+        float[] results = new float[sizeK.length * subSteps];
+
+        nMemTestStart(b);
+
+        float[] dat = new float[1000];
+        postTextToView(mTextStatus, "Running Memory Bandwidth test");
+        for (int t = 0; t < 1000; t++) {
+            mLinesLow[t * 4 + 0] = (float)t;
+            mLinesLow[t * 4 + 1] = 498.f;
+            mLinesLow[t * 4 + 2] = (float)t;
+            mLinesLow[t * 4 + 3] = 500.f;
+        }
+
+        for (int i = 0; i < sizeK.length; i++) {
+            postTextToView(mTextStatus, "Running " + sizeK[i] + " K");
+
+            float rtot = 0.f;
+            for (int j = 0; j < subSteps; j++) {
+                float ret = nMemTestBandwidth(b, sizeK[i] * 1024);
+                rtot += ret;
+                results[i * subSteps + j] = ret;
+                mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - (results[i*15+j] * 20.f);
+                mViewToUpdate.postInvalidate();
+            }
+            rtot /= subSteps;
+
+            if (sizeK[i] == 2) {
+                postTextToView(mTextMin, "2K " + rtot + " GB/s");
+            }
+            if (sizeK[i] == 128) {
+                postTextToView(mTextMax, "128K " + rtot + " GB/s");
+            }
+            if (sizeK[i] == 8192) {
+                postTextToView(mTextTypical, "8M " + rtot + " GB/s");
+            }
+
+        }
+
+        nMemTestEnd(b);
+        postTextToView(mTextStatus, "Done");
+    }
+
+    void testCPUMemoryLatency(long b) {
+        int[] sizeK = {1, 2, 3, 4, 5, 6, 7,
+                8, 10, 12, 14, 16, 20, 24, 28,
+                32, 40, 48, 56, 64, 80, 96, 112,
+                128, 160, 192, 224, 256, 320, 384, 448,
+                512, 640, 768, 896, 1024, 1280, 1536, 1792,
+                2048, 2560, 3584, 4096, 5120, 6144, 7168,
+                8192, 10240, 12288, 14336, 16384
+        };
+        final int subSteps = 15;
+        float[] results = new float[sizeK.length * subSteps];
+
+        nMemTestStart(b);
+
+        float[] dat = new float[1000];
+        postTextToView(mTextStatus, "Running Memory Latency test");
+        for (int t = 0; t < 1000; t++) {
+            mLinesLow[t * 4 + 0] = (float)t;
+            mLinesLow[t * 4 + 1] = 498.f;
+            mLinesLow[t * 4 + 2] = (float)t;
+            mLinesLow[t * 4 + 3] = 500.f;
+        }
+
+        for (int i = 0; i < sizeK.length; i++) {
+            postTextToView(mTextStatus, "Running " + sizeK[i] + " K");
+
+            float rtot = 0.f;
+            for (int j = 0; j < subSteps; j++) {
+                float ret = nMemTestLatency(b, sizeK[i] * 1024);
+                rtot += ret;
+                results[i * subSteps + j] = ret;
+
+                if (ret > 400.f) ret = 400.f;
+                if (ret < 0.f) ret = 0.f;
+                mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - ret;
+                //android.util.Log.e("bench", "test bw " + sizeK[i] + " - " + ret);
+                mViewToUpdate.postInvalidate();
+            }
+            rtot /= subSteps;
+
+            if (sizeK[i] == 2) {
+                postTextToView(mTextMin, "2K " + rtot + " ns");
+            }
+            if (sizeK[i] == 128) {
+                postTextToView(mTextMax, "128K " + rtot + " ns");
+            }
+            if (sizeK[i] == 8192) {
+                postTextToView(mTextTypical, "8M " + rtot + " ns");
+            }
+
+        }
+
+        nMemTestEnd(b);
+        postTextToView(mTextStatus, "Done");
+    }
+
+    void testCPUGFlops(long b) {
+        int[] sizeK = {1, 2, 3, 4, 5, 6, 7
+        };
+        final int subSteps = 15;
+        float[] results = new float[sizeK.length * subSteps];
+
+        nMemTestStart(b);
+
+        float[] dat = new float[1000];
+        postTextToView(mTextStatus, "Running Memory Latency test");
+        for (int t = 0; t < 1000; t++) {
+            mLinesLow[t * 4 + 0] = (float)t;
+            mLinesLow[t * 4 + 1] = 498.f;
+            mLinesLow[t * 4 + 2] = (float)t;
+            mLinesLow[t * 4 + 3] = 500.f;
+        }
+
+        for (int i = 0; i < sizeK.length; i++) {
+            postTextToView(mTextStatus, "Running " + sizeK[i] + " K");
+
+            float rtot = 0.f;
+            for (int j = 0; j < subSteps; j++) {
+                float ret = nGFlopsTest(b, sizeK[i] * 1024);
+                rtot += ret;
+                results[i * subSteps + j] = ret;
+
+                if (ret > 400.f) ret = 400.f;
+                if (ret < 0.f) ret = 0.f;
+                mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - ret;
+                mViewToUpdate.postInvalidate();
+            }
+            rtot /= subSteps;
+
+            if (sizeK[i] == 2) {
+                postTextToView(mTextMin, "2K " + rtot + " ns");
+            }
+            if (sizeK[i] == 128) {
+                postTextToView(mTextMax, "128K " + rtot + " ns");
+            }
+            if (sizeK[i] == 8192) {
+                postTextToView(mTextTypical, "8M " + rtot + " ns");
+            }
+
+        }
+
+        nMemTestEnd(b);
+        postTextToView(mTextStatus, "Done");
+    }
+
+    public void runPowerManagement() {
+        mLT.runCommand(mLT.TestPowerManagement);
+    }
+
+    public void runMemoryBandwidth() {
+        mLT.runCommand(mLT.TestMemoryBandwidth);
+    }
+
+    public void runMemoryLatency() {
+        mLT.runCommand(mLT.TestMemoryLatency);
+    }
+
+    public void runCPUHeatSoak() {
+        mLT.runCommand(mLT.TestHeatSoak);
+    }
+
+    public void runCPUGFlops() {
+        mLT.runCommand(mLT.TestGFlops);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
new file mode 100644
index 0000000..f6a528a
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
@@ -0,0 +1,157 @@
+/*
+ * 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.benchmark.ui;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+/**
+ *
+ */
+public class BitmapUploadActivity extends AppCompatActivity {
+    private Automator mAutomator;
+
+    public static class UploadView extends View {
+        private int mColorValue;
+        private Bitmap mBitmap;
+        private final DisplayMetrics mMetrics = new DisplayMetrics();
+        private final Rect mRect = new Rect();
+
+        public UploadView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @SuppressWarnings("unused")
+        public void setColorValue(int colorValue) {
+            if (colorValue == mColorValue) return;
+
+            mColorValue = colorValue;
+
+            // modify the bitmap's color to ensure it's uploaded to the GPU
+            mBitmap.eraseColor(Color.rgb(mColorValue, 255 - mColorValue, 255));
+
+            invalidate();
+        }
+
+        @Override
+        protected void onAttachedToWindow() {
+            super.onAttachedToWindow();
+
+            getDisplay().getMetrics(mMetrics);
+            int minDisplayDimen = Math.min(mMetrics.widthPixels, mMetrics.heightPixels);
+            int bitmapSize = Math.min((int) (minDisplayDimen * 0.75), 720);
+            if (mBitmap == null
+                    || mBitmap.getWidth() != bitmapSize
+                    || mBitmap.getHeight() != bitmapSize) {
+                mBitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
+            }
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            if (mBitmap != null) {
+                mRect.set(0, 0, getWidth(), getHeight());
+                canvas.drawBitmap(mBitmap, null, mRect, null);
+            }
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            // animate color to force bitmap uploads
+            return super.onTouchEvent(event);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_bitmap_upload);
+
+        final View uploadRoot = findViewById(R.id.upload_root);
+        uploadRoot.setKeepScreenOn(true);
+        uploadRoot.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View view, MotionEvent motionEvent) {
+                UploadView uploadView = (UploadView) findViewById(R.id.upload_view);
+                ObjectAnimator colorValueAnimator =
+                        ObjectAnimator.ofInt(uploadView, "colorValue", 0, 255);
+                colorValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
+                colorValueAnimator.setRepeatCount(100);
+                colorValueAnimator.start();
+
+                // animate scene root to guarantee there's a minimum amount of GPU rendering work
+                ObjectAnimator yAnimator = ObjectAnimator.ofFloat(
+                        view, "translationY", 0, 100);
+                yAnimator.setRepeatMode(ValueAnimator.REVERSE);
+                yAnimator.setRepeatCount(100);
+                yAnimator.start();
+
+                return true;
+            }
+        });
+
+        final UploadView uploadView = (UploadView) findViewById(R.id.upload_view);
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+        mAutomator = new Automator("BMUpload", runId, iteration, getWindow(),
+                new Automator.AutomateCallback() {
+                    @Override
+                    public void onPostAutomate() {
+                        Intent result = new Intent();
+                        setResult(RESULT_OK, result);
+                        finish();
+                    }
+
+                    @Override
+                    public void onAutomate() {
+                        int[] coordinates = new int[2];
+                        uploadRoot.getLocationOnScreen(coordinates);
+
+                        int x = coordinates[0];
+                        int y = coordinates[1];
+
+                        float width = uploadRoot.getWidth();
+                        float height = uploadRoot.getHeight();
+
+                        float middleX = (x + width) / 5;
+                        float middleY = (y + height) / 5;
+
+                        addInteraction(Interaction.newTap(middleX, middleY));
+                    }
+                });
+
+        mAutomator.start();
+    }
+
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java
new file mode 100644
index 0000000..ea6fb58
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java
@@ -0,0 +1,160 @@
+/*
+ * 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.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+public class EditTextInputActivity extends AppCompatActivity {
+
+    private Automator mAutomator;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final EditText editText = new EditText(this);
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+        editText.setWidth(400);
+        editText.setHeight(200);
+        setContentView(editText);
+
+        String testName = getString(R.string.edit_text_input_name);
+
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setTitle(testName);
+        }
+
+        mAutomator = new Automator(testName, runId, iteration, getWindow(),
+                new Automator.AutomateCallback() {
+            @Override
+            public void onPostAutomate() {
+                Intent result = new Intent();
+                setResult(RESULT_OK, result);
+                finish();
+            }
+
+            @Override
+            public void onAutomate() {
+
+                int[] coordinates = new int[2];
+                editText.getLocationOnScreen(coordinates);
+
+                int x = coordinates[0];
+                int y = coordinates[1];
+
+                float width = editText.getWidth();
+                float height = editText.getHeight();
+
+                float middleX = (x + width) / 2;
+                float middleY = (y + height) / 2;
+
+                Interaction tap = Interaction.newTap(middleX, middleY);
+                addInteraction(tap);
+
+                int[] alphabet = {
+                        KeyEvent.KEYCODE_A,
+                        KeyEvent.KEYCODE_B,
+                        KeyEvent.KEYCODE_C,
+                        KeyEvent.KEYCODE_D,
+                        KeyEvent.KEYCODE_E,
+                        KeyEvent.KEYCODE_F,
+                        KeyEvent.KEYCODE_G,
+                        KeyEvent.KEYCODE_H,
+                        KeyEvent.KEYCODE_I,
+                        KeyEvent.KEYCODE_J,
+                        KeyEvent.KEYCODE_K,
+                        KeyEvent.KEYCODE_L,
+                        KeyEvent.KEYCODE_M,
+                        KeyEvent.KEYCODE_N,
+                        KeyEvent.KEYCODE_O,
+                        KeyEvent.KEYCODE_P,
+                        KeyEvent.KEYCODE_Q,
+                        KeyEvent.KEYCODE_R,
+                        KeyEvent.KEYCODE_S,
+                        KeyEvent.KEYCODE_T,
+                        KeyEvent.KEYCODE_U,
+                        KeyEvent.KEYCODE_V,
+                        KeyEvent.KEYCODE_W,
+                        KeyEvent.KEYCODE_X,
+                        KeyEvent.KEYCODE_Y,
+                        KeyEvent.KEYCODE_Z,
+                        KeyEvent.KEYCODE_SPACE
+                };
+                Interaction typeAlphabet = Interaction.newKeyInput(new int[] {
+                        KeyEvent.KEYCODE_A,
+                        KeyEvent.KEYCODE_B,
+                        KeyEvent.KEYCODE_C,
+                        KeyEvent.KEYCODE_D,
+                        KeyEvent.KEYCODE_E,
+                        KeyEvent.KEYCODE_F,
+                        KeyEvent.KEYCODE_G,
+                        KeyEvent.KEYCODE_H,
+                        KeyEvent.KEYCODE_I,
+                        KeyEvent.KEYCODE_J,
+                        KeyEvent.KEYCODE_K,
+                        KeyEvent.KEYCODE_L,
+                        KeyEvent.KEYCODE_M,
+                        KeyEvent.KEYCODE_N,
+                        KeyEvent.KEYCODE_O,
+                        KeyEvent.KEYCODE_P,
+                        KeyEvent.KEYCODE_Q,
+                        KeyEvent.KEYCODE_R,
+                        KeyEvent.KEYCODE_S,
+                        KeyEvent.KEYCODE_T,
+                        KeyEvent.KEYCODE_U,
+                        KeyEvent.KEYCODE_V,
+                        KeyEvent.KEYCODE_W,
+                        KeyEvent.KEYCODE_X,
+                        KeyEvent.KEYCODE_Y,
+                        KeyEvent.KEYCODE_Z,
+                        KeyEvent.KEYCODE_SPACE,
+                });
+
+                for (int i = 0; i < 5; i++) {
+                    addInteraction(typeAlphabet);
+                }
+            }
+        });
+        mAutomator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAutomator != null) {
+            mAutomator.cancel();
+            mAutomator = null;
+        }
+    }
+
+    private String getRunFilename() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(getClass().getSimpleName());
+        builder.append(System.currentTimeMillis());
+        return builder.toString();
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java
new file mode 100644
index 0000000..95fce38
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java
@@ -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.
+ */
+
+package com.android.benchmark.ui;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+public class FullScreenOverdrawActivity extends AppCompatActivity {
+
+    private Automator mAutomator;
+
+    private class OverdrawView extends View {
+        Paint paint = new Paint();
+        int mColorValue = 0;
+
+        public OverdrawView(Context context) {
+            super(context);
+        }
+
+        @SuppressWarnings("unused")
+        public void setColorValue(int colorValue) {
+            mColorValue = colorValue;
+            invalidate();
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "colorValue", 0, 255);
+            objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
+            objectAnimator.setRepeatCount(100);
+            objectAnimator.start();
+            return super.onTouchEvent(event);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            paint.setColor(Color.rgb(mColorValue, 255 - mColorValue, 255));
+
+            for (int i = 0; i < 10; i++) {
+                canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+            }
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final OverdrawView overdrawView = new OverdrawView(this);
+        overdrawView.setKeepScreenOn(true);
+        setContentView(overdrawView);
+
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+        String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_overdraw);
+
+        mAutomator = new Automator(name, runId, iteration, getWindow(),
+                new Automator.AutomateCallback() {
+                    @Override
+                    public void onPostAutomate() {
+                        Intent result = new Intent();
+                        setResult(RESULT_OK, result);
+                        finish();
+                    }
+
+                    @Override
+                    public void onAutomate() {
+                        int[] coordinates = new int[2];
+                        overdrawView.getLocationOnScreen(coordinates);
+
+                        int x = coordinates[0];
+                        int y = coordinates[1];
+
+                        float width = overdrawView.getWidth();
+                        float height = overdrawView.getHeight();
+
+                        float middleX = (x + width) / 5;
+                        float middleY = (y + height) / 5;
+
+                        addInteraction(Interaction.newTap(middleX, middleY));
+                    }
+                });
+
+        mAutomator.start();
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java
new file mode 100644
index 0000000..4644ea1
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java
@@ -0,0 +1,162 @@
+/*
+ * 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.benchmark.ui;
+
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.android.benchmark.R;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+
+public class ImageListViewScrollActivity extends ListViewScrollActivity {
+
+    private static final int LIST_SIZE = 100;
+
+    private static final int[] IMG_RES_ID = new int[]{
+            R.drawable.img1,
+            R.drawable.img2,
+            R.drawable.img3,
+            R.drawable.img4,
+            R.drawable.img1,
+            R.drawable.img2,
+            R.drawable.img3,
+            R.drawable.img4,
+            R.drawable.img1,
+            R.drawable.img2,
+            R.drawable.img3,
+            R.drawable.img4,
+            R.drawable.img1,
+            R.drawable.img2,
+            R.drawable.img3,
+            R.drawable.img4,
+    };
+
+    private static Bitmap[] mBitmapCache = new Bitmap[IMG_RES_ID.length];
+
+    private static final String[] WORDS = Utils.buildStringList(LIST_SIZE);
+
+    private HashMap<View, BitmapWorkerTask> mInFlight = new HashMap<>();
+
+    @Override
+    protected ListAdapter createListAdapter() {
+        return new ImageListAdapter();
+    }
+
+    @Override
+    protected String getName() {
+        return getString(R.string.image_list_view_scroll_name);
+    }
+
+    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
+        private final WeakReference<ImageView> imageViewReference;
+        private int data = 0;
+        private int cacheIdx = 0;
+        volatile boolean cancelled = false;
+
+        public BitmapWorkerTask(ImageView imageView, int cacheIdx) {
+            // Use a WeakReference to ensure the ImageView can be garbage collected
+            imageViewReference = new WeakReference<>(imageView);
+            this.cacheIdx = cacheIdx;
+        }
+
+        // Decode image in background.
+        @Override
+        protected Bitmap doInBackground(Integer... params) {
+            data = params[0];
+            return Utils.decodeSampledBitmapFromResource(getResources(), data, 100, 100);
+        }
+
+        // Once complete, see if ImageView is still around and set bitmap.
+        @Override
+        protected void onPostExecute(Bitmap bitmap) {
+            if (bitmap != null) {
+                final ImageView imageView = imageViewReference.get();
+                if (imageView != null) {
+                    if (!cancelled) {
+                        imageView.setImageBitmap(bitmap);
+                    }
+                    mBitmapCache[cacheIdx] = bitmap;
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        for (int i = 0; i < mBitmapCache.length; i++) {
+            mBitmapCache[i] = null;
+        }
+    }
+
+    class ImageListAdapter extends BaseAdapter {
+
+        @Override
+        public int getCount() {
+            return LIST_SIZE;
+        }
+
+        @Override
+        public Object getItem(int postition) {
+            return null;
+        }
+
+        @Override
+        public long getItemId(int postition) {
+            return postition;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(getBaseContext())
+                        .inflate(R.layout.image_scroll_list_item, parent, false);
+            }
+
+            ImageView imageView = (ImageView) convertView.findViewById(R.id.image_scroll_image);
+            BitmapWorkerTask inFlight = mInFlight.get(convertView);
+            if (inFlight != null) {
+                inFlight.cancelled = true;
+                mInFlight.remove(convertView);
+            }
+
+            int cacheIdx = position % IMG_RES_ID.length;
+            Bitmap bitmap = mBitmapCache[(cacheIdx)];
+            if (bitmap == null) {
+                BitmapWorkerTask bitmapWorkerTask = new BitmapWorkerTask(imageView, cacheIdx);
+                bitmapWorkerTask.execute(IMG_RES_ID[(cacheIdx)]);
+                mInFlight.put(convertView, bitmapWorkerTask);
+            }
+
+            imageView.setImageBitmap(bitmap);
+
+            TextView textView = (TextView) convertView.findViewById(R.id.image_scroll_text);
+            textView.setText(WORDS[position]);
+
+            return convertView;
+        }
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java
new file mode 100644
index 0000000..b973bc7
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java
@@ -0,0 +1,55 @@
+/*
+ * 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.benchmark.ui;
+
+import android.app.ActionBar;
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Window;
+import android.widget.ListAdapter;
+
+import com.android.benchmark.R;
+
+/**
+ * Simple list activity base class
+ */
+public abstract class ListActivityBase extends AppCompatActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_list_fragment);
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            actionBar.setTitle(getName());
+        }
+
+        if (findViewById(R.id.list_fragment_container) != null) {
+            FragmentManager fm = getSupportFragmentManager();
+            ListFragment listView = new ListFragment();
+            listView.setListAdapter(createListAdapter());
+            fm.beginTransaction().add(R.id.list_fragment_container, listView).commit();
+        }
+    }
+
+    protected abstract ListAdapter createListAdapter();
+    protected abstract String getName();
+}
+
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java
new file mode 100644
index 0000000..3ffb770
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java
@@ -0,0 +1,113 @@
+/*
+ * 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.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.view.FrameMetrics;
+import android.view.MotionEvent;
+import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.ListAdapter;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+import java.io.File;
+import java.util.List;
+
+public class ListViewScrollActivity extends ListActivityBase {
+
+    private static final int LIST_SIZE = 400;
+    private static final int INTERACTION_COUNT = 4;
+
+    private Automator mAutomator;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setTitle(getTitle());
+        }
+
+        mAutomator = new Automator(getName(), runId, iteration, getWindow(),
+                new Automator.AutomateCallback() {
+            @Override
+            public void onPostAutomate() {
+                Intent result = new Intent();
+                setResult(RESULT_OK, result);
+                finish();
+            }
+
+            @Override
+            public void onPostInteraction(List<FrameMetrics> metrics) {}
+
+            @Override
+            public void onAutomate() {
+                FrameLayout v = (FrameLayout) findViewById(R.id.list_fragment_container);
+
+                int[] coordinates = new int[2];
+                v.getLocationOnScreen(coordinates);
+
+                int x = coordinates[0];
+                int y = coordinates[1];
+
+                float width = v.getWidth();
+                float height = v.getHeight();
+
+                float middleX = (x + width) / 5;
+                float middleY = (y + height) / 5;
+
+                Interaction flingUp = Interaction.newFlingUp(middleX, middleY);
+                Interaction flingDown = Interaction.newFlingDown(middleX, middleY);
+
+                for (int i = 0; i < INTERACTION_COUNT; i++) {
+                    addInteraction(flingUp);
+                    addInteraction(flingDown);
+                }
+            }
+        });
+
+        mAutomator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAutomator != null) {
+            mAutomator.cancel();
+            mAutomator = null;
+        }
+    }
+
+    @Override
+    protected ListAdapter createListAdapter() {
+        return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
+                Utils.buildStringList(LIST_SIZE));
+    }
+
+    @Override
+    protected String getName() {
+        return getString(R.string.list_view_scroll_name);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java
new file mode 100644
index 0000000..68f75a3
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java
@@ -0,0 +1,107 @@
+/*
+ * 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.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+public class ShadowGridActivity extends AppCompatActivity {
+    private Automator mAutomator;
+    public static class MyListFragment extends ListFragment {
+	    @Override
+	    public void onViewCreated(View view, Bundle savedInstanceState) {
+		    super.onViewCreated(view, savedInstanceState);
+		    getListView().setDivider(null);
+	    }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+        FragmentManager fm = getSupportFragmentManager();
+        if (fm.findFragmentById(android.R.id.content) == null) {
+            ListFragment listFragment = new MyListFragment();
+
+            listFragment.setListAdapter(new ArrayAdapter<>(this,
+                    R.layout.card_row, R.id.card_text, Utils.buildStringList(200)));
+            fm.beginTransaction().add(android.R.id.content, listFragment).commit();
+
+            String testName = getString(R.string.shadow_grid_name);
+
+            mAutomator = new Automator(testName, runId, iteration, getWindow(),
+                    new Automator.AutomateCallback() {
+                @Override
+                public void onPostAutomate() {
+                    Intent result = new Intent();
+                    setResult(RESULT_OK, result);
+                    finish();
+                }
+
+                @Override
+                public void onAutomate() {
+                    ListView v = (ListView) findViewById(android.R.id.list);
+
+                    int[] coordinates = new int[2];
+                    v.getLocationOnScreen(coordinates);
+
+                    int x = coordinates[0];
+                    int y = coordinates[1];
+
+                    float width = v.getWidth();
+                    float height = v.getHeight();
+
+                    float middleX = (x + width) / 2;
+                    float middleY = (y + height) / 2;
+
+                    Interaction flingUp = Interaction.newFlingUp(middleX, middleY);
+                    Interaction flingDown = Interaction.newFlingDown(middleX, middleY);
+
+                    addInteraction(flingUp);
+                    addInteraction(flingDown);
+                    addInteraction(flingUp);
+                    addInteraction(flingDown);
+                    addInteraction(flingUp);
+                    addInteraction(flingDown);
+                    addInteraction(flingUp);
+                    addInteraction(flingDown);
+                }
+            });
+            mAutomator.start();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAutomator != null) {
+            mAutomator.cancel();
+            mAutomator = null;
+        }
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java
new file mode 100644
index 0000000..fcd168e
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java
@@ -0,0 +1,118 @@
+/*
+ * 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.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+import java.io.File;
+
+public class TextScrollActivity extends ListActivityBase {
+
+    public static final String EXTRA_HIT_RATE = ".TextScrollActivity.EXTRA_HIT_RATE";
+
+    private static final int PARAGRAPH_COUNT = 200;
+
+    private int mHitPercentage = 100;
+    private Automator mAutomator;
+    private String mName;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        mHitPercentage = getIntent().getIntExtra(EXTRA_HIT_RATE,
+                mHitPercentage);
+        super.onCreate(savedInstanceState);
+        final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+        final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+        final int id = getIntent().getIntExtra(BenchmarkRegistry.EXTRA_ID, -1);
+
+        if (id == -1) {
+            finish();
+            return;
+        }
+
+        mName = BenchmarkRegistry.getBenchmarkName(this, id);
+
+        mAutomator = new Automator(getName(), runId, iteration, getWindow(),
+                new Automator.AutomateCallback() {
+            @Override
+            public void onPostAutomate() {
+                Intent result = new Intent();
+                setResult(RESULT_OK, result);
+                finish();
+            }
+
+            @Override
+            public void onAutomate() {
+                ListView v = (ListView) findViewById(android.R.id.list);
+
+                int[] coordinates = new int[2];
+                v.getLocationOnScreen(coordinates);
+
+                int x = coordinates[0];
+                int y = coordinates[1];
+
+                float width = v.getWidth();
+                float height = v.getHeight();
+
+                float middleX = (x + width) / 2;
+                float middleY = (y + height) / 2;
+
+                Interaction flingUp = Interaction.newFlingUp(middleX, middleY);
+                Interaction flingDown = Interaction.newFlingDown(middleX, middleY);
+
+                addInteraction(flingUp);
+                addInteraction(flingDown);
+                addInteraction(flingUp);
+                addInteraction(flingDown);
+                addInteraction(flingUp);
+                addInteraction(flingDown);
+                addInteraction(flingUp);
+                addInteraction(flingDown);
+            }
+        });
+
+        mAutomator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAutomator != null) {
+            mAutomator.cancel();
+            mAutomator = null;
+        }
+    }
+
+    @Override
+    protected ListAdapter createListAdapter() {
+        return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
+                Utils.buildParagraphListWithHitPercentage(PARAGRAPH_COUNT, 80));
+    }
+
+    @Override
+    protected String getName() {
+        return mName;
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java
new file mode 100644
index 0000000..39f9206
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java
@@ -0,0 +1,135 @@
+/*
+ * 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.benchmark.ui;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.util.Random;
+
+public class Utils {
+
+    private static final int RANDOM_WORD_LENGTH = 10;
+
+    public static String getRandomWord(Random random, int length) {
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            char base = random.nextBoolean() ? 'A' : 'a';
+            char nextChar = (char)(random.nextInt(26) + base);
+            builder.append(nextChar);
+        }
+        return builder.toString();
+    }
+
+    public static String[] buildStringList(int count) {
+        Random random = new Random(0);
+        String[] result = new String[count];
+        for (int i = 0; i < count; i++) {
+            result[i] = getRandomWord(random, RANDOM_WORD_LENGTH);
+        }
+
+        return result;
+    }
+
+     // a small number of strings reused frequently, expected to hit
+    // in the word-granularity text layout cache
+    static final String[] CACHE_HIT_STRINGS = new String[] {
+            "a",
+            "small",
+            "number",
+            "of",
+            "strings",
+            "reused",
+            "frequently"
+    };
+
+    private static final int WORDS_IN_PARAGRAPH = 150;
+
+    // misses are fairly long 'words' to ensure they miss
+    private static final int PARAGRAPH_MISS_MIN_LENGTH = 4;
+    private static final int PARAGRAPH_MISS_MAX_LENGTH = 9;
+
+    static String[] buildParagraphListWithHitPercentage(int paragraphCount, int hitPercentage) {
+        if (hitPercentage < 0 || hitPercentage > 100) throw new IllegalArgumentException();
+
+        String[] strings = new String[paragraphCount];
+        Random random = new Random(0);
+        for (int i = 0; i < strings.length; i++) {
+            StringBuilder result = new StringBuilder();
+            for (int word = 0; word < WORDS_IN_PARAGRAPH; word++) {
+                if (word != 0) {
+                    result.append(" ");
+                }
+                if (random.nextInt(100) < hitPercentage) {
+                    // add a common word, which is very likely to hit in the cache
+                    result.append(CACHE_HIT_STRINGS[random.nextInt(CACHE_HIT_STRINGS.length)]);
+                } else {
+                    // construct a random word, which will *most likely* miss
+                    int length = PARAGRAPH_MISS_MIN_LENGTH;
+                    length += random.nextInt(PARAGRAPH_MISS_MAX_LENGTH - PARAGRAPH_MISS_MIN_LENGTH);
+
+                    result.append(getRandomWord(random, length));
+                }
+            }
+            strings[i] = result.toString();
+        }
+
+        return strings;
+    }
+
+
+    public static int calculateInSampleSize(
+            BitmapFactory.Options options, int reqWidth, int reqHeight) {
+        // Raw height and width of image
+        final int height = options.outHeight;
+        final int width = options.outWidth;
+        int inSampleSize = 1;
+
+        if (height > reqHeight || width > reqWidth) {
+
+            final int halfHeight = height / 2;
+            final int halfWidth = width / 2;
+
+            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+            // height and width larger than the requested height and width.
+            while ((halfHeight / inSampleSize) > reqHeight
+                    && (halfWidth / inSampleSize) > reqWidth) {
+                inSampleSize *= 2;
+            }
+        }
+
+        return inSampleSize;
+    }
+
+    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
+                                                   int reqWidth, int reqHeight) {
+
+        // First decode with inJustDecodeBounds=true to check dimensions
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeResource(res, resId, options);
+
+        // Calculate inSampleSize
+        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+        // Decode bitmap with inSampleSize set
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeResource(res, resId, options);
+    }
+
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java
new file mode 100644
index 0000000..1efd6bc
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java
@@ -0,0 +1,269 @@
+/*
+ * 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.benchmark.ui.automation;
+
+import android.annotation.TargetApi;
+import android.app.Instrumentation;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.view.FrameMetrics;
+import android.view.MotionEvent;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+
+import com.android.benchmark.results.GlobalResultsStore;
+import com.android.benchmark.results.UiBenchmarkResult;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@TargetApi(24)
+public class Automator extends HandlerThread
+        implements ViewTreeObserver.OnGlobalLayoutListener, CollectorThread.CollectorListener {
+    public static final long FRAME_PERIOD_MILLIS = 16;
+
+    private static final int PRE_READY_STATE_COUNT = 3;
+    private static final String TAG = "Benchmark.Automator";
+    private final AtomicInteger mReadyState;
+
+    private AutomateCallback mCallback;
+    private Window mWindow;
+    private AutomatorHandler mHandler;
+    private CollectorThread mCollectorThread;
+    private int mRunId;
+    private int mIteration;
+    private String mTestName;
+
+    public static class AutomateCallback {
+        public void onAutomate() {}
+        public void onPostInteraction(List<FrameMetrics> metrics) {}
+        public void onPostAutomate() {}
+
+        protected final void addInteraction(Interaction interaction) {
+            if (mInteractions == null) {
+                return;
+            }
+
+            mInteractions.add(interaction);
+        }
+
+        protected final void setInteractions(List<Interaction> interactions) {
+            mInteractions = interactions;
+        }
+
+        private List<Interaction> mInteractions;
+    }
+
+    private static final class AutomatorHandler extends Handler {
+        public static final int MSG_NEXT_INTERACTION = 0;
+        public static final int MSG_ON_AUTOMATE = 1;
+        public static final int MSG_ON_POST_INTERACTION = 2;
+        private final String mTestName;
+        private final int mRunId;
+        private final int mIteration;
+
+        private Instrumentation mInstrumentation;
+        private volatile boolean mCancelled;
+        private CollectorThread mCollectorThread;
+        private AutomateCallback mCallback;
+        private Window mWindow;
+
+        LinkedList<Interaction> mInteractions;
+        private UiBenchmarkResult mResults;
+
+        AutomatorHandler(Looper looper, Window window, CollectorThread collectorThread,
+                         AutomateCallback callback, String testName, int runId, int iteration) {
+            super(looper);
+
+            mInstrumentation = new Instrumentation();
+
+            mCallback = callback;
+            mWindow = window;
+            mCollectorThread = collectorThread;
+            mInteractions = new LinkedList<>();
+            mTestName = testName;
+            mRunId = runId;
+            mIteration = iteration;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mCancelled) {
+                return;
+            }
+
+            switch (msg.what) {
+                case MSG_NEXT_INTERACTION:
+                    if (!nextInteraction()) {
+                        stopCollector();
+                        writeResults();
+                        mCallback.onPostAutomate();
+                    }
+                    break;
+                case MSG_ON_AUTOMATE:
+                    mCollectorThread.attachToWindow(mWindow);
+                    mCallback.setInteractions(mInteractions);
+                    mCallback.onAutomate();
+                    postNextInteraction();
+                    break;
+                case MSG_ON_POST_INTERACTION:
+                    List<FrameMetrics> collectedStats = (List<FrameMetrics>)msg.obj;
+                    persistResults(collectedStats);
+                    mCallback.onPostInteraction(collectedStats);
+                    postNextInteraction();
+                    break;
+            }
+        }
+
+        public void cancel() {
+            mCancelled = true;
+            stopCollector();
+        }
+
+        private void stopCollector() {
+            mCollectorThread.quitCollector();
+        }
+
+        private boolean nextInteraction() {
+
+            Interaction interaction = mInteractions.poll();
+            if (interaction != null) {
+                doInteraction(interaction);
+                return true;
+            }
+            return false;
+        }
+
+        private void doInteraction(Interaction interaction) {
+            if (mCancelled) {
+                return;
+            }
+
+            mCollectorThread.markInteractionStart();
+
+            if (interaction.getType() == Interaction.Type.KEY_EVENT) {
+                for (int code : interaction.getKeyCodes()) {
+                    if (!mCancelled) {
+                        mInstrumentation.sendKeyDownUpSync(code);
+                    } else {
+                        break;
+                    }
+                }
+            } else {
+                for (MotionEvent event : interaction.getEvents()) {
+                    if (!mCancelled) {
+                        mInstrumentation.sendPointerSync(event);
+                    } else {
+                        break;
+                    }
+                }
+            }
+        }
+
+        protected void postNextInteraction() {
+            final Message msg = obtainMessage(AutomatorHandler.MSG_NEXT_INTERACTION);
+            sendMessage(msg);
+        }
+
+        private void persistResults(List<FrameMetrics> stats) {
+            if (stats.isEmpty()) {
+                return;
+            }
+
+            if (mResults == null) {
+                mResults = new UiBenchmarkResult(stats);
+            } else {
+                mResults.update(stats);
+            }
+        }
+
+        private void writeResults() {
+            GlobalResultsStore.getInstance(mWindow.getContext())
+                    .storeRunResults(mTestName, mRunId, mIteration, mResults);
+        }
+    }
+
+    private void initHandler() {
+        mHandler = new AutomatorHandler(getLooper(), mWindow, mCollectorThread, mCallback,
+                mTestName, mRunId, mIteration);
+        mWindow = null;
+        mCallback = null;
+        mCollectorThread = null;
+        mTestName = null;
+        mRunId = 0;
+        mIteration = 0;
+    }
+
+    @Override
+    public final void onGlobalLayout() {
+        if (!mCollectorThread.isAlive()) {
+            mCollectorThread.start();
+            mWindow.getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
+            mReadyState.decrementAndGet();
+        }
+    }
+
+    @Override
+    public void onCollectorThreadReady() {
+        if (mReadyState.decrementAndGet() == 0) {
+            initHandler();
+            postOnAutomate();
+        }
+    }
+
+    @Override
+    protected void onLooperPrepared() {
+        if (mReadyState.decrementAndGet() == 0) {
+            initHandler();
+            postOnAutomate();
+        }
+    }
+
+    @Override
+    public void onPostInteraction(List<FrameMetrics> stats) {
+        Message m = mHandler.obtainMessage(AutomatorHandler.MSG_ON_POST_INTERACTION, stats);
+        mHandler.sendMessage(m);
+    }
+
+    protected void postOnAutomate() {
+        final Message msg = mHandler.obtainMessage(AutomatorHandler.MSG_ON_AUTOMATE);
+        mHandler.sendMessage(msg);
+    }
+
+    public void cancel() {
+        mHandler.removeMessages(AutomatorHandler.MSG_NEXT_INTERACTION);
+        mHandler.cancel();
+        mHandler = null;
+    }
+
+    public Automator(String testName, int runId, int iteration,
+                     Window window, AutomateCallback callback) {
+        super("AutomatorThread");
+
+        mTestName = testName;
+        mRunId = runId;
+        mIteration = iteration;
+        mCallback = callback;
+        mWindow = window;
+        mWindow.getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(this);
+        mCollectorThread = new CollectorThread(this);
+        mReadyState = new AtomicInteger(PRE_READY_STATE_COUNT);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java
new file mode 100644
index 0000000..806c704
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java
@@ -0,0 +1,164 @@
+/*
+ * 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.benchmark.ui.automation;
+
+import android.annotation.TargetApi;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.SystemClock;
+import android.view.FrameMetrics;
+import android.view.Window;
+
+import java.lang.ref.WeakReference;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ */
+final class CollectorThread extends HandlerThread {
+    private FrameStatsCollector mCollector;
+    private Window mAttachedWindow;
+    private List<FrameMetrics> mFrameTimingStats;
+    private long mLastFrameTime;
+    private WatchdogHandler mWatchdog;
+    private WeakReference<CollectorListener> mListener;
+
+    private volatile boolean mCollecting;
+
+
+    interface CollectorListener {
+        void onCollectorThreadReady();
+        void onPostInteraction(List<FrameMetrics> stats);
+    }
+
+    private final class WatchdogHandler extends Handler {
+        private static final long SCHEDULE_INTERVAL_MILLIS = 20 * Automator.FRAME_PERIOD_MILLIS;
+
+        private static final int MSG_SCHEDULE = 0;
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (!mCollecting) {
+                return;
+            }
+
+            long currentTime = SystemClock.uptimeMillis();
+            if (mLastFrameTime + SCHEDULE_INTERVAL_MILLIS <= currentTime) {
+                // haven't seen a frame in a while, interaction is probably done
+                mCollecting = false;
+                CollectorListener listener = mListener.get();
+                if (listener != null) {
+                    listener.onPostInteraction(mFrameTimingStats);
+                }
+            } else {
+                schedule();
+            }
+        }
+
+        public void schedule() {
+            sendMessageDelayed(obtainMessage(MSG_SCHEDULE), SCHEDULE_INTERVAL_MILLIS);
+        }
+
+        public void deschedule() {
+            removeMessages(MSG_SCHEDULE);
+        }
+    }
+
+    static boolean tripleBuffered = false;
+    static int janks = 0;
+    static int total = 0;
+    @TargetApi(24)
+    private class FrameStatsCollector implements Window.OnFrameMetricsAvailableListener {
+        @Override
+        public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCount) {
+            if (!mCollecting) {
+                return;
+            }
+            mFrameTimingStats.add(new FrameMetrics(frameMetrics));
+            mLastFrameTime = SystemClock.uptimeMillis();
+        }
+    }
+
+    public CollectorThread(CollectorListener listener) {
+        super("FrameStatsCollectorThread");
+        mFrameTimingStats = new LinkedList<>();
+        mListener = new WeakReference<>(listener);
+    }
+
+    @TargetApi(24)
+    public void attachToWindow(Window window) {
+        if (mAttachedWindow != null) {
+            mAttachedWindow.removeOnFrameMetricsAvailableListener(mCollector);
+        }
+
+        mAttachedWindow = window;
+        window.addOnFrameMetricsAvailableListener(mCollector, new Handler(getLooper()));
+    }
+
+    @TargetApi(24)
+    public synchronized void detachFromWindow() {
+        if (mAttachedWindow != null) {
+            mAttachedWindow.removeOnFrameMetricsAvailableListener(mCollector);
+        }
+
+        mAttachedWindow = null;
+    }
+
+    @TargetApi(24)
+    @Override
+    protected void onLooperPrepared() {
+        super.onLooperPrepared();
+        mCollector = new FrameStatsCollector();
+        mWatchdog = new WatchdogHandler();
+
+        CollectorListener listener = mListener.get();
+        if (listener != null) {
+            listener.onCollectorThreadReady();
+        }
+    }
+
+    public boolean quitCollector() {
+        stopCollecting();
+        detachFromWindow();
+        System.out.println("Jank Percentage: " + (100 * janks/ (double) total) + "%");
+        tripleBuffered = false;
+        total = 0;
+        janks = 0;
+        return quit();
+    }
+
+    void stopCollecting() {
+        if (!mCollecting) {
+            return;
+        }
+
+        mCollecting = false;
+        mWatchdog.deschedule();
+
+
+    }
+
+    public void markInteractionStart() {
+        mLastFrameTime = 0;
+        mFrameTimingStats.clear();
+        mCollecting = true;
+
+        mWatchdog.schedule();
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java
new file mode 100644
index 0000000..1fd0998
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java
@@ -0,0 +1,86 @@
+/*
+ * 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.benchmark.ui.automation;
+
+import android.support.annotation.IntDef;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+public class FrameTimingStats {
+    @IntDef ({
+            Index.FLAGS,
+            Index.INTENDED_VSYNC,
+            Index.VSYNC,
+            Index.OLDEST_INPUT_EVENT,
+            Index.NEWEST_INPUT_EVENT,
+            Index.HANDLE_INPUT_START,
+            Index.ANIMATION_START,
+            Index.PERFORM_TRAVERSALS_START,
+            Index.DRAW_START,
+            Index.SYNC_QUEUED,
+            Index.SYNC_START,
+            Index.ISSUE_DRAW_COMMANDS_START,
+            Index.SWAP_BUFFERS,
+            Index.FRAME_COMPLETED,
+    })
+    public @interface Index {
+        int FLAGS = 0;
+        int INTENDED_VSYNC = 1;
+        int VSYNC = 2;
+        int OLDEST_INPUT_EVENT = 3;
+        int NEWEST_INPUT_EVENT = 4;
+        int HANDLE_INPUT_START = 5;
+        int ANIMATION_START = 6;
+        int PERFORM_TRAVERSALS_START = 7;
+        int DRAW_START = 8;
+        int SYNC_QUEUED = 9;
+        int SYNC_START = 10;
+        int ISSUE_DRAW_COMMANDS_START = 11;
+        int SWAP_BUFFERS = 12;
+        int FRAME_COMPLETED = 13;
+
+        int FRAME_STATS_COUNT = 14; // must always be last
+    }
+
+    private final long[] mStats;
+
+    FrameTimingStats(long[] stats) {
+        mStats = Arrays.copyOf(stats, Index.FRAME_STATS_COUNT);
+    }
+
+    public FrameTimingStats(DataInputStream inputStream) throws IOException {
+        mStats = new long[Index.FRAME_STATS_COUNT];
+        update(inputStream);
+    }
+
+    public void update(DataInputStream inputStream) throws IOException {
+        for (int i = 0; i < mStats.length; i++) {
+            mStats[i] = inputStream.readLong();
+        }
+    }
+
+    public long get(@Index int index) {
+        return mStats[index];
+    }
+
+    public long[] data() {
+        return mStats;
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java
new file mode 100644
index 0000000..370fed2
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java
@@ -0,0 +1,187 @@
+/*
+ * 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.benchmark.ui.automation;
+
+import android.os.SystemClock;
+import android.support.annotation.IntDef;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Encodes a UI interaction as a series of MotionEvents
+ */
+public class Interaction {
+    private static final int STEP_COUNT = 20;
+    // TODO: scale to device display density
+    private static final int DEFAULT_FLING_SIZE_PX = 500;
+    private static final int DEFAULT_FLING_DURATION_MS = 20;
+    private static final int DEFAULT_TAP_DURATION_MS = 20;
+    private List<MotionEvent> mEvents;
+
+    // Interaction parameters
+    private final float[] mXPositions;
+    private final float[] mYPositions;
+    private final long mDuration;
+    private final int[] mKeyCodes;
+    private final @Interaction.Type int mType;
+
+    @IntDef({
+            Interaction.Type.TAP,
+            Interaction.Type.FLING,
+            Interaction.Type.PINCH,
+            Interaction.Type.KEY_EVENT})
+    public @interface Type {
+        int TAP = 0;
+        int FLING = 1;
+        int PINCH = 2;
+        int KEY_EVENT = 3;
+    }
+
+    public static Interaction newFling(float startX, float startY,
+                                       float endX, float endY, long duration) {
+       return new Interaction(Interaction.Type.FLING, new float[]{startX, endX},
+               new float[]{startY, endY}, duration);
+    }
+
+    public static Interaction newFlingDown(float startX, float startY) {
+        return new Interaction(Interaction.Type.FLING,
+                new float[]{startX, startX},
+                new float[]{startY, startY + DEFAULT_FLING_SIZE_PX}, DEFAULT_FLING_DURATION_MS);
+    }
+
+    public static Interaction newFlingUp(float startX, float startY) {
+        return new Interaction(Interaction.Type.FLING,
+                new float[]{startX, startX}, new float[]{startY, startY - DEFAULT_FLING_SIZE_PX},
+                        DEFAULT_FLING_DURATION_MS);
+    }
+
+    public static Interaction newTap(float startX, float startY) {
+        return new Interaction(Interaction.Type.TAP,
+                new float[]{startX, startX}, new float[]{startY, startY},
+                DEFAULT_FLING_DURATION_MS);
+    }
+
+    public static Interaction newKeyInput(int[] keyCodes) {
+        return new Interaction(keyCodes);
+    }
+
+    public List<MotionEvent> getEvents() {
+        switch (mType) {
+            case Type.FLING:
+                mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration);
+                break;
+            case Type.PINCH:
+                break;
+            case Type.TAP:
+                mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration);
+                break;
+        }
+
+        return mEvents;
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    public int[] getKeyCodes() {
+        return mKeyCodes;
+    }
+
+    private static List<MotionEvent> createInterpolatedEventList(
+            float[] xPos, float[] yPos, long duration) {
+        long startTime = SystemClock.uptimeMillis() + 100;
+        List<MotionEvent> result = new ArrayList<>();
+
+        float startX = xPos[0];
+        float startY = yPos[0];
+
+        MotionEvent downEvent = MotionEvent.obtain(
+                startTime, startTime, MotionEvent.ACTION_DOWN, startX, startY, 0);
+        result.add(downEvent);
+
+        for (int i = 1; i < xPos.length; i++) {
+            float endX = xPos[i];
+            float endY = yPos[i];
+            float stepX = (endX - startX) / STEP_COUNT;
+            float stepY = (endY - startY) / STEP_COUNT;
+            float stepT = duration / STEP_COUNT;
+
+            for (int j = 0; j < STEP_COUNT; j++) {
+                long deltaT = Math.round(j * stepT);
+                long deltaX = Math.round(j * stepX);
+                long deltaY = Math.round(j * stepY);
+                MotionEvent moveEvent = MotionEvent.obtain(startTime, startTime + deltaT,
+                        MotionEvent.ACTION_MOVE, startX + deltaX, startY + deltaY, 0);
+                result.add(moveEvent);
+            }
+
+            startX = endX;
+            startY = endY;
+        }
+
+        float lastX = xPos[xPos.length - 1];
+        float lastY = yPos[yPos.length - 1];
+        MotionEvent lastEvent = MotionEvent.obtain(startTime, startTime + duration,
+                MotionEvent.ACTION_UP, lastX, lastY, 0);
+        result.add(lastEvent);
+
+        return result;
+    }
+
+    private Interaction(@Interaction.Type int type,
+                        float[] xPos, float[] yPos, long duration) {
+        mType = type;
+        mXPositions = xPos;
+        mYPositions = yPos;
+        mDuration = duration;
+        mKeyCodes = null;
+    }
+
+    private Interaction(int[] codes) {
+        mKeyCodes = codes;
+        mType = Type.KEY_EVENT;
+        mYPositions = null;
+        mXPositions = null;
+        mDuration = 0;
+    }
+
+    private Interaction(@Interaction.Type int type,
+                        List<Float> xPositions, List<Float> yPositions, long duration) {
+        if (xPositions.size() != yPositions.size()) {
+            throw new IllegalArgumentException("must have equal number of x and y positions");
+        }
+
+        int current = 0;
+        mXPositions = new float[xPositions.size()];
+        for (float p : xPositions) {
+            mXPositions[current++] = p;
+        }
+
+        current = 0;
+        mYPositions = new float[yPositions.size()];
+        for (float p : xPositions) {
+            mXPositions[current++] = p;
+        }
+
+        mType = type;
+        mDuration = duration;
+        mKeyCodes = null;
+    }
+}
diff --git a/tests/JankBench/app/src/main/jni/Android.mk b/tests/JankBench/app/src/main/jni/Android.mk
new file mode 100644
index 0000000..8ba874de
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+LOCAL_SDK_VERSION := 26
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS = -Wno-unused-parameter
+
+LOCAL_MODULE:= libnativebench
+
+LOCAL_SRC_FILES := \
+	Bench.cpp \
+	WorkerPool.cpp \
+	test.cpp
+
+LOCAL_LDLIBS := -llog
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/JankBench/app/src/main/jni/Application.mk b/tests/JankBench/app/src/main/jni/Application.mk
new file mode 100644
index 0000000..09bc0ac
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Application.mk
@@ -0,0 +1,17 @@
+# 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.
+
+APP_ABI := armeabi 
+
+APP_MODULES := nativebench
diff --git a/tests/JankBench/app/src/main/jni/Bench.cpp b/tests/JankBench/app/src/main/jni/Bench.cpp
new file mode 100644
index 0000000..fbb4f11
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Bench.cpp
@@ -0,0 +1,373 @@
+/*
+ * 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 <android/log.h>
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "Bench.h"
+
+
+Bench::Bench()
+{
+    mTimeBucket = NULL;
+    mTimeBuckets = 0;
+    mTimeBucketDivisor = 1;
+
+    mMemLatencyLastSize = 0;
+    mMemDst = NULL;
+    mMemSrc = NULL;
+    mMemLoopCount = 0;
+}
+
+
+Bench::~Bench()
+{
+}
+
+uint64_t Bench::getTimeNanos() const
+{
+    struct timespec t;
+    clock_gettime(CLOCK_MONOTONIC, &t);
+    return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000);
+}
+
+uint64_t Bench::getTimeMillis() const
+{
+    return getTimeNanos() / 1000000;
+}
+
+
+void Bench::testWork(void *usr, uint32_t idx)
+{
+    Bench *b = (Bench *)usr;
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "test %i   %p", idx, b);
+
+    float f1 = 0.f;
+    float f2 = 0.f;
+    float f3 = 0.f;
+    float f4 = 0.f;
+
+    float *ipk = b->mIpKernel[idx];
+    volatile float *src = b->mSrcBuf[idx];
+    volatile float *out = b->mOutBuf[idx];
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "test %p %p %p", ipk, src, out);
+
+    do {
+
+        for (int i = 0; i < 1024; i++) {
+            f1 += src[i * 4] * ipk[i];
+            f2 += src[i * 4 + 1] * ipk[i];
+            f3 += src[i * 4 + 2] * ipk[i];
+            f4 += sqrtf(f1 + f2 + f3);
+        }
+        out[0] = f1;
+        out[1] = f2;
+        out[2] = f3;
+        out[3] = f4;
+
+    } while (b->incTimeBucket());
+}
+
+bool Bench::initIP() {
+    int workers = mWorkers.getWorkerCount();
+
+    mIpKernel = new float *[workers];
+    mSrcBuf = new float *[workers];
+    mOutBuf = new float *[workers];
+
+    for (int i = 0; i < workers; i++) {
+        mIpKernel[i] = new float[1024];
+        mSrcBuf[i] = new float[4096];
+        mOutBuf[i] = new float[4];
+    }
+
+    return true;
+}
+
+bool Bench::runPowerManagementTest(uint64_t options) {
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt x %i", options);
+
+    mTimeBucketDivisor = 1000 * 1000;  // use ms
+    allocateBuckets(2 * 1000);
+
+    usleep(2 * 1000 * 1000);
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt 2  b %i", mTimeBuckets);
+
+    mTimeStartNanos = getTimeNanos();
+    mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor;
+    memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets);
+
+    bool useMT = false;
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt 2.1  b %i", mTimeBuckets);
+    mTimeEndGroupNanos = mTimeStartNanos;
+    do  {
+        // Advance 8ms
+        mTimeEndGroupNanos += 8 * 1000 * 1000;
+
+        int threads = useMT ? 1 : 0;
+        useMT = !useMT;
+        if ((options & 0x1f) != 0) {
+            threads = options & 0x1f;
+        }
+
+        //__android_log_print(ANDROID_LOG_INFO, "bench", "threads %i", threads);
+
+        mWorkers.launchWork(testWork, this, threads);
+    } while (mTimeEndGroupNanos <= mTimeEndNanos);
+
+    return true;
+}
+
+bool Bench::allocateBuckets(size_t bucketCount) {
+    if (bucketCount == mTimeBuckets) {
+        return true;
+    }
+
+    if (mTimeBucket != NULL) {
+        delete[] mTimeBucket;
+        mTimeBucket = NULL;
+    }
+
+    mTimeBuckets = bucketCount;
+    if (mTimeBuckets > 0) {
+        mTimeBucket = new uint32_t[mTimeBuckets];
+    }
+
+    return true;
+}
+
+bool Bench::init() {
+    mWorkers.init();
+
+    initIP();
+    //ALOGV("%p Launching thread(s), CPUs %i", mRSC, mWorkers.mCount + 1);
+
+    return true;
+}
+
+bool Bench::incTimeBucket() const {
+    uint64_t time = getTimeNanos();
+    uint64_t bucket = (time - mTimeStartNanos) / mTimeBucketDivisor;
+
+    if (bucket >= mTimeBuckets) {
+        return false;
+    }
+
+    __sync_fetch_and_add(&mTimeBucket[bucket], 1);
+
+    return time < mTimeEndGroupNanos;
+}
+
+void Bench::getData(float *data, size_t count) const {
+    if (count > mTimeBuckets) {
+        count = mTimeBuckets;
+    }
+    for (size_t ct = 0; ct < count; ct++) {
+        data[ct] = (float)mTimeBucket[ct];
+    }
+}
+
+bool Bench::runCPUHeatSoak(uint64_t /* options */)
+{
+    mTimeBucketDivisor = 1000 * 1000;  // use ms
+    allocateBuckets(1000);
+
+    mTimeStartNanos = getTimeNanos();
+    mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor;
+    memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets);
+
+    mTimeEndGroupNanos = mTimeEndNanos;
+    mWorkers.launchWork(testWork, this, 0);
+    return true;
+}
+
+float Bench::runMemoryBandwidthTest(uint64_t size)
+{
+    uint64_t t1 = getTimeMillis();
+    for (size_t ct = mMemLoopCount; ct > 0; ct--) {
+        memcpy(mMemDst, mMemSrc, size);
+    }
+    double dt = getTimeMillis() - t1;
+    dt /= 1000;
+
+    double bw = ((double)size) * mMemLoopCount / dt;
+    bw /= 1024 * 1024 * 1024;
+
+    float targetTime = 0.2f;
+    if (dt > targetTime) {
+        mMemLoopCount = (size_t)((double)mMemLoopCount / (dt / targetTime));
+    }
+
+    return (float)bw;
+}
+
+float Bench::runMemoryLatencyTest(uint64_t size)
+{
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "latency %i", (int)size);
+    void ** sp = (void **)mMemSrc;
+    size_t maxIndex = size / sizeof(void *);
+    size_t loops = ((maxIndex / 2) & (~3));
+    //loops = 10;
+
+    if (size != mMemLatencyLastSize) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "latency build %i %i", (int)maxIndex, loops);
+        mMemLatencyLastSize = size;
+        memset((void *)mMemSrc, 0, mMemLatencyLastSize);
+
+        size_t lastIdx = 0;
+        for (size_t ct = 0; ct < loops; ct++) {
+            size_t ni = rand() * rand();
+            ni = ni % maxIndex;
+            while ((sp[ni] != NULL) || (ni == lastIdx)) {
+                ni++;
+                if (ni >= maxIndex) {
+                    ni = 1;
+                }
+    //            __android_log_print(ANDROID_LOG_INFO, "bench", "gen ni loop %i %i", lastIdx, ni);
+            }
+      //      __android_log_print(ANDROID_LOG_INFO, "bench", "gen ct = %i  %i  %i  %p  %p", (int)ct, lastIdx, ni, &sp[lastIdx], &sp[ni]);
+            sp[lastIdx] = &sp[ni];
+            lastIdx = ni;
+        }
+        sp[lastIdx] = 0;
+    }
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "latency testing");
+
+    uint64_t t1 = getTimeNanos();
+    for (size_t ct = mMemLoopCount; ct > 0; ct--) {
+        size_t lc = 1;
+        volatile void *p = sp[0];
+        while (p != NULL) {
+            // Unroll once to minimize branching overhead.
+            void **pn = (void **)p;
+            p = pn[0];
+            pn = (void **)p;
+            p = pn[0];
+        }
+    }
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "v %i %i", loops * mMemLoopCount, v);
+
+    double dt = getTimeNanos() - t1;
+    double dts = dt / 1000000000;
+    double lat = dt / (loops * mMemLoopCount);
+    __android_log_print(ANDROID_LOG_INFO, "bench", "latency ret %f", lat);
+
+    float targetTime = 0.2f;
+    if (dts > targetTime) {
+        mMemLoopCount = (size_t)((double)mMemLoopCount / (dts / targetTime));
+        if (mMemLoopCount < 1) {
+            mMemLoopCount = 1;
+        }
+    }
+
+    return (float)lat;
+}
+
+bool Bench::startMemTests()
+{
+    mMemSrc = (uint8_t *)malloc(1024*1024*64);
+    mMemDst = (uint8_t *)malloc(1024*1024*64);
+
+    memset(mMemSrc, 0, 1024*1024*16);
+    memset(mMemDst, 0, 1024*1024*16);
+
+    mMemLoopCount = 1;
+    uint64_t start = getTimeMillis();
+    while((getTimeMillis() - start) < 500) {
+        memcpy(mMemDst, mMemSrc, 1024);
+        mMemLoopCount++;
+    }
+    mMemLatencyLastSize = 0;
+    return true;
+}
+
+void Bench::endMemTests()
+{
+    free(mMemSrc);
+    free(mMemDst);
+    mMemSrc = NULL;
+    mMemDst = NULL;
+    mMemLatencyLastSize = 0;
+}
+
+void Bench::GflopKernelC() {
+    int halfKX = (mGFlop.kernelXSize / 2);
+    for (int x = halfKX; x < (mGFlop.imageXSize - halfKX - 1); x++) {
+        const float * krnPtr = mGFlop.kernelBuffer;
+        float sum = 0.f;
+
+        int srcInc = mGFlop.imageXSize - mGFlop.kernelXSize;
+        const float * srcPtr = &mGFlop.srcBuffer[x - halfKX];
+
+        for (int ix = 0; ix < mGFlop.kernelXSize; ix++) {
+            sum += srcPtr[0] * krnPtr[0];
+            krnPtr++;
+            srcPtr++;
+        }
+
+        float * dstPtr = &mGFlop.dstBuffer[x];
+        dstPtr[0] = sum;
+
+    }
+
+}
+
+void Bench::GflopKernelC_y3() {
+}
+
+float Bench::runGFlopsTest(uint64_t /* options */)
+{
+    mTimeBucketDivisor = 1000 * 1000;  // use ms
+    allocateBuckets(1000);
+
+    mTimeStartNanos = getTimeNanos();
+    mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor;
+    memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets);
+
+    mTimeEndGroupNanos = mTimeEndNanos;
+    mWorkers.launchWork(testWork, this, 0);
+
+    // Simulate image convolve
+    mGFlop.kernelXSize = 27;
+    mGFlop.imageXSize = 1024 * 1024;
+
+    mGFlop.srcBuffer = (float *)malloc(mGFlop.imageXSize * sizeof(float));
+    mGFlop.dstBuffer = (float *)malloc(mGFlop.imageXSize * sizeof(float));
+    mGFlop.kernelBuffer = (float *)malloc(mGFlop.kernelXSize * sizeof(float));
+
+    double ops = mGFlop.kernelXSize;
+    ops = ops * 2.f - 1.f;
+    ops *= mGFlop.imageXSize;
+
+    uint64_t t1 = getTimeNanos();
+    GflopKernelC();
+    double dt = getTimeNanos() - t1;
+
+    dt /= 1000.f * 1000.f * 1000.f;
+
+    double gflops = ops / dt / 1000000000.f;
+
+    __android_log_print(ANDROID_LOG_INFO, "bench", "v %f %f %f", dt, ops, gflops);
+
+    return (float)gflops;
+}
+
+
diff --git a/tests/JankBench/app/src/main/jni/Bench.h b/tests/JankBench/app/src/main/jni/Bench.h
new file mode 100644
index 0000000..43a9066
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Bench.h
@@ -0,0 +1,112 @@
+/*
+ * 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 ANDROID_BENCH_H
+#define ANDROID_BENCH_H
+
+#include <pthread.h>
+
+#include "WorkerPool.h"
+
+#include <string.h>
+
+
+
+class Bench {
+public:
+    Bench();
+    ~Bench();
+
+    struct GFlop {
+        int kernelXSize;
+        //int kernelYSize;
+        int imageXSize;
+        //int imageYSize;
+
+        float *srcBuffer;
+        float *kernelBuffer;
+        float *dstBuffer;
+
+
+    };
+    GFlop mGFlop;
+
+    bool init();
+
+    bool runPowerManagementTest(uint64_t options);
+    bool runCPUHeatSoak(uint64_t options);
+
+    bool startMemTests();
+    void endMemTests();
+    float runMemoryBandwidthTest(uint64_t options);
+    float runMemoryLatencyTest(uint64_t options);
+
+    float runGFlopsTest(uint64_t options);
+
+    void getData(float *data, size_t count) const;
+
+
+    void finish();
+
+    void setPriority(int32_t p);
+    void destroyWorkerThreadResources();
+
+    uint64_t getTimeNanos() const;
+    uint64_t getTimeMillis() const;
+
+    // Adds a work unit completed to the timeline and returns
+    // true if the test is ongoing, false if time is up
+    bool incTimeBucket() const;
+
+
+protected:
+    WorkerPool mWorkers;
+
+    bool mExit;
+    bool mPaused;
+
+    static void testWork(void *usr, uint32_t idx);
+
+private:
+    uint8_t * volatile mMemSrc;
+    uint8_t * volatile mMemDst;
+    size_t mMemLoopCount;
+    size_t mMemLatencyLastSize;
+
+
+    float ** mIpKernel;
+    float * volatile * mSrcBuf;
+    float * volatile * mOutBuf;
+    uint32_t * mTimeBucket;
+
+    uint64_t mTimeStartNanos;
+    uint64_t mTimeEndNanos;
+    uint64_t mTimeBucketDivisor;
+    uint32_t mTimeBuckets;
+
+    uint64_t mTimeEndGroupNanos;
+
+    bool initIP();
+    void GflopKernelC();
+    void GflopKernelC_y3();
+
+    bool allocateBuckets(size_t);
+
+
+};
+
+
+#endif
diff --git a/tests/JankBench/app/src/main/jni/WorkerPool.cpp b/tests/JankBench/app/src/main/jni/WorkerPool.cpp
new file mode 100644
index 0000000..a92ac91
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/WorkerPool.cpp
@@ -0,0 +1,266 @@
+/*
+ * 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 "WorkerPool.h"
+//#include <atomic>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <android/log.h>
+
+
+//static pthread_key_t gThreadTLSKey = 0;
+//static uint32_t gThreadTLSKeyCount = 0;
+//static pthread_mutex_t gInitMutex = PTHREAD_MUTEX_INITIALIZER;
+
+
+WorkerPool::Signal::Signal() {
+    mSet = true;
+}
+
+WorkerPool::Signal::~Signal() {
+    pthread_mutex_destroy(&mMutex);
+    pthread_cond_destroy(&mCondition);
+}
+
+bool WorkerPool::Signal::init() {
+    int status = pthread_mutex_init(&mMutex, NULL);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool mutex init failure");
+        return false;
+    }
+
+    status = pthread_cond_init(&mCondition, NULL);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool condition init failure");
+        pthread_mutex_destroy(&mMutex);
+        return false;
+    }
+
+    return true;
+}
+
+void WorkerPool::Signal::set() {
+    int status;
+
+    status = pthread_mutex_lock(&mMutex);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i locking for set condition.", status);
+        return;
+    }
+
+    mSet = true;
+
+    status = pthread_cond_signal(&mCondition);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i on set condition.", status);
+    }
+
+    status = pthread_mutex_unlock(&mMutex);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i unlocking for set condition.", status);
+    }
+}
+
+bool WorkerPool::Signal::wait(uint64_t timeout) {
+    int status;
+    bool ret = false;
+
+    status = pthread_mutex_lock(&mMutex);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i locking for condition.", status);
+        return false;
+    }
+
+    if (!mSet) {
+        if (!timeout) {
+            status = pthread_cond_wait(&mCondition, &mMutex);
+        } else {
+#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE)
+            status = pthread_cond_timeout_np(&mCondition, &mMutex, timeout / 1000000);
+#else
+            // This is safe it will just make things less reponsive
+            status = pthread_cond_wait(&mCondition, &mMutex);
+#endif
+        }
+    }
+
+    if (!status) {
+        mSet = false;
+        ret = true;
+    } else {
+#ifndef RS_SERVER
+        if (status != ETIMEDOUT) {
+            __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i waiting for condition.", status);
+        }
+#endif
+    }
+
+    status = pthread_mutex_unlock(&mMutex);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i unlocking for condition.", status);
+    }
+
+    return ret;
+}
+
+
+
+WorkerPool::WorkerPool() {
+    mExit = false;
+    mRunningCount = 0;
+    mLaunchCount = 0;
+    mCount = 0;
+    mThreadId = NULL;
+    mNativeThreadId = NULL;
+    mLaunchSignals = NULL;
+    mLaunchCallback = NULL;
+
+
+}
+
+
+WorkerPool::~WorkerPool() {
+__android_log_print(ANDROID_LOG_INFO, "bench", "~wp");
+    mExit = true;
+    mLaunchData = NULL;
+    mLaunchCallback = NULL;
+    mRunningCount = mCount;
+
+    __sync_synchronize();
+    for (uint32_t ct = 0; ct < mCount; ct++) {
+        mLaunchSignals[ct].set();
+    }
+    void *res;
+    for (uint32_t ct = 0; ct < mCount; ct++) {
+        pthread_join(mThreadId[ct], &res);
+    }
+    //rsAssert(__sync_fetch_and_or(&mRunningCount, 0) == 0);
+    free(mThreadId);
+    free(mNativeThreadId);
+    delete[] mLaunchSignals;
+}
+
+bool WorkerPool::init(int threadCount) {
+    int cpu = sysconf(_SC_NPROCESSORS_CONF);
+    if (threadCount > 0) {
+        cpu = threadCount;
+    }
+    if (cpu < 1) {
+        return false;
+    }
+    mCount = (uint32_t)cpu;
+
+    __android_log_print(ANDROID_LOG_INFO, "Bench", "ThreadLaunch %i", mCount);
+
+    mThreadId = (pthread_t *) calloc(mCount, sizeof(pthread_t));
+    mNativeThreadId = (pid_t *) calloc(mCount, sizeof(pid_t));
+    mLaunchSignals = new Signal[mCount];
+    mLaunchCallback = NULL;
+
+    mCompleteSignal.init();
+    mRunningCount = mCount;
+    mLaunchCount = 0;
+    __sync_synchronize();
+
+    pthread_attr_t threadAttr;
+    int status = pthread_attr_init(&threadAttr);
+    if (status) {
+        __android_log_print(ANDROID_LOG_INFO, "bench", "Failed to init thread attribute.");
+        return false;
+    }
+
+    for (uint32_t ct=0; ct < mCount; ct++) {
+        status = pthread_create(&mThreadId[ct], &threadAttr, helperThreadProc, this);
+        if (status) {
+            mCount = ct;
+            __android_log_print(ANDROID_LOG_INFO, "bench", "Created fewer than expected number of threads.");
+            return false;
+        }
+    }
+    while (__sync_fetch_and_or(&mRunningCount, 0) != 0) {
+        usleep(100);
+    }
+
+    pthread_attr_destroy(&threadAttr);
+    return true;
+}
+
+void * WorkerPool::helperThreadProc(void *vwp) {
+    WorkerPool *wp = (WorkerPool *)vwp;
+
+    uint32_t idx = __sync_fetch_and_add(&wp->mLaunchCount, 1);
+
+    wp->mLaunchSignals[idx].init();
+    wp->mNativeThreadId[idx] = gettid();
+
+    while (!wp->mExit) {
+        wp->mLaunchSignals[idx].wait();
+        if (wp->mLaunchCallback) {
+           // idx +1 is used because the calling thread is always worker 0.
+           wp->mLaunchCallback(wp->mLaunchData, idx);
+        }
+        __sync_fetch_and_sub(&wp->mRunningCount, 1);
+        wp->mCompleteSignal.set();
+    }
+
+    //ALOGV("RS helperThread exited %p idx=%i", dc, idx);
+    return NULL;
+}
+
+
+void WorkerPool::waitForAll() const {
+}
+
+void WorkerPool::waitFor(uint64_t) const {
+}
+
+
+
+uint64_t WorkerPool::launchWork(WorkerCallback_t cb, void *usr, int maxThreads) {
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 1");
+    mLaunchData = usr;
+    mLaunchCallback = cb;
+
+    if (maxThreads < 1) {
+        maxThreads = mCount;
+    }
+    if ((uint32_t)maxThreads > mCount) {
+        //__android_log_print(ANDROID_LOG_INFO, "bench", "launchWork max > count", maxThreads, mCount);
+        maxThreads = mCount;
+    }
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 2  %i  %i  %i", maxThreads, mRunningCount, mCount);
+    mRunningCount = maxThreads;
+    __sync_synchronize();
+
+    for (int ct = 0; ct < maxThreads; ct++) {
+        mLaunchSignals[ct].set();
+    }
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 3    %i", mRunningCount);
+    while (__sync_fetch_and_or(&mRunningCount, 0) != 0) {
+        //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 3.1    %i", mRunningCount);
+        mCompleteSignal.wait();
+    }
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 4    %i", mRunningCount);
+    return 0;
+
+}
+
+
+
diff --git a/tests/JankBench/app/src/main/jni/WorkerPool.h b/tests/JankBench/app/src/main/jni/WorkerPool.h
new file mode 100644
index 0000000..f8985d2
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/WorkerPool.h
@@ -0,0 +1,83 @@
+/*
+ * 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 ANDROID_WORKER_POOL_H
+#define ANDROID_WORKER_POOL_H
+
+#include <pthread.h>
+#include <string.h>
+
+
+
+class WorkerPool {
+public:
+    WorkerPool();
+    ~WorkerPool();
+
+    typedef void (*WorkerCallback_t)(void *usr, uint32_t idx);
+
+    bool init(int threadCount = -1);
+    int getWorkerCount() const {return mCount;}
+
+    void waitForAll() const;
+    void waitFor(uint64_t) const;
+    uint64_t launchWork(WorkerCallback_t cb, void *usr, int maxThreads = -1);
+
+
+
+
+protected:
+    class Signal {
+    public:
+        Signal();
+        ~Signal();
+
+        bool init();
+        void set();
+
+        // returns true if the signal occured
+        // false for timeout
+        bool wait(uint64_t timeout = 0);
+
+    protected:
+        bool mSet;
+        pthread_mutex_t mMutex;
+        pthread_cond_t mCondition;
+    };
+
+    bool mExit;
+    volatile int mRunningCount;
+    volatile int mLaunchCount;
+    uint32_t mCount;
+    pthread_t *mThreadId;
+    pid_t *mNativeThreadId;
+    Signal mCompleteSignal;
+    Signal *mLaunchSignals;
+    WorkerCallback_t mLaunchCallback;
+    void *mLaunchData;
+
+
+
+
+private:
+    //static void * threadProc(void *);
+    static void * helperThreadProc(void *);
+
+
+};
+
+
+#endif
diff --git a/tests/JankBench/app/src/main/jni/test.cpp b/tests/JankBench/app/src/main/jni/test.cpp
new file mode 100644
index 0000000..e163daa
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/test.cpp
@@ -0,0 +1,178 @@
+/*
+ * 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 <stdlib.h>
+#include <stdio.h>
+//#include <fcntl.h>
+//#include <unistd.h>
+#include <math.h>
+#include <inttypes.h>
+#include <time.h>
+#include <android/log.h>
+
+#include "jni.h"
+#include "Bench.h"
+
+#define FUNC(name) Java_com_android_benchmark_synthetic_TestInterface_##name
+
+static uint64_t GetTime() {
+    struct timespec t;
+    clock_gettime(CLOCK_MONOTONIC, &t);
+    return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000);
+}
+
+extern "C" {
+
+jlong Java_com_android_benchmark_synthetic_TestInterface_nInit(JNIEnv *_env, jobject _this, jlong options) {
+    Bench *b = new Bench();
+    bool ret = b->init();
+
+    if (ret) {
+        return (jlong)b;
+    }
+
+    delete b;
+    return 0;
+}
+
+void Java_com_android_benchmark_synthetic_TestInterface_nDestroy(JNIEnv *_env, jobject _this, jlong _b) {
+    Bench *b = (Bench *)_b;
+
+    delete b;
+}
+
+jboolean Java_com_android_benchmark_synthetic_TestInterface_nRunPowerManagementTest(
+        JNIEnv *_env, jobject _this, jlong _b, jlong options) {
+    Bench *b = (Bench *)_b;
+    return b->runPowerManagementTest(options);
+}
+
+jboolean Java_com_android_benchmark_synthetic_TestInterface_nRunCPUHeatSoakTest(
+        JNIEnv *_env, jobject _this, jlong _b, jlong options) {
+    Bench *b = (Bench *)_b;
+    return b->runCPUHeatSoak(options);
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nGetData(
+        JNIEnv *_env, jobject _this, jlong _b, jfloatArray data) {
+    Bench *b = (Bench *)_b;
+
+    jsize len = _env->GetArrayLength(data);
+    float * ptr = _env->GetFloatArrayElements(data, 0);
+
+    b->getData(ptr, len);
+
+    _env->ReleaseFloatArrayElements(data, (jfloat *)ptr, 0);
+
+    return 0;
+}
+
+jboolean Java_com_android_benchmark_synthetic_TestInterface_nMemTestStart(
+        JNIEnv *_env, jobject _this, jlong _b) {
+    Bench *b = (Bench *)_b;
+    return b->startMemTests();
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nMemTestBandwidth(
+        JNIEnv *_env, jobject _this, jlong _b, jlong opt) {
+    Bench *b = (Bench *)_b;
+    return b->runMemoryBandwidthTest(opt);
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nGFlopsTest(
+        JNIEnv *_env, jobject _this, jlong _b, jlong opt) {
+    Bench *b = (Bench *)_b;
+    return b->runGFlopsTest(opt);
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nMemTestLatency(
+        JNIEnv *_env, jobject _this, jlong _b, jlong opt) {
+    Bench *b = (Bench *)_b;
+    return b->runMemoryLatencyTest(opt);
+}
+
+void Java_com_android_benchmark_synthetic_TestInterface_nMemTestEnd(
+        JNIEnv *_env, jobject _this, jlong _b) {
+    Bench *b = (Bench *)_b;
+    b->endMemTests();
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nMemoryTest(
+        JNIEnv *_env, jobject _this, jint subtest) {
+
+    uint8_t * volatile m1 = (uint8_t *)malloc(1024*1024*64);
+    uint8_t * m2 = (uint8_t *)malloc(1024*1024*64);
+
+    memset(m1, 0, 1024*1024*16);
+    memset(m2, 0, 1024*1024*16);
+
+    //__android_log_print(ANDROID_LOG_INFO, "bench", "test %i  %p  %p", subtest, m1, m2);
+
+
+    size_t loopCount = 0;
+    uint64_t start = GetTime();
+    while((GetTime() - start) < 1000000000) {
+        memcpy(m1, m2, subtest);
+        loopCount++;
+    }
+    if (loopCount == 0) {
+        loopCount = 1;
+    }
+
+    size_t count = loopCount;
+    uint64_t t1 = GetTime();
+    while (loopCount > 0) {
+        memcpy(m1, m2, subtest);
+        loopCount--;
+    }
+    uint64_t t2 = GetTime();
+
+    double dt = t2 - t1;
+    dt /= 1000 * 1000 * 1000;
+    double bw = ((double)subtest) * count / dt;
+
+    bw /= 1024 * 1024 * 1024;
+
+    __android_log_print(ANDROID_LOG_INFO, "bench", "size %i, bw %f", subtest, bw);
+
+    free (m1);
+    free (m2);
+    return (float)bw;
+}
+
+jlong Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestMalloc(
+        JNIEnv *_env, jobject _this, jint bytes) {
+    uint8_t *p = (uint8_t *)malloc(bytes);
+    memset(p, 0, bytes);
+    return (jlong)p;
+}
+
+void Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestFree(
+        JNIEnv *_env, jobject _this, jlong ptr) {
+    free((void *)ptr);
+}
+
+jlong Java_com_android_benchmark_synthetic_MemoryAvailableLoad2_nMemTestMalloc(
+        JNIEnv *_env, jobject _this, jint bytes) {
+    return Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestMalloc(_env, _this, bytes);
+}
+
+void Java_com_android_benchmark_synthetic_MemoryAvailableLoad2_nMemTestFree(
+        JNIEnv *_env, jobject _this, jlong ptr) {
+    Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestFree(_env, _this, ptr);
+}
+
+}; // extern "C"
diff --git a/tests/JankBench/app/src/main/res/drawable/ic_play.png b/tests/JankBench/app/src/main/res/drawable/ic_play.png
new file mode 100644
index 0000000..13ed283
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/ic_play.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img1.jpg b/tests/JankBench/app/src/main/res/drawable/img1.jpg
new file mode 100644
index 0000000..33c1fed
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img1.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img2.jpg b/tests/JankBench/app/src/main/res/drawable/img2.jpg
new file mode 100644
index 0000000..1ea97f2
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img2.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img3.jpg b/tests/JankBench/app/src/main/res/drawable/img3.jpg
new file mode 100644
index 0000000..ff99269
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img3.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img4.jpg b/tests/JankBench/app/src/main/res/drawable/img4.jpg
new file mode 100644
index 0000000..d9cbd2f
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img4.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml
new file mode 100644
index 0000000..6b3c899
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/upload_root"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="10dp"
+    android:clipToPadding="false">
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+        <view class="com.android.benchmark.ui.BitmapUploadActivity$UploadView"
+            android:id="@+id/upload_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </android.support.v7.widget.CardView>
+
+    <android.support.v4.widget.Space
+        android:layout_height="10dp"
+        android:layout_width="match_parent" />
+
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <android.support.v4.widget.Space
+        android:layout_height="10dp"
+        android:layout_width="match_parent" />
+
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/activity_home.xml b/tests/JankBench/app/src/main/res/layout/activity_home.xml
new file mode 100644
index 0000000..c4f4299
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_home.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context=".app.HomeActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_main" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml
new file mode 100644
index 0000000..0aaadde
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    tools:context=".app.HomeActivity"
+    android:orientation="vertical">
+
+    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/list_fragment_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</LinearLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/activity_memory.xml b/tests/JankBench/app/src/main/res/layout/activity_memory.xml
new file mode 100644
index 0000000..fd5cadc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_memory.xml
@@ -0,0 +1,49 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    tools:context="com.android.benchmark.synthetic.MemoryActivity">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:text="Large Text"
+            android:id="@+id/textView_status" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text=""
+            android:id="@+id/textView_min" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text=""
+            android:id="@+id/textView_max" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text=""
+            android:id="@+id/textView_typical" />
+
+        <view
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            class="com.android.benchmark.app.PerfTimeline"
+            android:id="@+id/mem_timeline" />
+
+    </LinearLayout>
+</RelativeLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/activity_running_list.xml b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml
new file mode 100644
index 0000000..7b7b930
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingBottom="@dimen/activity_vertical_margin"
+        android:paddingLeft="@dimen/activity_horizontal_margin"
+        android:paddingRight="@dimen/activity_horizontal_margin"
+        android:paddingTop="@dimen/activity_vertical_margin"
+        tools:context=".app.HomeActivity"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/score_text_view"
+            android:textSize="20sp"
+            android:textStyle="bold"
+            android:layout_width="match_parent"
+            android:layout_height="30dp"
+            />
+
+        <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/list_fragment_container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml
new file mode 100644
index 0000000..5375dbc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/group_name"
+        android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
+        android:textSize="17dp"
+        android:paddingTop="10dp"
+        android:paddingBottom="10dp"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml
new file mode 100644
index 0000000..5282e14
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:paddingLeft="?android:expandableListPreferredChildPaddingLeft"
+    android:layout_width="match_parent"
+    android:layout_height="55dip">
+
+
+    <CheckBox
+        android:id="@+id/benchmark_enable_checkbox"
+        android:checked="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:id="@+id/benchmark_name"
+        android:textSize="17dp"
+        android:paddingLeft="10dp"
+        android:paddingTop="10dp"
+        android:paddingBottom="10dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/card_row.xml b/tests/JankBench/app/src/main/res/layout/card_row.xml
new file mode 100644
index 0000000..215f9df
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/card_row.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="100dp"
+    android:paddingStart="10dp"
+    android:paddingEnd="10dp"
+    android:paddingTop="5dp"
+    android:paddingBottom="5dp"
+    android:clipToPadding="false"
+    android:background="@null">
+    <android.support.v7.widget.CardView
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1">
+        <TextView
+            android:id="@+id/card_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </android.support.v7.widget.CardView>
+
+    <android.support.v4.widget.Space
+        android:layout_height="match_parent"
+        android:layout_width="10dp" />
+
+    <android.support.v7.widget.CardView
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/content_main.xml b/tests/JankBench/app/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..201bd66
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/fragment_start_button"
+    android:name="com.android.benchmark.app.BenchmarkDashboardFragment"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:layout="@layout/fragment_dashboard" />
+
diff --git a/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml
new file mode 100644
index 0000000..f3100c7
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/main_content"
+    android:layout_width="match_parent"
+    android:layout_height="fill_parent"
+    android:fitsSystemWindows="true">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/appbar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/detail_backdrop_height"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+        android:fitsSystemWindows="true">
+
+        <android.support.design.widget.CollapsingToolbarLayout
+            android:id="@+id/collapsing_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layout_scrollFlags="scroll"
+            android:fitsSystemWindows="true"
+            app:contentScrim="?attr/colorPrimary"
+            app:expandedTitleMarginStart="48dp"
+            app:expandedTitleMarginEnd="64dp">
+
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
+                app:layout_collapseMode="parallax" />
+
+            <ImageView
+                android:id="@+id/backdrop"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:src="@mipmap/ic_launcher"
+                android:scaleType="centerCrop"
+                android:fitsSystemWindows="true"
+                app:layout_collapseMode="parallax" />
+
+        </android.support.design.widget.CollapsingToolbarLayout>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <android.support.v4.widget.NestedScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical" >
+
+            <ExpandableListView
+                android:id="@+id/test_list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" />
+
+        </LinearLayout>
+
+    </android.support.v4.widget.NestedScrollView>
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/start_button"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:src="@drawable/ic_play"
+        app:layout_anchor="@id/appbar"
+        app:layout_anchorGravity="bottom|right|end"
+        android:layout_margin="@dimen/fab_margin"
+        android:clickable="true"/>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml
new file mode 100644
index 0000000..74d9891
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ExpandableListView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml
new file mode 100644
index 0000000..c1662ea
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ImageView
+        android:id="@+id/image_scroll_image"
+        android:scaleType="centerCrop"
+        android:layout_width="100dp"
+        android:layout_height="100dp" />
+
+    <TextView
+        android:id="@+id/image_scroll_text"
+        android:layout_gravity="right"
+        android:textSize="12sp"
+        android:paddingLeft="20dp"
+        android:layout_width="100dp"
+        android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/results_list_item.xml b/tests/JankBench/app/src/main/res/layout/results_list_item.xml
new file mode 100644
index 0000000..f38b147
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/results_list_item.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal" android:layout_width="match_parent"
+    android:padding="8dp"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/result_name"
+        android:textSize="16sp"
+        android:layout_gravity="left"
+        android:layout_width="200dp"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:id="@+id/result_value"
+        android:textSize="16sp"
+        android:layout_gravity="right"
+        android:layout_width="200dp"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml
new file mode 100644
index 0000000..8a9d015
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/benchmark_name"
+        android:textSize="17sp"
+        android:paddingLeft="?android:listPreferredItemPaddingLeft"
+        android:paddingTop="10dp"
+        android:paddingBottom="10dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/menu/menu_main.xml b/tests/JankBench/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..1633acd
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".app.HomeActivity">
+    <item
+        android:id="@+id/action_settings"
+        android:orderInCategory="100"
+        android:title="@string/action_export"
+        app:showAsAction="never" />
+</menu>
diff --git a/tests/JankBench/app/src/main/res/menu/menu_memory.xml b/tests/JankBench/app/src/main/res/menu/menu_memory.xml
new file mode 100644
index 0000000..f2df7c9
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/menu/menu_memory.xml
@@ -0,0 +1,5 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools" tools:context="com.android.benchmark.Memory">
+    <item android:id="@+id/action_settings" android:title="@string/action_export"
+        android:orderInCategory="100" />
+</menu>
diff --git a/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/values-v21/styles.xml b/tests/JankBench/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..99ed094
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+    </style>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..e783e5d
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/attrs.xml b/tests/JankBench/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..a4286f1
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/attrs.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+    <!-- Root tag for benchmarks -->
+    <declare-styleable name="AndroidBenchmarks" />
+
+    <declare-styleable name="BenchmarkGroup">
+        <attr name="name" format="reference|string" />
+        <attr name="description" format="reference|string" />
+    </declare-styleable>
+
+    <declare-styleable name="Benchmark">
+        <attr name="name" />
+        <attr name="description" />
+        <attr name="id" format="reference" />
+        <attr name="category" format="enum">
+            <enum name="generic" value="0" />
+            <enum name="ui" value="1" />
+            <enum name="compute" value="2" />
+        </attr>
+    </declare-styleable>
+
+    <declare-styleable name="PerfTimeline"><attr name="exampleString" format="string"/>
+        <attr name="exampleDimension" format="dimension"/>
+        <attr name="exampleColor" format="color"/>
+        <attr name="exampleDrawable" format="color|reference"/>
+    </declare-styleable>
+
+</resources>
+
diff --git a/tests/JankBench/app/src/main/res/values/colors.xml b/tests/JankBench/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..59156ee
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/dimens.xml b/tests/JankBench/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..9da649a
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/dimens.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="detail_backdrop_height">256dp</dimen>
+    <dimen name="card_margin">16dp</dimen>
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="fab_margin">16dp</dimen>
+    <dimen name="app_bar_height">200dp</dimen>
+    <dimen name="item_width">200dp</dimen>
+    <dimen name="text_margin">16dp</dimen>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml
new file mode 100644
index 0000000..6801fd9
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/ids.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+    <item name="benchmark_list_view_scroll" type="id" />
+    <item name="benchmark_image_list_view_scroll" type="id" />
+    <item name="benchmark_shadow_grid" type="id" />
+    <item name="benchmark_text_high_hitrate" type="id" />
+    <item name="benchmark_text_low_hitrate" type="id" />
+    <item name="benchmark_edit_text_input" type="id" />
+    <item name="benchmark_overdraw" type="id" />
+    <item name="benchmark_memory_bandwidth" type="id" />
+    <item name="benchmark_memory_latency" type="id" />
+    <item name="benchmark_power_management" type="id" />
+    <item name="benchmark_cpu_heat_soak" type="id" />
+    <item name="benchmark_cpu_gflops" type="id" />
+</resources>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..270adf8
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/strings.xml
@@ -0,0 +1,51 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+    <string name="app_name">Benchmark</string>
+
+    <string name="action_export">Export to CSV</string>
+
+    <string name="list_view_scroll_name">List View Fling</string>
+    <string name="list_view_scroll_description">Tests list view fling performance</string>
+    <string name="image_list_view_scroll_name">Image List View Fling</string>
+    <string name="image_list_view_scroll_description">Tests list view fling performance with images</string>
+    <string name="shadow_grid_name">Shadow Grid Fling</string>
+    <string name="shadow_grid_description">Tests shadow grid fling performance with images</string>
+    <string name="text_high_hitrate_name">High-hitrate text render</string>
+    <string name="text_high_hitrate_description">Tests high hitrate text rendering</string>
+    <string name="text_low_hitrate_name">Low-hitrate text render</string>
+    <string name="text_low_hitrate_description">Tests low-hitrate text rendering</string>
+    <string name="edit_text_input_name">Edit Text Input</string>
+    <string name="edit_text_input_description">Tests edit text input</string>
+    <string name="overdraw_name">Overdraw Test</string>
+    <string name="overdraw_description">Tests how the device handles overdraw</string>
+    <string name="memory_bandwidth_name">Memory Bandwidth</string>
+    <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string>
+    <string name="memory_latency_name">Memory Latency</string>
+    <string name="memory_latency_description">Test device\'s memory latency</string>
+    <string name="power_management_name">Power Management</string>
+    <string name="power_management_description">Test device\'s power management</string>
+    <string name="cpu_heat_soak_name">CPU Heat Soak</string>
+    <string name="cpu_heat_soak_description">How hot can we make it?</string>
+    <string name="cpu_gflops_name">CPU GFlops</string>
+    <string name="cpu_gflops_description">How many gigaflops can the device attain?</string>
+
+    <string name="benchmark_category_ui">UI</string>
+    <string name="benchmark_category_compute">Compute</string>
+    <string name="benchmark_category_generic">Generic</string>
+    <string name="title_activity_image_list_view_scroll">ImageListViewScroll</string>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/styles.xml b/tests/JankBench/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..25ce730
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/styles.xml
@@ -0,0 +1,43 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+    <style name="Widget.CardContent" parent="android:Widget">
+        <item name="android:paddingLeft">16dp</item>
+        <item name="android:paddingRight">16dp</item>
+        <item name="android:paddingTop">24dp</item>
+        <item name="android:paddingBottom">24dp</item>
+        <item name="android:orientation">vertical</item>
+    </style>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml
new file mode 100644
index 0000000..07c453c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<com.android.benchmark.BenchmarkGroup
+    xmlns:benchmark="http://schemas.android.com/apk/res-auto"
+    benchmark:description="Benchmarks of the Android system"
+    benchmark:name="Android Benchmarks">
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/list_view_scroll_name"
+        benchmark:id="@id/benchmark_list_view_scroll"
+        benchmark:category="ui"
+        benchmark:description="@string/list_view_scroll_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/image_list_view_scroll_name"
+        benchmark:id="@id/benchmark_image_list_view_scroll"
+        benchmark:category="ui"
+        benchmark:description="@string/image_list_view_scroll_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/shadow_grid_name"
+        benchmark:id="@id/benchmark_shadow_grid"
+        benchmark:category="ui"
+        benchmark:description="@string/shadow_grid_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/text_low_hitrate_name"
+        benchmark:id="@id/benchmark_text_low_hitrate"
+        benchmark:category="ui"
+        benchmark:description="@string/text_low_hitrate_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/text_high_hitrate_name"
+        benchmark:id="@id/benchmark_text_high_hitrate"
+        benchmark:category="ui"
+        benchmark:description="@string/text_high_hitrate_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/edit_text_input_name"
+        benchmark:id="@id/benchmark_edit_text_input"
+        benchmark:category="ui"
+        benchmark:description="@string/edit_text_input_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/overdraw_name"
+        benchmark:id="@id/benchmark_overdraw"
+        benchmark:category="ui"
+        benchmark:description="@string/overdraw_description" />
+
+    <!--
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/memory_bandwidth_name"
+        benchmark:id="@id/benchmark_memory_bandwidth"
+        benchmark:category="compute"
+        benchmark:description="@string/memory_bandwidth_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/memory_latency_name"
+        benchmark:id="@id/benchmark_memory_latency"
+        benchmark:category="compute"
+        benchmark:description="@string/memory_latency_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/power_management_name"
+        benchmark:id="@id/benchmark_power_management"
+        benchmark:category="compute"
+        benchmark:description="@string/power_management_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/cpu_heat_soak_name"
+        benchmark:id="@id/benchmark_cpu_heat_soak"
+        benchmark:category="compute"
+        benchmark:description="@string/cpu_heat_soak_description" />
+
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/cpu_gflops_name"
+        benchmark:id="@id/benchmark_cpu_gflops"
+        benchmark:category="compute"
+        benchmark:description="@string/cpu_gflops_description" />
+        -->
+
+</com.android.benchmark.BenchmarkGroup>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java
new file mode 100644
index 0000000..4464e87
--- /dev/null
+++ b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.benchmark;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() throws Exception {
+        assertEquals(4, 2 + 2);
+    }
+}
diff --git a/tests/JankBench/scripts/adbutil.py b/tests/JankBench/scripts/adbutil.py
new file mode 100644
index 0000000..ad9f7d9
--- /dev/null
+++ b/tests/JankBench/scripts/adbutil.py
@@ -0,0 +1,62 @@
+import subprocess
+import re
+import threading
+
+ATRACE_PATH="/android/catapult/systrace/systrace/systrace.py"
+
+class AdbError(RuntimeError):
+    def __init__(self, arg):
+        self.args = arg
+
+def am(serial, cmd, args):
+    if not isinstance(args, list):
+        args = [args]
+    full_args = ["am"] + [cmd] + args
+    __call_adb(serial, full_args, False)
+
+def pm(serial, cmd, args):
+    if not isinstance(args, list):
+        args = [args]
+    full_args = ["pm"] + [cmd] + args
+    __call_adb(serial, full_args, False)
+
+def dumpsys(serial, topic):
+    return __call_adb(serial, ["dumpsys"] + [topic], True)
+
+def trace(serial,
+        tags = ["gfx", "sched", "view", "freq", "am", "wm", "power", "load", "memreclaim"],
+        time = "10"):
+    args = [ATRACE_PATH, "-e", serial, "-t" + time, "-b32768"] + tags
+    subprocess.call(args)
+
+def wake(serial):
+    output = dumpsys(serial, "power")
+    wakefulness = re.search('mWakefulness=([a-zA-Z]+)', output)
+    if wakefulness.group(1) != "Awake":
+        __call_adb(serial, ["input", "keyevent", "KEYCODE_POWER"], False)
+
+def root(serial):
+    subprocess.call(["adb", "-s", serial, "root"])
+
+def pull(serial, path, dest):
+    subprocess.call(["adb", "-s", serial, "wait-for-device", "pull"] + [path] + [dest])
+
+def shell(serial, cmd):
+    __call_adb(serial, cmd, False)
+
+def track_logcat(serial, awaited_string, callback):
+    threading.Thread(target=__track_logcat, name=serial + "-waiter", args=(serial, awaited_string, callback)).start()
+
+def __call_adb(serial, args, block):
+    full_args = ["adb", "-s", serial, "wait-for-device", "shell"] + args
+    print full_args
+    output = None
+    try:
+        if block:
+            output = subprocess.check_output(full_args)
+        else:
+            subprocess.call(full_args)
+    except subprocess.CalledProcessError:
+        raise AdbError("Error calling " + " ".join(args))
+
+    return output
diff --git a/tests/JankBench/scripts/collect.py b/tests/JankBench/scripts/collect.py
new file mode 100755
index 0000000..87a0594
--- /dev/null
+++ b/tests/JankBench/scripts/collect.py
@@ -0,0 +1,240 @@
+#!/usr/bin/python
+
+import optparse
+import sys
+import sqlite3
+import scipy.stats
+import numpy
+from math import log10, floor
+import matplotlib
+
+matplotlib.use("Agg")
+
+import matplotlib.pyplot as plt
+import pylab
+
+import adbutil
+from devices import DEVICES
+
+DB_PATH="/data/data/com.android.benchmark/databases/BenchmarkResults"
+OUT_PATH = "db/"
+
+QUERY_BAD_FRAME = ("select run_id, name, iteration, total_duration from ui_results "
+                   "where total_duration >= 16 order by run_id, name, iteration")
+QUERY_PERCENT_JANK = ("select run_id, name, iteration, sum(jank_frame) as jank_count, count (*) as total "
+                      "from ui_results group by run_id, name, iteration")
+
+SKIP_TESTS = [
+    # "BMUpload",
+    # "Low-hitrate text render",
+    # "High-hitrate text render",
+    # "Edit Text Input",
+    # "List View Fling"
+]
+
+INCLUDE_TESTS = [
+    #"BMUpload"
+    #"Shadow Grid Fling"
+    #"Image List View Fling"
+    #"Edit Text Input"
+]
+
+class IterationResult:
+    def __init__(self):
+        self.durations = []
+        self.jank_count = 0
+        self.total_count = 0
+
+
+def get_scoremap(dbpath):
+    db = sqlite3.connect(dbpath)
+    rows = db.execute(QUERY_BAD_FRAME)
+
+    scoremap = {}
+    for row in rows:
+        run_id = row[0]
+        name = row[1]
+        iteration = row[2]
+        total_duration = row[3]
+
+        if not run_id in scoremap:
+            scoremap[run_id] = {}
+
+        if not name in scoremap[run_id]:
+            scoremap[run_id][name] = {}
+
+        if not iteration in scoremap[run_id][name]:
+            scoremap[run_id][name][iteration] = IterationResult()
+
+        scoremap[run_id][name][iteration].durations.append(float(total_duration))
+
+    for row in db.execute(QUERY_PERCENT_JANK):
+        run_id = row[0]
+        name = row[1]
+        iteration = row[2]
+        jank_count = row[3]
+        total_count = row[4]
+
+        if run_id in scoremap.keys() and name in scoremap[run_id].keys() and iteration in scoremap[run_id][name].keys():
+            scoremap[run_id][name][iteration].jank_count = long(jank_count)
+            scoremap[run_id][name][iteration].total_count = long(total_count)
+
+    db.close()
+    return scoremap
+
+def round_to_2(val):
+    return val
+    if val == 0:
+        return val
+    return round(val , -int(floor(log10(abs(val)))) + 1)
+
+def score_device(name, serial, pull = False, verbose = False):
+    dbpath = OUT_PATH + name + ".db"
+
+    if pull:
+        adbutil.root(serial)
+        adbutil.pull(serial, DB_PATH, dbpath)
+
+    scoremap = None
+    try:
+        scoremap = get_scoremap(dbpath)
+    except sqlite3.DatabaseError:
+        print "Database corrupt, fetching..."
+        adbutil.root(serial)
+        adbutil.pull(serial, DB_PATH, dbpath)
+        scoremap = get_scoremap(dbpath)
+
+    per_test_score = {}
+    per_test_sample_count = {}
+    global_overall = {}
+
+    for run_id in iter(scoremap):
+        overall = []
+        if len(scoremap[run_id]) < 1:
+            if verbose:
+                print "Skipping short run %s" % run_id
+            continue
+        print "Run: %s" % run_id
+        for test in iter(scoremap[run_id]):
+            if test in SKIP_TESTS:
+                continue
+            if INCLUDE_TESTS and test not in INCLUDE_TESTS:
+                continue
+            if verbose:
+                print "\t%s" % test
+            scores = []
+            means = []
+            stddevs = []
+            pjs = []
+            sample_count = 0
+            hit_min_count = 0
+            # try pooling together all iterations
+            for iteration in iter(scoremap[run_id][test]):
+                res = scoremap[run_id][test][iteration]
+                stddev = round_to_2(numpy.std(res.durations))
+                mean = round_to_2(numpy.mean(res.durations))
+                sample_count += len(res.durations)
+                pj = round_to_2(100 * res.jank_count / float(res.total_count))
+                score = stddev * mean * pj
+                score = 100 * len(res.durations) / float(res.total_count)
+                if score == 0:
+                    score = 1
+                scores.append(score)
+                means.append(mean)
+                stddevs.append(stddev)
+                pjs.append(pj)
+                if verbose:
+                    print "\t%s: Score = %f x %f x %f = %f (%d samples)" % (iteration, stddev, mean, pj, score, len(res.durations))
+
+            if verbose:
+                print "\tHit min: %d" % hit_min_count
+                print "\tMean Variation: %0.2f%%" % (100 * scipy.stats.variation(means))
+                print "\tStdDev Variation: %0.2f%%" % (100 * scipy.stats.variation(stddevs))
+                print "\tPJ Variation: %0.2f%%" % (100 * scipy.stats.variation(pjs))
+
+            geo_run = numpy.mean(scores)
+            if test not in per_test_score:
+                per_test_score[test] = []
+
+            if test not in per_test_sample_count:
+                per_test_sample_count[test] = []
+
+            sample_count /= len(scoremap[run_id][test])
+
+            per_test_score[test].append(geo_run)
+            per_test_sample_count[test].append(int(sample_count))
+            overall.append(geo_run)
+
+            if not verbose:
+                print "\t%s:\t%0.2f (%0.2f avg. sample count)" % (test, geo_run, sample_count)
+            else:
+                print "\tOverall:\t%0.2f (%0.2f avg. sample count)" % (geo_run, sample_count)
+                print ""
+
+        global_overall[run_id] = scipy.stats.gmean(overall)
+        print "Run Overall: %f" % global_overall[run_id]
+        print ""
+
+    print ""
+    print "Variability (CV) - %s:" % name
+
+    worst_offender_test = None
+    worst_offender_variation = 0
+    for test in per_test_score:
+        variation = 100 * scipy.stats.variation(per_test_score[test])
+        if worst_offender_variation < variation:
+            worst_offender_test = test
+            worst_offender_variation = variation
+        print "\t%s:\t%0.2f%% (%0.2f avg sample count)" % (test, variation, numpy.mean(per_test_sample_count[test]))
+
+    print "\tOverall: %0.2f%%" % (100 * scipy.stats.variation([x for x in global_overall.values()]))
+    print ""
+
+    return {
+            "overall": global_overall.values(),
+            "worst_offender_test": (name, worst_offender_test, worst_offender_variation)
+            }
+
+def parse_options(argv):
+    usage = 'Usage: %prog [options]'
+    desc = 'Example: %prog'
+    parser = optparse.OptionParser(usage=usage, description=desc)
+    parser.add_option("-p", dest='pull', action="store_true")
+    parser.add_option("-d", dest='device', action="store")
+    parser.add_option("-v", dest='verbose', action="store_true")
+    options, categories = parser.parse_args(argv[1:])
+    return options
+
+def main():
+    options = parse_options(sys.argv)
+    if options.device != None:
+        score_device(options.device, DEVICES[options.device], options.pull, options.verbose)
+    else:
+        device_scores = []
+        worst_offenders = []
+        for name, serial in DEVICES.iteritems():
+            print "======== %s =========" % name
+            result = score_device(name, serial, options.pull, options.verbose)
+            device_scores.append((name, result["overall"]))
+            worst_offenders.append(result["worst_offender_test"])
+
+
+        device_scores.sort(cmp=(lambda x, y: cmp(x[1], y[1])))
+        print "Ranking by max overall score:"
+        for name, score in device_scores:
+            plt.plot([0, 1, 2, 3, 4, 5], score, label=name)
+            print "\t%s: %s" % (name, score)
+
+        plt.ylabel("Jank %")
+        plt.xlabel("Iteration")
+        plt.title("Jank Percentage")
+        plt.legend()
+        pylab.savefig("holy.png", bbox_inches="tight")
+
+        print "Worst offender tests:"
+        for device, test, variation in worst_offenders:
+            print "\t%s: %s %.2f%%" % (device, test, variation)
+
+if __name__ == "__main__":
+    main()
+
diff --git a/tests/JankBench/scripts/devices.py b/tests/JankBench/scripts/devices.py
new file mode 100644
index 0000000..c8266c0
--- /dev/null
+++ b/tests/JankBench/scripts/devices.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+
+DEVICES = {
+        'bullhead': '00606a370e3ca155',
+        'volantis': 'HT4BTWV00612',
+        'angler': '84B5T15A29021748',
+        'seed': '1285c85e',
+        'ryu': '5A27000599',
+        'shamu': 'ZX1G22W24R',
+}
+
diff --git a/tests/JankBench/scripts/external/__init__.py b/tests/JankBench/scripts/external/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/JankBench/scripts/external/__init__.py
diff --git a/tests/JankBench/scripts/external/statistics.py b/tests/JankBench/scripts/external/statistics.py
new file mode 100644
index 0000000..518f546
--- /dev/null
+++ b/tests/JankBench/scripts/external/statistics.py
@@ -0,0 +1,638 @@
+##  Module statistics.py
+##
+##  Copyright (c) 2013 Steven D'Aprano <steve+python@pearwood.info>.
+##
+##  Licensed under the Apache License, Version 2.0 (the "License");
+##  you may not use this file except in compliance with the License.
+##  You may obtain a copy of the License at
+##
+##  http://www.apache.org/licenses/LICENSE-2.0
+##
+##  Unless required by applicable law or agreed to in writing, software
+##  distributed under the License is distributed on an "AS IS" BASIS,
+##  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+##  See the License for the specific language governing permissions and
+##  limitations under the License.
+
+
+"""
+Basic statistics module.
+
+This module provides functions for calculating statistics of data, including
+averages, variance, and standard deviation.
+
+Calculating averages
+--------------------
+
+==================  =============================================
+Function            Description
+==================  =============================================
+mean                Arithmetic mean (average) of data.
+median              Median (middle value) of data.
+median_low          Low median of data.
+median_high         High median of data.
+median_grouped      Median, or 50th percentile, of grouped data.
+mode                Mode (most common value) of data.
+==================  =============================================
+
+Calculate the arithmetic mean ("the average") of data:
+
+>>> mean([-1.0, 2.5, 3.25, 5.75])
+2.625
+
+
+Calculate the standard median of discrete data:
+
+>>> median([2, 3, 4, 5])
+3.5
+
+
+Calculate the median, or 50th percentile, of data grouped into class intervals
+centred on the data values provided. E.g. if your data points are rounded to
+the nearest whole number:
+
+>>> median_grouped([2, 2, 3, 3, 3, 4])  #doctest: +ELLIPSIS
+2.8333333333...
+
+This should be interpreted in this way: you have two data points in the class
+interval 1.5-2.5, three data points in the class interval 2.5-3.5, and one in
+the class interval 3.5-4.5. The median of these data points is 2.8333...
+
+
+Calculating variability or spread
+---------------------------------
+
+==================  =============================================
+Function            Description
+==================  =============================================
+pvariance           Population variance of data.
+variance            Sample variance of data.
+pstdev              Population standard deviation of data.
+stdev               Sample standard deviation of data.
+==================  =============================================
+
+Calculate the standard deviation of sample data:
+
+>>> stdev([2.5, 3.25, 5.5, 11.25, 11.75])  #doctest: +ELLIPSIS
+4.38961843444...
+
+If you have previously calculated the mean, you can pass it as the optional
+second argument to the four "spread" functions to avoid recalculating it:
+
+>>> data = [1, 2, 2, 4, 4, 4, 5, 6]
+>>> mu = mean(data)
+>>> pvariance(data, mu)
+2.5
+
+
+Exceptions
+----------
+
+A single exception is defined: StatisticsError is a subclass of ValueError.
+
+"""
+
+__all__ = [ 'StatisticsError',
+            'pstdev', 'pvariance', 'stdev', 'variance',
+            'median',  'median_low', 'median_high', 'median_grouped',
+            'mean', 'mode',
+          ]
+
+
+import collections
+import math
+
+from fractions import Fraction
+from decimal import Decimal
+from itertools import groupby
+
+
+
+# === Exceptions ===
+
+class StatisticsError(ValueError):
+    pass
+
+
+# === Private utilities ===
+
+def _sum(data, start=0):
+    """_sum(data [, start]) -> (type, sum, count)
+
+    Return a high-precision sum of the given numeric data as a fraction,
+    together with the type to be converted to and the count of items.
+
+    If optional argument ``start`` is given, it is added to the total.
+    If ``data`` is empty, ``start`` (defaulting to 0) is returned.
+
+
+    Examples
+    --------
+
+    >>> _sum([3, 2.25, 4.5, -0.5, 1.0], 0.75)
+    (<class 'float'>, Fraction(11, 1), 5)
+
+    Some sources of round-off error will be avoided:
+
+    >>> _sum([1e50, 1, -1e50] * 1000)  # Built-in sum returns zero.
+    (<class 'float'>, Fraction(1000, 1), 3000)
+
+    Fractions and Decimals are also supported:
+
+    >>> from fractions import Fraction as F
+    >>> _sum([F(2, 3), F(7, 5), F(1, 4), F(5, 6)])
+    (<class 'fractions.Fraction'>, Fraction(63, 20), 4)
+
+    >>> from decimal import Decimal as D
+    >>> data = [D("0.1375"), D("0.2108"), D("0.3061"), D("0.0419")]
+    >>> _sum(data)
+    (<class 'decimal.Decimal'>, Fraction(6963, 10000), 4)
+
+    Mixed types are currently treated as an error, except that int is
+    allowed.
+    """
+    count = 0
+    n, d = _exact_ratio(start)
+    partials = {d: n}
+    partials_get = partials.get
+    T = _coerce(int, type(start))
+    for typ, values in groupby(data, type):
+        T = _coerce(T, typ)  # or raise TypeError
+        for n,d in map(_exact_ratio, values):
+            count += 1
+            partials[d] = partials_get(d, 0) + n
+    if None in partials:
+        # The sum will be a NAN or INF. We can ignore all the finite
+        # partials, and just look at this special one.
+        total = partials[None]
+        assert not _isfinite(total)
+    else:
+        # Sum all the partial sums using builtin sum.
+        # FIXME is this faster if we sum them in order of the denominator?
+        total = sum(Fraction(n, d) for d, n in sorted(partials.items()))
+    return (T, total, count)
+
+
+def _isfinite(x):
+    try:
+        return x.is_finite()  # Likely a Decimal.
+    except AttributeError:
+        return math.isfinite(x)  # Coerces to float first.
+
+
+def _coerce(T, S):
+    """Coerce types T and S to a common type, or raise TypeError.
+
+    Coercion rules are currently an implementation detail. See the CoerceTest
+    test class in test_statistics for details.
+    """
+    # See http://bugs.python.org/issue24068.
+    assert T is not bool, "initial type T is bool"
+    # If the types are the same, no need to coerce anything. Put this
+    # first, so that the usual case (no coercion needed) happens as soon
+    # as possible.
+    if T is S:  return T
+    # Mixed int & other coerce to the other type.
+    if S is int or S is bool:  return T
+    if T is int:  return S
+    # If one is a (strict) subclass of the other, coerce to the subclass.
+    if issubclass(S, T):  return S
+    if issubclass(T, S):  return T
+    # Ints coerce to the other type.
+    if issubclass(T, int):  return S
+    if issubclass(S, int):  return T
+    # Mixed fraction & float coerces to float (or float subclass).
+    if issubclass(T, Fraction) and issubclass(S, float):
+        return S
+    if issubclass(T, float) and issubclass(S, Fraction):
+        return T
+    # Any other combination is disallowed.
+    msg = "don't know how to coerce %s and %s"
+    raise TypeError(msg % (T.__name__, S.__name__))
+
+
+def _exact_ratio(x):
+    """Return Real number x to exact (numerator, denominator) pair.
+
+    >>> _exact_ratio(0.25)
+    (1, 4)
+
+    x is expected to be an int, Fraction, Decimal or float.
+    """
+    try:
+        # Optimise the common case of floats. We expect that the most often
+        # used numeric type will be builtin floats, so try to make this as
+        # fast as possible.
+        if type(x) is float:
+            return x.as_integer_ratio()
+        try:
+            # x may be an int, Fraction, or Integral ABC.
+            return (x.numerator, x.denominator)
+        except AttributeError:
+            try:
+                # x may be a float subclass.
+                return x.as_integer_ratio()
+            except AttributeError:
+                try:
+                    # x may be a Decimal.
+                    return _decimal_to_ratio(x)
+                except AttributeError:
+                    # Just give up?
+                    pass
+    except (OverflowError, ValueError):
+        # float NAN or INF.
+        assert not math.isfinite(x)
+        return (x, None)
+    msg = "can't convert type '{}' to numerator/denominator"
+    raise TypeError(msg.format(type(x).__name__))
+
+
+# FIXME This is faster than Fraction.from_decimal, but still too slow.
+def _decimal_to_ratio(d):
+    """Convert Decimal d to exact integer ratio (numerator, denominator).
+
+    >>> from decimal import Decimal
+    >>> _decimal_to_ratio(Decimal("2.6"))
+    (26, 10)
+
+    """
+    sign, digits, exp = d.as_tuple()
+    if exp in ('F', 'n', 'N'):  # INF, NAN, sNAN
+        assert not d.is_finite()
+        return (d, None)
+    num = 0
+    for digit in digits:
+        num = num*10 + digit
+    if exp < 0:
+        den = 10**-exp
+    else:
+        num *= 10**exp
+        den = 1
+    if sign:
+        num = -num
+    return (num, den)
+
+
+def _convert(value, T):
+    """Convert value to given numeric type T."""
+    if type(value) is T:
+        # This covers the cases where T is Fraction, or where value is
+        # a NAN or INF (Decimal or float).
+        return value
+    if issubclass(T, int) and value.denominator != 1:
+        T = float
+    try:
+        # FIXME: what do we do if this overflows?
+        return T(value)
+    except TypeError:
+        if issubclass(T, Decimal):
+            return T(value.numerator)/T(value.denominator)
+        else:
+            raise
+
+
+def _counts(data):
+    # Generate a table of sorted (value, frequency) pairs.
+    table = collections.Counter(iter(data)).most_common()
+    if not table:
+        return table
+    # Extract the values with the highest frequency.
+    maxfreq = table[0][1]
+    for i in range(1, len(table)):
+        if table[i][1] != maxfreq:
+            table = table[:i]
+            break
+    return table
+
+
+# === Measures of central tendency (averages) ===
+
+def mean(data):
+    """Return the sample arithmetic mean of data.
+
+    >>> mean([1, 2, 3, 4, 4])
+    2.8
+
+    >>> from fractions import Fraction as F
+    >>> mean([F(3, 7), F(1, 21), F(5, 3), F(1, 3)])
+    Fraction(13, 21)
+
+    >>> from decimal import Decimal as D
+    >>> mean([D("0.5"), D("0.75"), D("0.625"), D("0.375")])
+    Decimal('0.5625')
+
+    If ``data`` is empty, StatisticsError will be raised.
+    """
+    if iter(data) is data:
+        data = list(data)
+    n = len(data)
+    if n < 1:
+        raise StatisticsError('mean requires at least one data point')
+    T, total, count = _sum(data)
+    assert count == n
+    return _convert(total/n, T)
+
+
+# FIXME: investigate ways to calculate medians without sorting? Quickselect?
+def median(data):
+    """Return the median (middle value) of numeric data.
+
+    When the number of data points is odd, return the middle data point.
+    When the number of data points is even, the median is interpolated by
+    taking the average of the two middle values:
+
+    >>> median([1, 3, 5])
+    3
+    >>> median([1, 3, 5, 7])
+    4.0
+
+    """
+    data = sorted(data)
+    n = len(data)
+    if n == 0:
+        raise StatisticsError("no median for empty data")
+    if n%2 == 1:
+        return data[n//2]
+    else:
+        i = n//2
+        return (data[i - 1] + data[i])/2
+
+
+def median_low(data):
+    """Return the low median of numeric data.
+
+    When the number of data points is odd, the middle value is returned.
+    When it is even, the smaller of the two middle values is returned.
+
+    >>> median_low([1, 3, 5])
+    3
+    >>> median_low([1, 3, 5, 7])
+    3
+
+    """
+    data = sorted(data)
+    n = len(data)
+    if n == 0:
+        raise StatisticsError("no median for empty data")
+    if n%2 == 1:
+        return data[n//2]
+    else:
+        return data[n//2 - 1]
+
+
+def median_high(data):
+    """Return the high median of data.
+
+    When the number of data points is odd, the middle value is returned.
+    When it is even, the larger of the two middle values is returned.
+
+    >>> median_high([1, 3, 5])
+    3
+    >>> median_high([1, 3, 5, 7])
+    5
+
+    """
+    data = sorted(data)
+    n = len(data)
+    if n == 0:
+        raise StatisticsError("no median for empty data")
+    return data[n//2]
+
+
+def median_grouped(data, interval=1):
+    """Return the 50th percentile (median) of grouped continuous data.
+
+    >>> median_grouped([1, 2, 2, 3, 4, 4, 4, 4, 4, 5])
+    3.7
+    >>> median_grouped([52, 52, 53, 54])
+    52.5
+
+    This calculates the median as the 50th percentile, and should be
+    used when your data is continuous and grouped. In the above example,
+    the values 1, 2, 3, etc. actually represent the midpoint of classes
+    0.5-1.5, 1.5-2.5, 2.5-3.5, etc. The middle value falls somewhere in
+    class 3.5-4.5, and interpolation is used to estimate it.
+
+    Optional argument ``interval`` represents the class interval, and
+    defaults to 1. Changing the class interval naturally will change the
+    interpolated 50th percentile value:
+
+    >>> median_grouped([1, 3, 3, 5, 7], interval=1)
+    3.25
+    >>> median_grouped([1, 3, 3, 5, 7], interval=2)
+    3.5
+
+    This function does not check whether the data points are at least
+    ``interval`` apart.
+    """
+    data = sorted(data)
+    n = len(data)
+    if n == 0:
+        raise StatisticsError("no median for empty data")
+    elif n == 1:
+        return data[0]
+    # Find the value at the midpoint. Remember this corresponds to the
+    # centre of the class interval.
+    x = data[n//2]
+    for obj in (x, interval):
+        if isinstance(obj, (str, bytes)):
+            raise TypeError('expected number but got %r' % obj)
+    try:
+        L = x - interval/2  # The lower limit of the median interval.
+    except TypeError:
+        # Mixed type. For now we just coerce to float.
+        L = float(x) - float(interval)/2
+    cf = data.index(x)  # Number of values below the median interval.
+    # FIXME The following line could be more efficient for big lists.
+    f = data.count(x)  # Number of data points in the median interval.
+    return L + interval*(n/2 - cf)/f
+
+
+def mode(data):
+    """Return the most common data point from discrete or nominal data.
+
+    ``mode`` assumes discrete data, and returns a single value. This is the
+    standard treatment of the mode as commonly taught in schools:
+
+    >>> mode([1, 1, 2, 3, 3, 3, 3, 4])
+    3
+
+    This also works with nominal (non-numeric) data:
+
+    >>> mode(["red", "blue", "blue", "red", "green", "red", "red"])
+    'red'
+
+    If there is not exactly one most common value, ``mode`` will raise
+    StatisticsError.
+    """
+    # Generate a table of sorted (value, frequency) pairs.
+    table = _counts(data)
+    if len(table) == 1:
+        return table[0][0]
+    elif table:
+        raise StatisticsError(
+                'no unique mode; found %d equally common values' % len(table)
+                )
+    else:
+        raise StatisticsError('no mode for empty data')
+
+
+# === Measures of spread ===
+
+# See http://mathworld.wolfram.com/Variance.html
+#     http://mathworld.wolfram.com/SampleVariance.html
+#     http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
+#
+# Under no circumstances use the so-called "computational formula for
+# variance", as that is only suitable for hand calculations with a small
+# amount of low-precision data. It has terrible numeric properties.
+#
+# See a comparison of three computational methods here:
+# http://www.johndcook.com/blog/2008/09/26/comparing-three-methods-of-computing-standard-deviation/
+
+def _ss(data, c=None):
+    """Return sum of square deviations of sequence data.
+
+    If ``c`` is None, the mean is calculated in one pass, and the deviations
+    from the mean are calculated in a second pass. Otherwise, deviations are
+    calculated from ``c`` as given. Use the second case with care, as it can
+    lead to garbage results.
+    """
+    if c is None:
+        c = mean(data)
+    T, total, count = _sum((x-c)**2 for x in data)
+    # The following sum should mathematically equal zero, but due to rounding
+    # error may not.
+    U, total2, count2 = _sum((x-c) for x in data)
+    assert T == U and count == count2
+    total -=  total2**2/len(data)
+    assert not total < 0, 'negative sum of square deviations: %f' % total
+    return (T, total)
+
+
+def variance(data, xbar=None):
+    """Return the sample variance of data.
+
+    data should be an iterable of Real-valued numbers, with at least two
+    values. The optional argument xbar, if given, should be the mean of
+    the data. If it is missing or None, the mean is automatically calculated.
+
+    Use this function when your data is a sample from a population. To
+    calculate the variance from the entire population, see ``pvariance``.
+
+    Examples:
+
+    >>> data = [2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5]
+    >>> variance(data)
+    1.3720238095238095
+
+    If you have already calculated the mean of your data, you can pass it as
+    the optional second argument ``xbar`` to avoid recalculating it:
+
+    >>> m = mean(data)
+    >>> variance(data, m)
+    1.3720238095238095
+
+    This function does not check that ``xbar`` is actually the mean of
+    ``data``. Giving arbitrary values for ``xbar`` may lead to invalid or
+    impossible results.
+
+    Decimals and Fractions are supported:
+
+    >>> from decimal import Decimal as D
+    >>> variance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")])
+    Decimal('31.01875')
+
+    >>> from fractions import Fraction as F
+    >>> variance([F(1, 6), F(1, 2), F(5, 3)])
+    Fraction(67, 108)
+
+    """
+    if iter(data) is data:
+        data = list(data)
+    n = len(data)
+    if n < 2:
+        raise StatisticsError('variance requires at least two data points')
+    T, ss = _ss(data, xbar)
+    return _convert(ss/(n-1), T)
+
+
+def pvariance(data, mu=None):
+    """Return the population variance of ``data``.
+
+    data should be an iterable of Real-valued numbers, with at least one
+    value. The optional argument mu, if given, should be the mean of
+    the data. If it is missing or None, the mean is automatically calculated.
+
+    Use this function to calculate the variance from the entire population.
+    To estimate the variance from a sample, the ``variance`` function is
+    usually a better choice.
+
+    Examples:
+
+    >>> data = [0.0, 0.25, 0.25, 1.25, 1.5, 1.75, 2.75, 3.25]
+    >>> pvariance(data)
+    1.25
+
+    If you have already calculated the mean of the data, you can pass it as
+    the optional second argument to avoid recalculating it:
+
+    >>> mu = mean(data)
+    >>> pvariance(data, mu)
+    1.25
+
+    This function does not check that ``mu`` is actually the mean of ``data``.
+    Giving arbitrary values for ``mu`` may lead to invalid or impossible
+    results.
+
+    Decimals and Fractions are supported:
+
+    >>> from decimal import Decimal as D
+    >>> pvariance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")])
+    Decimal('24.815')
+
+    >>> from fractions import Fraction as F
+    >>> pvariance([F(1, 4), F(5, 4), F(1, 2)])
+    Fraction(13, 72)
+
+    """
+    if iter(data) is data:
+        data = list(data)
+    n = len(data)
+    if n < 1:
+        raise StatisticsError('pvariance requires at least one data point')
+    ss = _ss(data, mu)
+    T, ss = _ss(data, mu)
+    return _convert(ss/n, T)
+
+
+def stdev(data, xbar=None):
+    """Return the square root of the sample variance.
+
+    See ``variance`` for arguments and other details.
+
+    >>> stdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75])
+    1.0810874155219827
+
+    """
+    var = variance(data, xbar)
+    try:
+        return var.sqrt()
+    except AttributeError:
+        return math.sqrt(var)
+
+
+def pstdev(data, mu=None):
+    """Return the square root of the population variance.
+
+    See ``pvariance`` for arguments and other details.
+
+    >>> pstdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75])
+    0.986893273527251
+
+    """
+    var = pvariance(data, mu)
+    try:
+        return var.sqrt()
+    except AttributeError:
+        return math.sqrt(var)
diff --git a/tests/JankBench/scripts/itr_collect.py b/tests/JankBench/scripts/itr_collect.py
new file mode 100755
index 0000000..76499a4
--- /dev/null
+++ b/tests/JankBench/scripts/itr_collect.py
@@ -0,0 +1,154 @@
+#!/usr/bin/python
+
+import optparse
+import sys
+import sqlite3
+import scipy.stats
+import numpy
+
+import adbutil
+from devices import DEVICES
+
+DB_PATH="/data/data/com.android.benchmark/databases/BenchmarkResults"
+OUT_PATH = "db/"
+
+QUERY_BAD_FRAME = ("select run_id, name, total_duration from ui_results "
+                   "where total_duration >=12 order by run_id, name")
+QUERY_PERCENT_JANK = ("select run_id, name, sum(jank_frame) as jank_count, count (*) as total "
+                      "from ui_results group by run_id, name")
+
+class IterationResult:
+    def __init__(self):
+        self.durations = []
+        self.jank_count = 0
+        self.total_count = 0
+
+
+def get_scoremap(dbpath):
+    db = sqlite3.connect(dbpath)
+    rows = db.execute(QUERY_BAD_FRAME)
+
+    scoremap = {}
+    for row in rows:
+        run_id = row[0]
+        name = row[1]
+        total_duration = row[2]
+
+        if not run_id in scoremap:
+            scoremap[run_id] = {}
+
+        if not name in scoremap[run_id]:
+            scoremap[run_id][name] = IterationResult()
+
+
+        scoremap[run_id][name].durations.append(float(total_duration))
+
+    for row in db.execute(QUERY_PERCENT_JANK):
+        run_id = row[0]
+        name = row[1]
+        jank_count = row[2]
+        total_count = row[3]
+
+        if run_id in scoremap.keys() and name in scoremap[run_id].keys():
+            scoremap[run_id][name].jank_count = long(jank_count)
+            scoremap[run_id][name].total_count = long(total_count)
+
+
+    db.close()
+    return scoremap
+
+def score_device(name, serial, pull = False, verbose = False):
+    dbpath = OUT_PATH + name + ".db"
+
+    if pull:
+        adbutil.root(serial)
+        adbutil.pull(serial, DB_PATH, dbpath)
+
+    scoremap = None
+    try:
+        scoremap = get_scoremap(dbpath)
+    except sqlite3.DatabaseError:
+        print "Database corrupt, fetching..."
+        adbutil.root(serial)
+        adbutil.pull(serial, DB_PATH, dbpath)
+        scoremap = get_scoremap(dbpath)
+
+    per_test_score = {}
+    per_test_sample_count = {}
+    global_overall = {}
+
+    for run_id in iter(scoremap):
+        overall = []
+        if len(scoremap[run_id]) < 1:
+            if verbose:
+                print "Skipping short run %s" % run_id
+            continue
+        print "Run: %s" % run_id
+        for test in iter(scoremap[run_id]):
+            if verbose:
+                print "\t%s" % test
+            scores = []
+            sample_count = 0
+            res = scoremap[run_id][test]
+            stddev = numpy.std(res.durations)
+            mean = numpy.mean(res.durations)
+            sample_count = len(res.durations)
+            pj = 100 * res.jank_count / float(res.total_count)
+            score = stddev * mean *pj
+            if score == 0:
+                score = 1
+            scores.append(score)
+            if verbose:
+                print "\tScore = %f x %f x %f = %f (%d samples)" % (stddev, mean, pj, score, len(res.durations))
+
+            geo_run = scipy.stats.gmean(scores)
+            if test not in per_test_score:
+                per_test_score[test] = []
+
+            if test not in per_test_sample_count:
+                per_test_sample_count[test] = []
+
+            per_test_score[test].append(geo_run)
+            per_test_sample_count[test].append(int(sample_count))
+            overall.append(geo_run)
+
+            if not verbose:
+                print "\t%s:\t%0.2f (%0.2f avg. sample count)" % (test, geo_run, sample_count)
+            else:
+                print "\tOverall:\t%0.2f (%0.2f avg. sample count)" % (geo_run, sample_count)
+                print ""
+
+        global_overall[run_id] = scipy.stats.gmean(overall)
+        print "Run Overall: %f" % global_overall[run_id]
+        print ""
+
+    print ""
+    print "Variability (CV) - %s:" % name
+
+    for test in per_test_score:
+        print "\t%s:\t%0.2f%% (%0.2f avg sample count)" % (test, 100 * scipy.stats.variation(per_test_score[test]), numpy.mean(per_test_sample_count[test]))
+
+    print "\tOverall: %0.2f%%" % (100 * scipy.stats.variation([x for x in global_overall.values()]))
+    print ""
+
+def parse_options(argv):
+    usage = 'Usage: %prog [options]'
+    desc = 'Example: %prog'
+    parser = optparse.OptionParser(usage=usage, description=desc)
+    parser.add_option("-p", dest='pull', action="store_true")
+    parser.add_option("-d", dest='device', action="store")
+    parser.add_option("-v", dest='verbose', action="store_true")
+    options, categories = parser.parse_args(argv[1:])
+    return options
+
+def main():
+    options = parse_options(sys.argv)
+    if options.device != None:
+        score_device(options.device, DEVICES[options.device], options.pull, options.verbose)
+    else:
+        for name, serial in DEVICES.iteritems():
+            print "======== %s =========" % name
+            score_device(name, serial, options.pull, options.verbose)
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/JankBench/scripts/runall.py b/tests/JankBench/scripts/runall.py
new file mode 100755
index 0000000..d9a0662
--- /dev/null
+++ b/tests/JankBench/scripts/runall.py
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+
+import optparse
+import sys
+import time
+
+import adbutil
+from devices import DEVICES
+
+def parse_options(argv):
+    usage = 'Usage: %prog [options]'
+    desc = 'Example: %prog'
+    parser = optparse.OptionParser(usage=usage, description=desc)
+    parser.add_option("-c", dest='clear', action="store_true")
+    parser.add_option("-d", dest='device', action="store",)
+    parser.add_option("-t", dest='trace', action="store_true")
+    options, categories = parser.parse_args(argv[1:])
+    return (options, categories)
+
+def clear_data(device = None):
+    if device != None:
+        dev = DEVICES[device]
+        adbutil.root(dev)
+        adbutil.pm(dev, "clear", "com.android.benchmark")
+    else:
+        for name, dev in DEVICES.iteritems():
+            print("Clearing " + name)
+            adbutil.root(dev)
+            adbutil.pm(dev, "clear", "com.android.benchmark")
+
+def start_device(name, dev):
+    print("Go " + name + "!")
+    try:
+        adbutil.am(dev, "force-stop", "com.android.benchmark")
+        adbutil.wake(dev)
+        adbutil.am(dev, "start",
+            ["-n", "\"com.android.benchmark/.app.RunLocalBenchmarksActivity\"",
+            "--eia", "\"com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS\"", "\"0,1,2,3,4,5,6\"",
+            "--ei", "\"com.android.benchmark.EXTRA_RUN_COUNT\"", "\"5\""])
+    except adbutil.AdbError:
+        print "Couldn't launch " + name + "."
+
+def start_benchmark(device, trace):
+    if device != None:
+        start_device(device, DEVICES[device])
+        if trace:
+            time.sleep(3)
+            adbutil.trace(DEVICES[device])
+    else:
+        if trace:
+            print("Note: -t only valid with -d, can't trace")
+        for name, dev in DEVICES.iteritems():
+            start_device(name, dev)
+
+def main():
+    options, categories = parse_options(sys.argv)
+    if options.clear:
+        print options.device
+        clear_data(options.device)
+    else:
+        start_benchmark(options.device, options.trace)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 80e42a3..2282c13 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -348,64 +348,6 @@
     }
 
     @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);
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 5d1e10e..0467989 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -35,6 +35,8 @@
 
 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;
@@ -76,6 +78,36 @@
 
     private static final InetAddress INADDR_ANY;
 
+    private static final byte[] AEAD_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,
+        0x73, 0x61, 0x6C, 0x74
+    };
+    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
+    };
+
+    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, AEAD_KEY, 128);
+
+    private static final int[] DIRECTIONS =
+            new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT};
+
     static {
         try {
             INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
@@ -270,6 +302,127 @@
     }
 
     @Test
+    public void testValidateAlgorithmsAuth() {
+        for (int direction : DIRECTIONS) {
+            // Validate that correct algorithm type succeeds
+            IpSecConfig config = new IpSecConfig();
+            config.setAuthentication(direction, AUTH_ALGO);
+            mIpSecService.validateAlgorithms(config, direction);
+
+            // Validate that incorrect algorithm types fails
+            for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) {
+                try {
+                    config = new IpSecConfig();
+                    config.setAuthentication(direction, algo);
+                    mIpSecService.validateAlgorithms(config, direction);
+                    fail("Did not throw exception on invalid algorithm type");
+                } catch (IllegalArgumentException expected) {
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testValidateAlgorithmsCrypt() {
+        for (int direction : DIRECTIONS) {
+            // Validate that correct algorithm type succeeds
+            IpSecConfig config = new IpSecConfig();
+            config.setEncryption(direction, CRYPT_ALGO);
+            mIpSecService.validateAlgorithms(config, direction);
+
+            // Validate that incorrect algorithm types fails
+            for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) {
+                try {
+                    config = new IpSecConfig();
+                    config.setEncryption(direction, algo);
+                    mIpSecService.validateAlgorithms(config, direction);
+                    fail("Did not throw exception on invalid algorithm type");
+                } catch (IllegalArgumentException expected) {
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testValidateAlgorithmsAead() {
+        for (int direction : DIRECTIONS) {
+            // Validate that correct algorithm type succeeds
+            IpSecConfig config = new IpSecConfig();
+            config.setAuthenticatedEncryption(direction, AEAD_ALGO);
+            mIpSecService.validateAlgorithms(config, direction);
+
+            // Validate that incorrect algorithm types fails
+            for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) {
+                try {
+                    config = new IpSecConfig();
+                    config.setAuthenticatedEncryption(direction, algo);
+                    mIpSecService.validateAlgorithms(config, direction);
+                    fail("Did not throw exception on invalid algorithm type");
+                } catch (IllegalArgumentException expected) {
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testValidateAlgorithmsAuthCrypt() {
+        for (int direction : DIRECTIONS) {
+            // Validate that correct algorithm type succeeds
+            IpSecConfig config = new IpSecConfig();
+            config.setAuthentication(direction, AUTH_ALGO);
+            config.setEncryption(direction, CRYPT_ALGO);
+            mIpSecService.validateAlgorithms(config, direction);
+        }
+    }
+
+    @Test
+    public void testValidateAlgorithmsNoAlgorithms() {
+        IpSecConfig config = new IpSecConfig();
+        try {
+            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            fail("Expected exception; no algorithms specified");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testValidateAlgorithmsAeadWithAuth() {
+        IpSecConfig config = new IpSecConfig();
+        config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
+        config.setAuthentication(IpSecTransform.DIRECTION_IN, AUTH_ALGO);
+        try {
+            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            fail("Expected exception; both AEAD and auth algorithm specified");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testValidateAlgorithmsAeadWithCrypt() {
+        IpSecConfig config = new IpSecConfig();
+        config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
+        config.setEncryption(IpSecTransform.DIRECTION_IN, CRYPT_ALGO);
+        try {
+            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            fail("Expected exception; both AEAD and crypt algorithm specified");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testValidateAlgorithmsAeadWithAuthAndCrypt() {
+        IpSecConfig config = new IpSecConfig();
+        config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
+        config.setAuthentication(IpSecTransform.DIRECTION_IN, AUTH_ALGO);
+        config.setEncryption(IpSecTransform.DIRECTION_IN, CRYPT_ALGO);
+        try {
+            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            fail("Expected exception; AEAD, auth and crypt algorithm specified");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
     public void testDeleteInvalidTransportModeTransform() throws Exception {
         try {
             mIpSecService.deleteTransportModeTransform(1);
diff --git a/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java b/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java
index 93aa555..b39db61 100644
--- a/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java
@@ -71,18 +71,6 @@
     private class TestInstallObserver extends PackageInstallObserver {
     }
 
-    @SmallTest
-    public void testInstallPackage() {
-        TestInstallObserver observer = new TestInstallObserver();
-        try {
-            mPm.installPackage(null, observer, 0, null);
-            fail("PackageManager.installPackage" +
-                    "did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
     /*
      * This test verifies that PackageManger.freeStorage
      * enforces permission android.permission.CLEAR_APP_CACHE
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 7c1e96e..3bec082 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -186,6 +186,12 @@
       out_path_data->push_back(std::move(path_data.value()));
     }
   }
+
+  // File-system directory enumeration order is platform-dependent. Sort the result to remove any
+  // inconsistencies between platforms.
+  std::sort(
+      out_path_data->begin(), out_path_data->end(),
+      [](const ResourcePathData& a, const ResourcePathData& b) { return a.source < b.source; });
   return true;
 }
 
diff --git a/tools/aapt2/filter/AbiFilter.h b/tools/aapt2/filter/AbiFilter.h
index d875cb2..2832711 100644
--- a/tools/aapt2/filter/AbiFilter.h
+++ b/tools/aapt2/filter/AbiFilter.h
@@ -33,6 +33,8 @@
  */
 class AbiFilter : public IPathFilter {
  public:
+  virtual ~AbiFilter() = default;
+
   /** Factory method to create a filter from a list of configuration::Abi. */
   static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list);
 
diff --git a/tools/aapt2/filter/Filter.h b/tools/aapt2/filter/Filter.h
index d737dc9..f932f9c 100644
--- a/tools/aapt2/filter/Filter.h
+++ b/tools/aapt2/filter/Filter.h
@@ -27,7 +27,7 @@
 /** A filter to be applied to a path segment. */
 class IPathFilter {
  public:
-  ~IPathFilter() = default;
+  virtual ~IPathFilter() = default;
 
   /** Returns true if the path should be kept. */
   virtual bool Keep(const std::string& path) = 0;
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml b/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml
index e66d709..dd45239 100644
--- a/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml
+++ b/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml
@@ -18,4 +18,7 @@
     package="com.android.aapt.autoversiontest">
 
     <uses-sdk android:minSdkVersion="7" />
+    <application>
+        <uses-library android:name="clockwork-system" />
+    </application>
 </manifest>
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index da05dc3..ccc3470 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -29,6 +29,22 @@
 
 namespace aapt {
 
+static bool RequiredNameIsNotEmpty(xml::Element* el, SourcePathDiagnostics* diag) {
+  xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
+  if (attr == nullptr) {
+    diag->Error(DiagMessage(el->line_number)
+                << "<" << el->name << "> is missing attribute 'android:name'");
+    return false;
+  }
+
+  if (attr->value.empty()) {
+    diag->Error(DiagMessage(el->line_number)
+                << "attribute 'android:name' in <" << el->name << "> tag must not be empty");
+    return false;
+  }
+  return true;
+}
+
 // This is how PackageManager builds class names from AndroidManifest.xml entries.
 static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr,
                                 SourcePathDiagnostics* diag) {
@@ -59,21 +75,29 @@
 }
 
 static bool RequiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
-  if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) {
-    return NameIsJavaClassName(el, attr, diag);
+  xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
+  if (attr == nullptr) {
+    diag->Error(DiagMessage(el->line_number)
+                << "<" << el->name << "> is missing attribute 'android:name'");
+    return false;
   }
-  diag->Error(DiagMessage(el->line_number)
-              << "<" << el->name << "> is missing attribute 'android:name'");
-  return false;
+  return NameIsJavaClassName(el, attr, diag);
 }
 
 static bool RequiredNameIsJavaPackage(xml::Element* el, SourcePathDiagnostics* diag) {
-  if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) {
-    return util::IsJavaPackageName(attr->value);
+  xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
+  if (attr == nullptr) {
+    diag->Error(DiagMessage(el->line_number)
+                << "<" << el->name << "> is missing attribute 'android:name'");
+    return false;
   }
-  diag->Error(DiagMessage(el->line_number)
-              << "<" << el->name << "> is missing attribute 'android:name'");
-  return false;
+
+  if (!util::IsJavaPackageName(attr->value)) {
+    diag->Error(DiagMessage(el->line_number) << "attribute 'android:name' in <" << el->name
+                                             << "> tag must be a valid Java package name");
+    return false;
+  }
+  return true;
 }
 
 static xml::XmlNodeAction::ActionFuncWithDiag RequiredAndroidAttribute(const std::string& attr) {
@@ -213,8 +237,8 @@
 
   // Common <intent-filter> actions.
   xml::XmlNodeAction intent_filter_action;
-  intent_filter_action["action"];
-  intent_filter_action["category"];
+  intent_filter_action["action"].Action(RequiredNameIsNotEmpty);
+  intent_filter_action["category"].Action(RequiredNameIsNotEmpty);
   intent_filter_action["data"];
 
   // Common <meta-data> actions.
@@ -317,8 +341,8 @@
   xml::XmlNodeAction& application_action = manifest_action["application"];
   application_action.Action(OptionalNameIsJavaClassName);
 
-  application_action["uses-library"].Action(RequiredNameIsJavaPackage);
-  application_action["library"].Action(RequiredNameIsJavaPackage);
+  application_action["uses-library"].Action(RequiredNameIsNotEmpty);
+  application_action["library"].Action(RequiredNameIsNotEmpty);
 
   xml::XmlNodeAction& static_library_action = application_action["static-library"];
   static_library_action.Action(RequiredNameIsJavaPackage);
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index c6f895b..ed98d71 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -494,4 +494,34 @@
   ASSERT_THAT(manifest, IsNull());
 }
 
+
+TEST_F(ManifestFixerTest, UsesLibraryMustHaveNonEmptyName) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <application>
+          <uses-library android:name="" />
+        </application>
+      </manifest>)";
+  EXPECT_THAT(Verify(input), IsNull());
+
+  input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <application>
+          <uses-library />
+        </application>
+      </manifest>)";
+  EXPECT_THAT(Verify(input), IsNull());
+
+  input = R"(
+       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+           package="android">
+         <application>
+           <uses-library android:name="blahhh" />
+         </application>
+       </manifest>)";
+  EXPECT_THAT(Verify(input), NotNull());
+}
+
 }  // namespace aapt
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index 7650795..e7b269a 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -20,6 +20,7 @@
 #include <map>
 #include <set>
 #include <string>
+#include <sstream>
 
 using namespace android;
 using namespace android::os;
@@ -407,6 +408,9 @@
                 splitAndPrint(s.args());
                 printf(" NULL),\n");
                 break;
+            case SECTION_LOG:
+                printf("    new LogSection(%d, %s),\n", field->number(), s.args().c_str());
+                break;
         }
     }
     printf("    NULL };\n");
@@ -482,9 +486,44 @@
 }
 
 // ================================================================================
+static string replace_string(const string& str, const char replace, const char with)
+{
+    string result(str);
+    const int N = result.size();
+    for (int i=0; i<N; i++) {
+        if (result[i] == replace) {
+            result[i] = with;
+        }
+    }
+    return result;
+}
+
+static void generateCsv(Descriptor const* descriptor, const string& indent, set<string>* parents) {
+    DebugStringOptions options;
+    options.include_comments = true;
+    for (int i=0; i<descriptor->field_count(); i++) {
+        const FieldDescriptor* field = descriptor->field(i);
+        stringstream text;
+        if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
+            text << field->message_type()->name();
+        } else {
+            text << field->type_name();
+        }
+        text << " " << field->name();
+        printf("%s%s,\n", indent.c_str(), replace_string(text.str(), '\n', ' ').c_str());
+        if (field->type() == FieldDescriptor::TYPE_MESSAGE &&
+            parents->find(field->message_type()->full_name()) == parents->end()) {
+            parents->insert(field->message_type()->full_name());
+            generateCsv(field->message_type(), indent + ",", parents);
+            parents->erase(field->message_type()->full_name());
+        }
+    }
+}
+
+// ================================================================================
 int main(int argc, char const *argv[])
 {
-    if (argc != 2) return 1;
+    if (argc < 2) return 1;
     const char* module = argv[1];
 
     Descriptor const* descriptor = IncidentProto::descriptor();
@@ -495,7 +534,23 @@
     if (strcmp(module, "incidentd") == 0 ) {
         return !generateSectionListCpp(descriptor);
     }
-
-    // return failure if not called by the whitelisted modules
+    // Generates Csv Format of proto definition for each section.
+    if (strcmp(module, "csv") == 0 && argc > 2) {
+        int sectionId = atoi(argv[2]);
+        for (int i=0; i<descriptor->field_count(); i++) {
+            const FieldDescriptor* field = descriptor->field(i);
+            if (strcmp(field->name().c_str(), argv[2]) == 0
+                || field->number() == sectionId) {
+                set<string> parents;
+                printf("%s\n", field->name().c_str());
+                generateCsv(field->message_type(), "", &parents);
+                break;
+            }
+        }
+        // Returns failure if csv is enabled to prevent Android building with it.
+        // It doesn't matter if this command runs manually.
+        return 1;
+    }
+    // Returns failure if not called by the whitelisted modules
     return 1;
 }
diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp
new file mode 100644
index 0000000..00fb8aa
--- /dev/null
+++ b/tools/sdkparcelables/Android.bp
@@ -0,0 +1,22 @@
+java_binary_host {
+    name: "sdkparcelables",
+    manifest: "manifest.txt",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "asm-6.0",
+    ],
+}
+
+java_library_host {
+    name: "sdkparcelables_test",
+    manifest: "manifest.txt",
+    srcs: [
+        "tests/**/*.kt",
+    ],
+    static_libs: [
+        "sdkparcelables",
+        "junit",
+    ],
+}
diff --git a/tools/sdkparcelables/manifest.txt b/tools/sdkparcelables/manifest.txt
new file mode 100644
index 0000000..cd5420c
--- /dev/null
+++ b/tools/sdkparcelables/manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.sdkparcelables.MainKt
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt
new file mode 100644
index 0000000..f278aec
--- /dev/null
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt
@@ -0,0 +1,28 @@
+package com.android.sdkparcelables
+
+import org.objectweb.asm.ClassVisitor
+import java.util.*
+
+data class Ancestors(val superName: String?, val interfaces: List<String>?)
+
+/** A class that implements an ASM ClassVisitor that collects super class and
+ * implemented interfaces for each class that it visits.
+ */
+class AncestorCollector(api: Int, dest: ClassVisitor?) : ClassVisitor(api, dest) {
+    private val _ancestors = LinkedHashMap<String, Ancestors>()
+
+    val ancestors: Map<String, Ancestors>
+        get() = _ancestors
+
+    override fun visit(version: Int, access: Int, name: String?, signature: String?,
+                       superName: String?, interfaces: Array<out String>?) {
+        name!!
+
+        val old = _ancestors.put(name, Ancestors(superName, interfaces?.toList()))
+        if (old != null) {
+            throw RuntimeException("class $name already found")
+        }
+
+        super.visit(version, access, name, signature, superName, interfaces)
+    }
+}
\ No newline at end of file
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
new file mode 100644
index 0000000..3e9d92c
--- /dev/null
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
@@ -0,0 +1,56 @@
+package com.android.sdkparcelables
+
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.Opcodes
+import java.io.File
+import java.io.IOException
+import java.util.zip.ZipFile
+
+fun main(args: Array<String>) {
+    if (args.size != 2) {
+        usage()
+    }
+
+    val zipFileName = args[0]
+    val aidlFileName = args[1]
+
+    val zipFile: ZipFile
+
+    try {
+        zipFile = ZipFile(zipFileName)
+    } catch (e: IOException) {
+        System.err.println("error reading input jar: ${e.message}")
+        kotlin.system.exitProcess(2)
+    }
+
+    val ancestorCollector = AncestorCollector(Opcodes.ASM6, null)
+
+    for (entry in zipFile.entries()) {
+        if (entry.name.endsWith(".class")) {
+            val reader = ClassReader(zipFile.getInputStream(entry))
+            reader.accept(ancestorCollector,
+                    ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
+        }
+    }
+
+    val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorCollector.ancestors)
+
+    try {
+        val outFile = File(aidlFileName)
+        val outWriter = outFile.bufferedWriter()
+        for (parcelable in parcelables) {
+            outWriter.write("parcelable ")
+            outWriter.write(parcelable.replace('/', '.').replace('$', '.'))
+            outWriter.write(";\n")
+        }
+        outWriter.flush()
+    } catch (e: IOException) {
+        System.err.println("error writing output aidl: ${e.message}")
+        kotlin.system.exitProcess(2)
+    }
+}
+
+fun usage() {
+    System.err.println("Usage: <input jar> <output aidl>")
+    kotlin.system.exitProcess(1)
+}
\ No newline at end of file
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt
new file mode 100644
index 0000000..620f798
--- /dev/null
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt
@@ -0,0 +1,52 @@
+package com.android.sdkparcelables
+
+/** A class that uses an ancestor map to find all classes that
+ * implement android.os.Parcelable, including indirectly through
+ * super classes or super interfaces.
+ */
+class ParcelableDetector {
+    companion object {
+        fun ancestorsToParcelables(ancestors: Map<String, Ancestors>): List<String> {
+            val impl = Impl(ancestors)
+            impl.build()
+            return impl.parcelables
+        }
+    }
+
+    private class Impl(val ancestors: Map<String, Ancestors>) {
+        val isParcelableCache = HashMap<String, Boolean>()
+        val parcelables = ArrayList<String>()
+
+        fun build() {
+            val classList = ancestors.keys
+            classList.filterTo(parcelables, this::isParcelable)
+            parcelables.sort()
+        }
+
+        private fun isParcelable(c: String?): Boolean {
+            if (c == null) {
+                return false
+            }
+
+            if (c == "android/os/Parcelable") {
+                return true
+            }
+
+            val old = isParcelableCache[c]
+            if (old != null) {
+                return old
+            }
+
+            val cAncestors = ancestors[c] ?:
+                    throw RuntimeException("class $c missing ancestor information")
+
+            val seq = (cAncestors.interfaces?.asSequence() ?: emptySequence()) +
+                    cAncestors.superName
+
+            val ancestorIsParcelable = seq.any(this::isParcelable)
+
+            isParcelableCache[c] = ancestorIsParcelable
+            return ancestorIsParcelable
+        }
+    }
+}
diff --git a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt
new file mode 100644
index 0000000..edfc825
--- /dev/null
+++ b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt
@@ -0,0 +1,57 @@
+package com.android.sdkparcelables
+
+import junit.framework.TestCase.assertEquals
+import org.junit.Test
+
+class ParcelableDetectorTest {
+    @Test
+    fun `detect implements`() {
+        val ancestorMap = mapOf(
+                testAncestors("android/test/Parcelable",null, "android/os/Parcelable"),
+                testAncestors("android/os/Parcelable", null))
+
+        val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
+
+        assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable"))
+    }
+
+    @Test
+    fun `detect implements in reverse order`() {
+        val ancestorMap = mapOf(
+                testAncestors("android/os/Parcelable", null),
+                testAncestors("android/test/Parcelable",null, "android/os/Parcelable"))
+
+        val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
+
+        assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable"))
+    }
+
+    @Test
+    fun `detect super implements`() {
+        val ancestorMap = mapOf(
+                testAncestors("android/test/SuperParcelable",null, "android/os/Parcelable"),
+                testAncestors("android/test/Parcelable","android/test/SuperParcelable"),
+                testAncestors("android/os/Parcelable", null))
+
+        val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
+
+        assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable", "android/test/SuperParcelable"))
+    }
+
+    @Test
+    fun `detect super interface`() {
+        val ancestorMap = mapOf(
+                testAncestors("android/test/IParcelable",null, "android/os/Parcelable"),
+                testAncestors("android/test/Parcelable",null, "android/test/IParcelable"),
+                testAncestors("android/os/Parcelable", null))
+
+        val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
+
+        assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/IParcelable", "android/test/Parcelable"))
+    }
+
+}
+
+private fun testAncestors(name: String, superName: String?, vararg interfaces: String): Pair<String, Ancestors> {
+    return Pair(name, Ancestors(superName, interfaces.toList()))
+}
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index a25784d..80853b1 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -112,7 +112,7 @@
         case FieldDescriptor::TYPE_MESSAGE:
             // TODO: not the final package name
             if (field->message_type()->full_name() ==
-                "android.os.statsd.AttributionChain") {
+                "android.os.statsd.AttributionNode") {
               return JAVA_TYPE_ATTRIBUTION_CHAIN;
             } else {
                 return JAVA_TYPE_OBJECT;
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 5e93c08..bbe6d63 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -166,7 +166,15 @@
                     attributionDecl.fields.front().name.c_str());
                 fprintf(out, "        event.begin();\n");
                 for (const auto &chainField : attributionDecl.fields) {
-                    fprintf(out, "        event << %s[i];\n", chainField.name.c_str());
+                    if (chainField.javaType == JAVA_TYPE_STRING) {
+                        fprintf(out, "        if (%s[i] != NULL) {\n", chainField.name.c_str());
+                        fprintf(out, "           event << %s[i];\n", chainField.name.c_str());
+                        fprintf(out, "        } else {\n");
+                        fprintf(out, "           event << \"\";\n");
+                        fprintf(out, "        }\n");
+                    } else {
+                        fprintf(out, "        event << %s[i];\n", chainField.name.c_str());
+                    }
                 }
                 fprintf(out, "        event.end();\n");
                 fprintf(out, "    }\n");
@@ -589,13 +597,18 @@
                         fprintf(out, "        jstring jstr = "
                             "(jstring)env->GetObjectArrayElement(%s, i);\n",
                              chainField.name.c_str());
-                        fprintf(out, "        ScopedUtfChars* scoped_%s = "
+                        fprintf(out, "        if (jstr == NULL) {\n");
+                        fprintf(out, "            %s_vec.push_back(NULL);\n",
+                            chainField.name.c_str());
+                        fprintf(out, "        } else {\n");
+                        fprintf(out, "            ScopedUtfChars* scoped_%s = "
                             "new ScopedUtfChars(env, jstr);\n",
                              chainField.name.c_str());
-                        fprintf(out, "        %s_vec.push_back(scoped_%s->c_str());\n",
+                        fprintf(out, "            %s_vec.push_back(scoped_%s->c_str());\n",
                                 chainField.name.c_str(), chainField.name.c_str());
-                        fprintf(out, "        scoped_%s_vec.push_back(scoped_%s);\n",
+                        fprintf(out, "            scoped_%s_vec.push_back(scoped_%s);\n",
                                 chainField.name.c_str(), chainField.name.c_str());
+                        fprintf(out, "        }\n");
                         fprintf(out, "    }\n");
                     }
                     fprintf(out, "\n");
@@ -648,7 +661,7 @@
                         fprintf(out, "    env->ReleaseIntArrayElements(%s, %s_array, 0);\n",
                             chainField.name.c_str(), chainField.name.c_str());
                     } else if (chainField.javaType == JAVA_TYPE_STRING) {
-                        fprintf(out, "    for (size_t i = 0; i < %s_length; ++i) {\n",
+                        fprintf(out, "    for (size_t i = 0; i < scoped_%s_vec.size(); ++i) {\n",
                             chainField.name.c_str());
                         fprintf(out, "        delete scoped_%s_vec[i];\n", chainField.name.c_str());
                         fprintf(out, "    }\n");
@@ -769,7 +782,7 @@
 
     AtomDecl attributionDecl;
     vector<java_type_t> attributionSignature;
-    collate_atom(android::os::statsd::Attribution::descriptor(),
+    collate_atom(android::os::statsd::AttributionNode::descriptor(),
                  &attributionDecl, &attributionSignature);
 
     // Write the .cpp file
diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto
index 84b22cda..66cbee8 100644
--- a/tools/stats_log_api_gen/test.proto
+++ b/tools/stats_log_api_gen/test.proto
@@ -39,7 +39,7 @@
 }
 
 message AllTypesAtom {
-  optional android.os.statsd.AttributionChain attribution_chain = 1;
+  repeated android.os.statsd.AttributionNode attribution_chain = 1;
   optional double double_field = 2;
   optional float float_field = 3;
   optional int64 int64_field = 4;
@@ -99,12 +99,12 @@
     }
 }
 
-message BadAttributionChainPositionAtom {
+message BadAttributionNodePositionAtom {
   optional int32 field1 = 1;
-  optional android.os.statsd.AttributionChain attribution = 2;
+  repeated android.os.statsd.AttributionNode attribution = 2;
 }
 
-message BadAttributionChainPosition {
-  oneof event { BadAttributionChainPositionAtom bad = 1; }
+message BadAttributionNodePosition {
+  oneof event { BadAttributionNodePositionAtom bad = 1; }
 }
 
diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp
index b2b7298..9e22cd9 100644
--- a/tools/stats_log_api_gen/test_collation.cpp
+++ b/tools/stats_log_api_gen/test_collation.cpp
@@ -191,10 +191,10 @@
  * Test that atoms that have an attribution chain not in the first position are
  * rejected.
  */
-TEST(CollationTest, FailBadAttributionChainPosition) {
+TEST(CollationTest, FailBadAttributionNodePosition) {
   Atoms atoms;
   int errorCount =
-      collate_atoms(BadAttributionChainPosition::descriptor(), &atoms);
+      collate_atoms(BadAttributionNodePosition::descriptor(), &atoms);
 
   EXPECT_EQ(1, errorCount);
 }
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index b6ad926..eaad137 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -21,7 +21,9 @@
 import android.os.Parcelable;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Describes information about a detected access point. In addition
@@ -227,6 +229,50 @@
     public long seen;
 
     /**
+     * On devices with multiple hardware radio chains, this class provides metadata about
+     * each radio chain that was used to receive this scan result (probe response or beacon).
+     * {@hide}
+     */
+    public static class RadioChainInfo {
+        /** Vendor defined id for a radio chain. */
+        public int id;
+        /** Detected signal level in dBm (also known as the RSSI) on this radio chain. */
+        public int level;
+
+        @Override
+        public String toString() {
+            return "RadioChainInfo: id=" + id + ", level=" + level;
+        }
+
+        @Override
+        public boolean equals(Object otherObj) {
+            if (this == otherObj) {
+                return true;
+            }
+            if (!(otherObj instanceof RadioChainInfo)) {
+                return false;
+            }
+            RadioChainInfo other = (RadioChainInfo) otherObj;
+            return id == other.id && level == other.level;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(id, level);
+        }
+    };
+
+    /**
+     * Information about the list of the radio chains used to receive this scan result
+     * (probe response or beacon).
+     *
+     * For Example: On devices with 2 hardware radio chains, this list could hold 1 or 2
+     * entries based on whether this scan result was received using one or both the chains.
+     * {@hide}
+     */
+    public RadioChainInfo[] radioChainInfos;
+
+    /**
      * @hide
      * Update RSSI of the scan result
      * @param previousRssi
@@ -481,6 +527,7 @@
         this.isCarrierAp = false;
         this.carrierApEapType = UNSPECIFIED;
         this.carrierName = null;
+        this.radioChainInfos = null;
     }
 
     /** {@hide} */
@@ -502,6 +549,7 @@
         this.isCarrierAp = false;
         this.carrierApEapType = UNSPECIFIED;
         this.carrierName = null;
+        this.radioChainInfos = null;
     }
 
     /** {@hide} */
@@ -530,6 +578,7 @@
         this.isCarrierAp = false;
         this.carrierApEapType = UNSPECIFIED;
         this.carrierName = null;
+        this.radioChainInfos = null;
     }
 
     /** {@hide} */
@@ -572,6 +621,7 @@
             isCarrierAp = source.isCarrierAp;
             carrierApEapType = source.carrierApEapType;
             carrierName = source.carrierName;
+            radioChainInfos = source.radioChainInfos;
         }
     }
 
@@ -615,6 +665,7 @@
         sb.append(", Carrier AP: ").append(isCarrierAp ? "yes" : "no");
         sb.append(", Carrier AP EAP Type: ").append(carrierApEapType);
         sb.append(", Carrier name: ").append(carrierName);
+        sb.append(", Radio Chain Infos: ").append(Arrays.toString(radioChainInfos));
         return sb.toString();
     }
 
@@ -687,6 +738,16 @@
         dest.writeInt(isCarrierAp ? 1 : 0);
         dest.writeInt(carrierApEapType);
         dest.writeString(carrierName);
+
+        if (radioChainInfos != null) {
+            dest.writeInt(radioChainInfos.length);
+            for (int i = 0; i < radioChainInfos.length; i++) {
+                dest.writeInt(radioChainInfos[i].id);
+                dest.writeInt(radioChainInfos[i].level);
+            }
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -759,6 +820,15 @@
                 sr.isCarrierAp = in.readInt() != 0;
                 sr.carrierApEapType = in.readInt();
                 sr.carrierName = in.readString();
+                n = in.readInt();
+                if (n != 0) {
+                    sr.radioChainInfos = new RadioChainInfo[n];
+                    for (int i = 0; i < n; i++) {
+                        sr.radioChainInfos[i] = new RadioChainInfo();
+                        sr.radioChainInfos[i].id = in.readInt();
+                        sr.radioChainInfos[i].level = in.readInt();
+                    }
+                }
                 return sr;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index a158d94..267fd6f 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -695,11 +695,11 @@
      * representing if the scan was successful or not.
      * Scans may fail for multiple reasons, these may include:
      * <ol>
-     * <li>A non-privileged app requested too many scans in a certain period of time.
-     * This may lead to additional scan request rejections via "scan throttling".
-     * See
-     * <a href="https://developer.android.com/preview/features/background-location-limits.html">
-     * here</a> for details.
+     * <li>An app requested too many scans in a certain period of time.
+     * This may lead to additional scan request rejections via "scan throttling" for both
+     * foreground and background apps.
+     * Note: Apps holding android.Manifest.permission.NETWORK_SETTINGS permission are
+     * exempted from scan throttling.
      * </li>
      * <li>The device is idle and scanning is disabled.</li>
      * <li>Wifi hardware reported a scan failure.</li>
@@ -1596,7 +1596,10 @@
      * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).getScanResults()}</li>
      * </ol>
      * @return {@code true} if the operation succeeded, i.e., the scan was initiated.
+     * @deprecated The ability for apps to trigger scan requests will be removed in a future
+     * release.
      */
+    @Deprecated
     public boolean startScan() {
         return startScan(null);
     }
@@ -1615,53 +1618,6 @@
     }
 
     /**
-     * startLocationRestrictedScan()
-     * Trigger a scan which will not make use of DFS channels and is thus not suitable for
-     * establishing wifi connection.
-     * @deprecated This API is nolonger supported.
-     * Use {@link android.net.wifi.WifiScanner} API
-     * @hide
-     * @removed
-     */
-    @Deprecated
-    @SystemApi
-    @SuppressLint("Doclava125")
-    public boolean startLocationRestrictedScan(WorkSource workSource) {
-        return false;
-    }
-
-    /**
-     * Check if the Batched Scan feature is supported.
-     *
-     * @return false if not supported.
-     * @deprecated This API is nolonger supported.
-     * Use {@link android.net.wifi.WifiScanner} API
-     * @hide
-     * @removed
-     */
-    @Deprecated
-    @SystemApi
-    @SuppressLint("Doclava125")
-    public boolean isBatchedScanSupported() {
-        return false;
-    }
-
-    /**
-     * Retrieve the latest batched scan result.  This should be called immediately after
-     * {@link BATCHED_SCAN_RESULTS_AVAILABLE_ACTION} is received.
-     * @deprecated This API is nolonger supported.
-     * Use {@link android.net.wifi.WifiScanner} API
-     * @hide
-     * @removed
-     */
-    @Deprecated
-    @SystemApi
-    @SuppressLint("Doclava125")
-    public List<BatchedScanResult> getBatchedScanResults() {
-        return null;
-    }
-
-    /**
      * Creates a configuration token describing the current network of MIME type
      * application/vnd.wfa.wsc. Can be used to configure WiFi networks via NFC.
      *
@@ -2846,8 +2802,7 @@
      * gets added to the list of configured networks for the foreground user.
      *
      * For a new network, this function is used instead of a
-     * sequence of addNetwork(), enableNetwork(), saveConfiguration() and
-     * reconnect()
+     * sequence of addNetwork(), enableNetwork(), and reconnect()
      *
      * @param config the set of variables that describe the configuration,
      *            contained in a {@link WifiConfiguration} object.
@@ -2869,8 +2824,7 @@
     /**
      * Connect to a network with the given networkId.
      *
-     * This function is used instead of a enableNetwork(), saveConfiguration() and
-     * reconnect()
+     * This function is used instead of a enableNetwork() and reconnect()
      *
      * @param networkId the ID of the network as returned by {@link #addNetwork} or {@link
      *        getConfiguredNetworks}.
@@ -2890,10 +2844,12 @@
      * is updated. Any new network is enabled by default.
      *
      * For a new network, this function is used instead of a
-     * sequence of addNetwork(), enableNetwork() and saveConfiguration().
+     * sequence of addNetwork() and enableNetwork().
      *
      * For an existing network, it accomplishes the task of updateNetwork()
-     * and saveConfiguration()
+     *
+     * This API will cause reconnect if the crecdentials of the current active
+     * connection has been changed.
      *
      * @param config the set of variables that describe the configuration,
      *            contained in a {@link WifiConfiguration} object.
@@ -2912,7 +2868,6 @@
      * foreground user.
      *
      * This function is used instead of a sequence of removeNetwork()
-     * and saveConfiguration().
      *
      * @param config the set of variables that describe the configuration,
      *            contained in a {@link WifiConfiguration} object.
@@ -3124,7 +3079,7 @@
 
         public void setWorkSource(WorkSource ws) {
             synchronized (mBinder) {
-                if (ws != null && ws.size() == 0) {
+                if (ws != null && ws.isEmpty()) {
                     ws = null;
                 }
                 boolean changed = true;
@@ -3136,7 +3091,7 @@
                         changed = mWorkSource != null;
                         mWorkSource = new WorkSource(ws);
                     } else {
-                        changed = mWorkSource.diff(ws);
+                        changed = !mWorkSource.equals(ws);
                         if (changed) {
                             mWorkSource.set(ws);
                         }
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index e3752ac..928a1da 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -160,6 +160,24 @@
      */
     public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
 
+    /**
+     * This is used to indicate the purpose of the scan to the wifi chip in
+     * {@link ScanSettings#type}.
+     * On devices with multiple hardware radio chains (and hence different modes of scan),
+     * this type serves as an indication to the hardware on what mode of scan to perform.
+     * Only apps holding android.Manifest.permission.NETWORK_STACK permission can set this value.
+     *
+     * Note: This serves as an intent and not as a stipulation, the wifi chip
+     * might honor or ignore the indication based on the current radio conditions. Always
+     * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration used
+     * to receive the corresponding scan result.
+     */
+    /** {@hide} */
+    public static final int TYPE_LOW_LATENCY = 0;
+    /** {@hide} */
+    public static final int TYPE_LOW_POWER = 1;
+    /** {@hide} */
+    public static final int TYPE_HIGH_ACCURACY = 2;
 
     /** {@hide} */
     public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
@@ -193,7 +211,8 @@
          * list of hidden networks to scan for. Explicit probe requests are sent out for such
          * networks during scan. Only valid for single scan requests.
          * {@hide}
-         * */
+         */
+        @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
         public HiddenNetwork[] hiddenNetworks;
         /** period of background scan; in millisecond, 0 => single shot scan */
         public int periodInMs;
@@ -223,6 +242,13 @@
          * {@hide}
          */
         public boolean isPnoScan;
+        /**
+         * Indicate the type of scan to be performed by the wifi chip.
+         * Default value: {@link #TYPE_LOW_LATENCY}.
+         * {@hide}
+         */
+        @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+        public int type = TYPE_LOW_LATENCY;
 
         /** Implement the Parcelable interface {@hide} */
         public int describeContents() {
@@ -239,6 +265,7 @@
             dest.writeInt(maxPeriodInMs);
             dest.writeInt(stepCount);
             dest.writeInt(isPnoScan ? 1 : 0);
+            dest.writeInt(type);
             if (channels != null) {
                 dest.writeInt(channels.length);
                 for (int i = 0; i < channels.length; i++) {
@@ -272,6 +299,7 @@
                         settings.maxPeriodInMs = in.readInt();
                         settings.stepCount = in.readInt();
                         settings.isPnoScan = in.readInt() == 1;
+                        settings.type = in.readInt();
                         int num_channels = in.readInt();
                         settings.channels = new ChannelSpec[num_channels];
                         for (int i = 0; i < num_channels; i++) {
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java b/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
index aa2c268..2052f15 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
@@ -131,7 +131,6 @@
      *                    {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} discovery sessions this
      *                    is the subscriber's match filter.
      * @param distanceMm The measured distance to the Publisher in mm.
-     * @hide
      */
     public void onServiceDiscoveredWithinRange(PeerHandle peerHandle,
         byte[] serviceSpecificInfo, List<byte[]> matchFilter, int distanceMm) {
diff --git a/wifi/java/android/net/wifi/aware/PublishConfig.java b/wifi/java/android/net/wifi/aware/PublishConfig.java
index 7a5049d..7a0250b 100644
--- a/wifi/java/android/net/wifi/aware/PublishConfig.java
+++ b/wifi/java/android/net/wifi/aware/PublishConfig.java
@@ -376,8 +376,6 @@
          *
          * @return The builder to facilitate chaining
          *         {@code builder.setXXX(..).setXXX(..)}.
-         *
-         * @hide
          */
         public Builder setRangingEnabled(boolean enable) {
             mEnableRanging = enable;
diff --git a/wifi/java/android/net/wifi/aware/SubscribeConfig.java b/wifi/java/android/net/wifi/aware/SubscribeConfig.java
index 91f8e520..2eab76a 100644
--- a/wifi/java/android/net/wifi/aware/SubscribeConfig.java
+++ b/wifi/java/android/net/wifi/aware/SubscribeConfig.java
@@ -435,8 +435,6 @@
          *
          * @return The builder to facilitate chaining
          *         {@code builder.setXXX(..).setXXX(..)}.
-         *
-         * @hide
          */
         public Builder setMinDistanceMm(int minDistanceMm) {
             mMinDistanceMm = minDistanceMm;
@@ -466,8 +464,6 @@
          *
          * @return The builder to facilitate chaining
          *         {@code builder.setXXX(..).setXXX(..)}.
-         *
-         * @hide
          */
         public Builder setMaxDistanceMm(int maxDistanceMm) {
             mMaxDistanceMm = maxDistanceMm;
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index d57d152..2f0c316 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -269,6 +269,10 @@
                     + identityChangedListener);
         }
 
+        if (attachCallback == null) {
+            throw new IllegalArgumentException("Null callback provided");
+        }
+
         synchronized (mLock) {
             Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
 
@@ -300,6 +304,10 @@
             DiscoverySessionCallback callback) {
         if (VDBG) Log.v(TAG, "publish(): clientId=" + clientId + ", config=" + publishConfig);
 
+        if (callback == null) {
+            throw new IllegalArgumentException("Null callback provided");
+        }
+
         try {
             mService.publish(mContext.getOpPackageName(), clientId, publishConfig,
                     new WifiAwareDiscoverySessionCallbackProxy(this, looper, true, callback,
@@ -333,6 +341,10 @@
             }
         }
 
+        if (callback == null) {
+            throw new IllegalArgumentException("Null callback provided");
+        }
+
         try {
             mService.subscribe(mContext.getOpPackageName(), clientId, subscribeConfig,
                     new WifiAwareDiscoverySessionCallbackProxy(this, looper, false, callback,
diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
index 8b86cdd..2ea6e79 100644
--- a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
+++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -26,13 +26,49 @@
  */
 public abstract class ProvisioningCallback {
 
-   /**
+    /**
      * The reason code for Provisioning Failure due to connection failure to OSU AP.
      * @hide
      */
     public static final int OSU_FAILURE_AP_CONNECTION      = 1;
 
     /**
+     * The reason code for Provisioning Failure due to connection failure to OSU AP.
+     * @hide
+     */
+    public static final int OSU_FAILURE_SERVER_URL_INVALID = 2;
+
+    /**
+     * The reason code for Provisioning Failure due to connection failure to OSU AP.
+     * @hide
+     */
+    public static final int OSU_FAILURE_SERVER_CONNECTION  = 3;
+
+    /**
+     * The reason code for Provisioning Failure due to connection failure to OSU AP.
+     * @hide
+     */
+    public static final int OSU_FAILURE_SERVER_VALIDATION  = 4;
+
+    /**
+     * The reason code for Provisioning Failure due to connection failure to OSU AP.
+     * @hide
+     */
+    public static final int OSU_FAILURE_PROVIDER_VERIFICATION = 5;
+
+    /**
+     * The reason code for Provisioning Failure when a provisioning flow is aborted.
+     * @hide
+     */
+    public static final int OSU_FAILURE_PROVISIONING_ABORTED = 6;
+
+    /**
+     * The reason code for Provisioning Failure when a provisioning flow is aborted.
+     * @hide
+     */
+    public static final int OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7;
+
+    /**
      * The status code for Provisioning flow to indicate connecting to OSU AP
      * @hide
      */
@@ -45,6 +81,24 @@
     public static final int OSU_STATUS_AP_CONNECTED        = 2;
 
     /**
+     * The status code for Provisioning flow to indicate connecting to OSU AP
+     * @hide
+     */
+    public static final int OSU_STATUS_SERVER_CONNECTED    = 3;
+
+    /**
+     * The status code for Provisioning flow to indicate connecting to OSU AP
+     * @hide
+     */
+    public static final int OSU_STATUS_SERVER_VALIDATED    = 4;
+
+    /**
+     * The status code for Provisioning flow to indicate connecting to OSU AP
+     * @hide
+     */
+    public static final int OSU_STATUS_PROVIDER_VERIFIED   = 5;
+
+    /**
      * Provisioning status for OSU failure
      * @param status indicates error condition
      */
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
index b4e3097..32f21b9 100644
--- a/wifi/java/android/net/wifi/rtt/RangingRequest.java
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java
@@ -17,6 +17,7 @@
 package android.net.wifi.rtt;
 
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
 import android.net.wifi.aware.AttachCallback;
@@ -41,8 +42,6 @@
  * 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;
@@ -198,7 +197,7 @@
             return addResponder(ResponderConfig.fromWifiAwarePeerHandleWithDefaults(peerHandle));
         }
 
-        /*
+        /**
          * Add the Responder device specified by the {@link ResponderConfig} to the list of devices
          * with which to measure range. The total number of peers added to the request cannot exceed
          * the limit specified by {@link #getMaxPeers()}.
@@ -206,8 +205,9 @@
          * @param responder Information on the RTT Responder.
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          *
-         * @hide (SystemApi)
+         * @hide
          */
+        @SystemApi
         public Builder addResponder(@NonNull ResponderConfig responder) {
             if (responder == null) {
                 throw new IllegalArgumentException("Null Responder!");
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java
index a380fae..d5ca8f7 100644
--- a/wifi/java/android/net/wifi/rtt/RangingResult.java
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.MacAddress;
 import android.net.wifi.aware.PeerHandle;
 import android.os.Handler;
@@ -36,8 +37,6 @@
  * <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";
@@ -108,6 +107,7 @@
      * 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.
      */
+    @Nullable
     public MacAddress getMacAddress() {
         return mMac;
     }
@@ -119,7 +119,7 @@
      * <p>
      * Will return a {@code null} for results corresponding to requests issued using a MAC address.
      */
-    public PeerHandle getPeerHandle() {
+    @Nullable public PeerHandle getPeerHandle() {
         return mPeerHandle;
     }
 
@@ -182,13 +182,11 @@
         return mTimestamp;
     }
 
-    /** @hide */
     @Override
     public int describeContents() {
         return 0;
     }
 
-    /** @hide */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mStatus);
@@ -210,7 +208,6 @@
         dest.writeLong(mTimestamp);
     }
 
-    /** @hide */
     public static final Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
         @Override
         public RangingResult[] newArray(int size) {
diff --git a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
index c8aea3c..9639dc8 100644
--- a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
+++ b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
@@ -17,6 +17,7 @@
 package android.net.wifi.rtt;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.os.Handler;
 
 import java.lang.annotation.Retention;
@@ -31,8 +32,6 @@
  * 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 */
@@ -68,5 +67,5 @@
      *
      * @param results List of range measurements, one per requested device.
      */
-    public abstract void onRangingResults(List<RangingResult> results);
+    public abstract void onRangingResults(@NonNull List<RangingResult> results);
 }
diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
index 8be7782..fb723c5 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
 import android.net.wifi.aware.PeerHandle;
@@ -35,9 +36,10 @@
  * A Responder configuration may be constructed from a {@link ScanResult} or manually (with the
  * data obtained out-of-band from a peer).
  *
- * @hide (@SystemApi)
+ * @hide
  */
-public class ResponderConfig implements Parcelable {
+@SystemApi
+public final class ResponderConfig implements Parcelable {
     private static final int AWARE_BAND_2_DISCOVERY_CHANNEL = 2437;
 
     /** @hide */
@@ -122,15 +124,14 @@
     /**
      * The MAC address of the Responder. Will be null if a Wi-Fi Aware peer identifier (the
      * peerHandle field) ise used to identify the Responder.
-     * TODO: convert to MacAddress
      */
-    public MacAddress macAddress;
+    public final MacAddress macAddress;
 
     /**
      * The peer identifier of a Wi-Fi Aware Responder. Will be null if a MAC Address (the macAddress
      * field) is used to identify the Responder.
      */
-    public PeerHandle peerHandle;
+    public final PeerHandle peerHandle;
 
     /**
      * The device type of the Responder.
@@ -171,7 +172,7 @@
     public final int preamble;
 
     /**
-     * Constructs Responder configuration.
+     * Constructs Responder configuration, using a MAC address to identify the Responder.
      *
      * @param macAddress      The MAC address of the Responder.
      * @param responderType   The type of the responder device, specified using
@@ -210,7 +211,7 @@
     }
 
     /**
-     * Constructs Responder configuration.
+     * Constructs Responder configuration, using a Wi-Fi Aware PeerHandle to identify the Responder.
      *
      * @param peerHandle      The Wi-Fi Aware peer identifier of the Responder.
      * @param responderType   The type of the responder device, specified using
@@ -245,6 +246,45 @@
     }
 
     /**
+     * Constructs Responder configuration. This is an internal-only constructor which specifies both
+     * a MAC address and a Wi-Fi PeerHandle to identify the Responder.
+     *
+     * @param macAddress      The MAC address of the Responder.
+     * @param peerHandle      The Wi-Fi Aware peer identifier of the Responder.
+     * @param responderType   The type of the responder device, specified using
+     *                        {@link ResponderType}.
+     * @param supports80211mc Indicates whether the responder supports IEEE 802.11mc.
+     * @param channelWidth    Responder channel bandwidth, specified using {@link ChannelWidth}.
+     * @param frequency       The primary 20 MHz frequency (in MHz) of the channel of the Responder.
+     * @param centerFreq0     Not used if the {@code channelWidth} is 20 MHz. If the Responder uses
+     *                        40, 80 or 160 MHz, this is the center frequency (in MHz), if the
+     *                        Responder uses 80 + 80 MHz, this is the center frequency of the first
+     *                        segment (in MHz).
+     * @param centerFreq1     Only used if the {@code channelWidth} is 80 + 80 MHz. If the
+     *                        Responder
+     *                        uses 80 + 80 MHz, this is the center frequency of the second segment
+     *                        (in
+     *                        MHz).
+     * @param preamble        The preamble used by the Responder, specified using
+     *                        {@link PreambleType}.
+     * @hide
+     */
+    public ResponderConfig(@NonNull MacAddress macAddress, @NonNull PeerHandle peerHandle,
+            @ResponderType int responderType, boolean supports80211mc,
+            @ChannelWidth int channelWidth, int frequency, int centerFreq0, int centerFreq1,
+            @PreambleType int preamble) {
+        this.macAddress = macAddress;
+        this.peerHandle = peerHandle;
+        this.responderType = responderType;
+        this.supports80211mc = supports80211mc;
+        this.channelWidth = channelWidth;
+        this.frequency = frequency;
+        this.centerFreq0 = centerFreq0;
+        this.centerFreq1 = centerFreq1;
+        this.preamble = preamble;
+    }
+
+    /**
      * Creates a Responder configuration from a {@link ScanResult} corresponding to an Access
      * Point (AP), which can be obtained from {@link android.net.wifi.WifiManager#getScanResults()}.
      */
@@ -252,7 +292,7 @@
         MacAddress macAddress = MacAddress.fromString(scanResult.BSSID);
         int responderType = RESPONDER_AP;
         boolean supports80211mc = scanResult.is80211mcResponder();
-        int channelWidth = translcateScanResultChannelWidth(scanResult.channelWidth);
+        int channelWidth = translateScanResultChannelWidth(scanResult.channelWidth);
         int frequency = scanResult.frequency;
         int centerFreq0 = scanResult.centerFreq0;
         int centerFreq1 = scanResult.centerFreq1;
@@ -416,7 +456,7 @@
     }
 
     /** @hide */
-    static int translcateScanResultChannelWidth(int scanResultChannelWidth) {
+    static int translateScanResultChannelWidth(int scanResultChannelWidth) {
         switch (scanResultChannelWidth) {
             case ScanResult.CHANNEL_WIDTH_20MHZ:
                 return CHANNEL_WIDTH_20MHZ;
@@ -430,7 +470,7 @@
                 return CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
             default:
                 throw new IllegalArgumentException(
-                        "translcateScanResultChannelWidth: bad " + scanResultChannelWidth);
+                        "translateScanResultChannelWidth: bad " + scanResultChannelWidth);
         }
     }
 }
diff --git a/wifi/java/android/net/wifi/rtt/WifiRttManager.java b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
index b4c690f..ec6c46e 100644
--- a/wifi/java/android/net/wifi/rtt/WifiRttManager.java
+++ b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package android.net.wifi.rtt;
 
 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
@@ -5,6 +21,7 @@
 import static android.Manifest.permission.CHANGE_WIFI_STATE;
 import static android.Manifest.permission.LOCATION_HARDWARE;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
@@ -38,8 +55,6 @@
  *     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_RTT_RANGING_SERVICE)
 public class WifiRttManager {
@@ -71,6 +86,8 @@
      * 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.
+     * <p>Note: availability of RTT does not mean that the app can use the API. The app's
+     * permissions and platform Location Mode are validated at run-time.
      *
      * @return A boolean indicating whether the app can use the RTT API at this time (true) or
      * not (false).
@@ -95,8 +112,8 @@
      *                 will be used.
      */
     @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
-    public void startRanging(RangingRequest request, RangingResultCallback callback,
-            @Nullable Handler handler) {
+    public void startRanging(@NonNull RangingRequest request,
+            @NonNull RangingResultCallback callback, @Nullable Handler handler) {
         startRanging(null, request, callback, handler);
     }
 
@@ -112,17 +129,22 @@
      *                 callback} object. If a null is provided then the application's main thread
      *                 will be used.
      *
-     * @hide (@SystemApi)
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE,
             ACCESS_WIFI_STATE})
-    public void startRanging(@Nullable WorkSource workSource, RangingRequest request,
-            RangingResultCallback callback, @Nullable Handler handler) {
+    public void startRanging(@Nullable WorkSource workSource, @NonNull RangingRequest request,
+            @NonNull RangingResultCallback callback, @Nullable Handler handler) {
         if (VDBG) {
             Log.v(TAG, "startRanging: workSource=" + workSource + ", request=" + request
                     + ", callback=" + callback + ", handler=" + handler);
         }
 
+        if (callback == null) {
+            throw new IllegalArgumentException("Null callback provided");
+        }
+
         Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
         Binder binder = new Binder();
         try {
@@ -139,10 +161,11 @@
      *
      * @param workSource The work-sources of the requesters.
      *
-     * @hide (@SystemApi)
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(allOf = {LOCATION_HARDWARE})
-    public void cancelRanging(WorkSource workSource) {
+    public void cancelRanging(@Nullable WorkSource workSource) {
         if (VDBG) {
             Log.v(TAG, "cancelRanging: workSource=" + workSource);
         }
diff --git a/wifi/java/android/net/wifi/rtt/package.html b/wifi/java/android/net/wifi/rtt/package.html
index a0d407a..11ac058 100644
--- a/wifi/java/android/net/wifi/rtt/package.html
+++ b/wifi/java/android/net/wifi/rtt/package.html
@@ -13,6 +13,8 @@
     <li>{@link android.Manifest.permission#CHANGE_WIFI_STATE}</li>
     <li>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</li>
 </ul>
+<p>Usage of the API is also gated by the device's Location Mode: whether it permits Wi-Fi based
+location to be queried.</p>
 
 <p class="note"><strong>Note:</strong> Not all Android-powered devices support Wi-Fi RTT
     functionality.
diff --git a/wifi/tests/src/android/net/wifi/ScanResultTest.java b/wifi/tests/src/android/net/wifi/ScanResultTest.java
new file mode 100644
index 0000000..689ebba
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/ScanResultTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.validateMockitoUsage;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.net.wifi.WifiScanner.ScanSettings;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiScanner}.
+ */
+@SmallTest
+public class ScanResultTest {
+    public static final String TEST_SSID = "\"test_ssid\"";
+    public static final String TEST_BSSID = "04:ac:fe:45:34:10";
+    public static final String TEST_CAPS = "CCMP";
+    public static final int TEST_LEVEL = -56;
+    public static final int TEST_FREQUENCY = 2412;
+    public static final long TEST_TSF = 04660l;
+
+    /**
+     * Setup before tests.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Clean up after tests.
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    /**
+     * Verify parcel read/write for ScanResult.
+     */
+    @Test
+    public void verifyScanResultParcelWithoutRadioChainInfo() throws Exception {
+        ScanResult writeScanResult = createScanResult();
+        ScanResult readScanResult = parcelReadWrite(writeScanResult);
+        assertScanResultEquals(writeScanResult, readScanResult);
+    }
+
+    /**
+     * Verify parcel read/write for ScanResult.
+     */
+    @Test
+    public void verifyScanResultParcelWithZeroRadioChainInfo() throws Exception {
+        ScanResult writeScanResult = createScanResult();
+        writeScanResult.radioChainInfos = new ScanResult.RadioChainInfo[0];
+        ScanResult readScanResult = parcelReadWrite(writeScanResult);
+        assertNull(readScanResult.radioChainInfos);
+    }
+
+    /**
+     * Verify parcel read/write for ScanResult.
+     */
+    @Test
+    public void verifyScanResultParcelWithRadioChainInfo() throws Exception {
+        ScanResult writeScanResult = createScanResult();
+        writeScanResult.radioChainInfos = new ScanResult.RadioChainInfo[2];
+        writeScanResult.radioChainInfos[0] = new ScanResult.RadioChainInfo();
+        writeScanResult.radioChainInfos[0].id = 0;
+        writeScanResult.radioChainInfos[0].level = -45;
+        writeScanResult.radioChainInfos[1] = new ScanResult.RadioChainInfo();
+        writeScanResult.radioChainInfos[1].id = 1;
+        writeScanResult.radioChainInfos[1].level = -54;
+        ScanResult readScanResult = parcelReadWrite(writeScanResult);
+        assertScanResultEquals(writeScanResult, readScanResult);
+    }
+
+    /**
+     * Verify copy constructor for ScanResult.
+     */
+    @Test
+    public void verifyScanResultCopyWithoutRadioChainInfo() throws Exception {
+        ScanResult scanResult = createScanResult();
+        ScanResult copyScanResult = new ScanResult(scanResult);
+        assertScanResultEquals(scanResult, copyScanResult);
+    }
+
+    /**
+     * Verify copy constructor for ScanResult.
+     */
+    @Test
+    public void verifyScanResultCopyWithRadioChainInfo() throws Exception {
+        ScanResult scanResult = createScanResult();
+        scanResult.radioChainInfos = new ScanResult.RadioChainInfo[2];
+        scanResult.radioChainInfos[0] = new ScanResult.RadioChainInfo();
+        scanResult.radioChainInfos[0].id = 0;
+        scanResult.radioChainInfos[0].level = -45;
+        scanResult.radioChainInfos[1] = new ScanResult.RadioChainInfo();
+        scanResult.radioChainInfos[1].id = 1;
+        scanResult.radioChainInfos[1].level = -54;
+        ScanResult copyScanResult = new ScanResult(scanResult);
+        assertScanResultEquals(scanResult, copyScanResult);
+    }
+
+    /**
+     * Verify toString for ScanResult.
+     */
+    @Test
+    public void verifyScanResultToStringWithoutRadioChainInfo() throws Exception {
+        ScanResult scanResult = createScanResult();
+        assertEquals("SSID: \"test_ssid\", BSSID: 04:ac:fe:45:34:10, capabilities: CCMP, " +
+                "level: -56, frequency: 2412, timestamp: 2480, distance: 0(cm), distanceSd: 0(cm), " +
+                "passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, " +
+                "80211mcResponder: is not supported, Carrier AP: no, " +
+                "Carrier AP EAP Type: 0, Carrier name: null, " +
+                "Radio Chain Infos: null", scanResult.toString());
+    }
+
+    /**
+     * Verify toString for ScanResult.
+     */
+    @Test
+    public void verifyScanResultToStringWithRadioChainInfo() throws Exception {
+        ScanResult scanResult = createScanResult();
+        scanResult.radioChainInfos = new ScanResult.RadioChainInfo[2];
+        scanResult.radioChainInfos[0] = new ScanResult.RadioChainInfo();
+        scanResult.radioChainInfos[0].id = 0;
+        scanResult.radioChainInfos[0].level = -45;
+        scanResult.radioChainInfos[1] = new ScanResult.RadioChainInfo();
+        scanResult.radioChainInfos[1].id = 1;
+        scanResult.radioChainInfos[1].level = -54;
+        assertEquals("SSID: \"test_ssid\", BSSID: 04:ac:fe:45:34:10, capabilities: CCMP, " +
+                "level: -56, frequency: 2412, timestamp: 2480, distance: 0(cm), distanceSd: 0(cm), " +
+                "passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, " +
+                "80211mcResponder: is not supported, Carrier AP: no, " +
+                "Carrier AP EAP Type: 0, Carrier name: null, " +
+                "Radio Chain Infos: [RadioChainInfo: id=0, level=-45, " +
+                "RadioChainInfo: id=1, level=-54]", scanResult.toString());
+    }
+
+    /**
+     * Write the provided {@link ScanResult} to a parcel and deserialize it.
+     */
+    private static ScanResult parcelReadWrite(ScanResult writeResult) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        writeResult.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        return ScanResult.CREATOR.createFromParcel(parcel);
+    }
+
+    private static ScanResult createScanResult() {
+        ScanResult result = new ScanResult();
+        result.wifiSsid = WifiSsid.createFromAsciiEncoded(TEST_SSID);
+        result.BSSID = TEST_BSSID;
+        result.capabilities = TEST_CAPS;
+        result.level = TEST_LEVEL;
+        result.frequency = TEST_FREQUENCY;
+        result.timestamp = TEST_TSF;
+        return result;
+    }
+
+    private static void assertScanResultEquals(ScanResult expected, ScanResult actual) {
+        assertEquals(expected.SSID, actual.SSID);
+        assertEquals(expected.BSSID, actual.BSSID);
+        assertEquals(expected.capabilities, actual.capabilities);
+        assertEquals(expected.level, actual.level);
+        assertEquals(expected.frequency, actual.frequency);
+        assertEquals(expected.timestamp, actual.timestamp);
+        assertArrayEquals(expected.radioChainInfos, actual.radioChainInfos);
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index e542789..a4366d4 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -16,19 +16,23 @@
 
 package android.net.wifi;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.validateMockitoUsage;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.os.Handler;
+import android.os.Parcel;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.net.wifi.WifiScanner.ScanSettings;
 
 import com.android.internal.util.test.BidirectionalAsyncChannelServer;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -70,4 +74,50 @@
         validateMockitoUsage();
     }
 
+    /**
+     * Verify parcel read/write for ScanSettings.
+     */
+    @Test
+    public void verifyScanSettingsParcelWithBand() throws Exception {
+        ScanSettings writeSettings = new ScanSettings();
+        writeSettings.type = WifiScanner.TYPE_LOW_POWER;
+        writeSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
+
+        ScanSettings readSettings = parcelWriteRead(writeSettings);
+        assertEquals(readSettings.type, writeSettings.type);
+        assertEquals(readSettings.band, writeSettings.band);
+        assertEquals(0, readSettings.channels.length);
+    }
+
+    /**
+     * Verify parcel read/write for ScanSettings.
+     */
+    @Test
+    public void verifyScanSettingsParcelWithChannels() throws Exception {
+        ScanSettings writeSettings = new ScanSettings();
+        writeSettings.type = WifiScanner.TYPE_HIGH_ACCURACY;
+        writeSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
+        writeSettings.channels = new WifiScanner.ChannelSpec[] {
+                new WifiScanner.ChannelSpec(5),
+                new WifiScanner.ChannelSpec(7)
+        };
+
+        ScanSettings readSettings = parcelWriteRead(writeSettings);
+        assertEquals(writeSettings.type, readSettings.type);
+        assertEquals(writeSettings.band, readSettings.band);
+        assertEquals(2, readSettings.channels.length);
+        assertEquals(5, readSettings.channels[0].frequency);
+        assertEquals(7, readSettings.channels[1].frequency);
+    }
+
+    /**
+     * Write the provided {@link ScanSettings} to a parcel and deserialize it.
+     */
+    private static ScanSettings parcelWriteRead(ScanSettings writeSettings) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        writeSettings.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        return ScanSettings.CREATOR.createFromParcel(parcel);
+    }
+
 }