Merge "Set SAM To Disabled On HdmiControl Disabled"
diff --git a/Android.bp b/Android.bp
index a044555..34bd122 100644
--- a/Android.bp
+++ b/Android.bp
@@ -25,667 +25,223 @@
 //
 // READ ME: ########################################################
 
-java_defaults {
-    name: "framework-defaults",
-    installable: true,
-
+filegroup {
+    name: "framework-core-sources",
     srcs: [
-        // From build/make/core/pathmap.mk FRAMEWORK_BASE_SUBDIRS
         "core/java/**/*.java",
-        "graphics/java/**/*.java",
-        "location/java/**/*.java",
-        "lowpan/java/**/*.java",
-        "media/java/**/*.java",
-        "media/mca/effect/java/**/*.java",
-        "media/mca/filterfw/java/**/*.java",
-        "media/mca/filterpacks/java/**/*.java",
+        "core/java/**/*.aidl",
+    ],
+    path: "core/java",
+}
+
+filegroup {
+    name: "framework-drm-sources",
+    srcs: [
         "drm/java/**/*.java",
-        "opengl/java/**/*.java",
-        "sax/java/**/*.java",
-        "telecomm/java/**/*.java",
-        "telephony/java/**/*.java",
-        "wifi/java/**/*.java",
+    ],
+    path: "drm/java",
+}
+
+filegroup {
+    name: "framework-graphics-sources",
+    srcs: [
+        "graphics/java/**/*.java",
+        "graphics/java/**/*.aidl",
+    ],
+    path: "graphics/java",
+}
+
+filegroup {
+    name: "framework-keystore-sources",
+    srcs: [
         "keystore/java/**/*.java",
+        "keystore/java/**/*.aidl",
+    ],
+    path: "keystore/java",
+}
+
+filegroup {
+    name: "framework-location-sources",
+    srcs: [
+        "location/java/**/*.java",
+        "location/java/**/*.aidl",
+    ],
+    path: "location/java",
+}
+
+filegroup {
+    name: "framework-lowpan-sources",
+    srcs: [
+        "lowpan/java/**/*.java",
+        "lowpan/java/**/*.aidl",
+    ],
+    path: "lowpan/java",
+}
+
+filegroup {
+    name: "framework-media-sources",
+    srcs: [
+        "media/java/**/*.java",
+        "media/java/**/*.aidl",
+    ],
+    path: "media/java",
+}
+
+filegroup {
+    name: "framework-mca-effect-sources",
+    srcs: [
+        "media/mca/effect/java/**/*.java",
+    ],
+    path: "media/mca/effect/java",
+}
+
+filegroup {
+    name: "framework-mca-filterfw-sources",
+    srcs: [
+        "media/mca/filterfw/java/**/*.java",
+    ],
+    path: "media/mca/filterfw/java",
+}
+
+filegroup {
+    name: "framework-mca-filterpacks-sources",
+    srcs: [
+        "media/mca/filterpacks/java/**/*.java",
+    ],
+    path: "media/mca/filterpacks/java",
+}
+
+filegroup {
+    name: "framework-opengl-sources",
+    srcs: [
+        "opengl/java/**/*.java",
+    ],
+    path: "opengl/java",
+}
+
+filegroup {
+    name: "framework-rs-sources",
+    srcs: [
         "rs/java/**/*.java",
+    ],
+    path: "rs/java",
+}
 
-        ":framework-javastream-protos",
+filegroup {
+    name: "framework-sax-sources",
+    srcs: [
+        "sax/java/**/*.java",
+    ],
+    path: "sax/java",
+}
 
-        "core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl",
-        "core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl",
-        "core/java/android/accounts/IAccountManager.aidl",
-        "core/java/android/accounts/IAccountManagerResponse.aidl",
-        "core/java/android/accounts/IAccountAuthenticator.aidl",
-        "core/java/android/accounts/IAccountAuthenticatorResponse.aidl",
-        "core/java/android/app/IActivityController.aidl",
-        "core/java/android/app/IActivityManager.aidl",
-        "core/java/android/app/IActivityPendingResult.aidl",
-        "core/java/android/app/IAlarmCompleteListener.aidl",
-        "core/java/android/app/IAlarmListener.aidl",
-        "core/java/android/app/IAlarmManager.aidl",
-        "core/java/android/app/IAppTask.aidl",
-        "core/java/android/app/IApplicationThread.aidl",
-        "core/java/android/app/IAssistDataReceiver.aidl",
-        "core/java/android/app/ITaskStackListener.aidl",
-        "core/java/android/app/IBackupAgent.aidl",
-        "core/java/android/app/IEphemeralResolver.aidl",
-        "core/java/android/app/IInputForwarder.aidl",
-        "core/java/android/app/IInstantAppResolver.aidl",
-        "core/java/android/app/IInstrumentationWatcher.aidl",
-        "core/java/android/app/INotificationManager.aidl",
-        "core/java/android/app/IProcessObserver.aidl",
-        "core/java/android/app/ISearchManager.aidl",
-        "core/java/android/app/ISearchManagerCallback.aidl",
-        "core/java/android/app/IServiceConnection.aidl",
-        "core/java/android/app/IStopUserCallback.aidl",
-        "core/java/android/app/job/IJobCallback.aidl",
-        "core/java/android/app/job/IJobScheduler.aidl",
-        "core/java/android/app/job/IJobService.aidl",
-        "core/java/android/app/ITransientNotification.aidl",
-        "core/java/android/app/IUidObserver.aidl",
-        "core/java/android/app/IUiAutomationConnection.aidl",
-        "core/java/android/app/IUiModeManager.aidl",
-        "core/java/android/app/IUserSwitchObserver.aidl",
-        "core/java/android/app/IWallpaperManager.aidl",
-        "core/java/android/app/IWallpaperManagerCallback.aidl",
-        "core/java/android/app/admin/IDeviceAdminService.aidl",
-        "core/java/android/app/admin/IDevicePolicyManager.aidl",
-        "core/java/android/app/trust/IStrongAuthTracker.aidl",
-        "core/java/android/app/trust/ITrustManager.aidl",
-        "core/java/android/app/trust/ITrustListener.aidl",
-        "core/java/android/app/backup/IBackupManager.aidl",
-        "core/java/android/app/backup/IBackupObserver.aidl",
-        "core/java/android/app/backup/IBackupManagerMonitor.aidl",
-        "core/java/android/app/backup/IFullBackupRestoreObserver.aidl",
-        "core/java/android/app/backup/IRestoreObserver.aidl",
-        "core/java/android/app/backup/IRestoreSession.aidl",
-        "core/java/android/app/backup/ISelectBackupTransportCallback.aidl",
-        "core/java/android/app/slice/ISliceManager.aidl",
-        "core/java/android/app/slice/ISliceListener.aidl",
-        "core/java/android/app/timedetector/ITimeDetectorService.aidl",
-        "core/java/android/app/timezone/ICallback.aidl",
-        "core/java/android/app/timezone/IRulesManager.aidl",
-        "core/java/android/app/usage/ICacheQuotaService.aidl",
-        "core/java/android/app/usage/IStorageStatsManager.aidl",
-        "core/java/android/app/usage/IUsageStatsManager.aidl",
-        ":libbluetooth-binder-aidl",
-        "core/java/android/content/IClipboard.aidl",
-        "core/java/android/content/IContentService.aidl",
-        "core/java/android/content/IIntentReceiver.aidl",
-        "core/java/android/content/IIntentSender.aidl",
-        "core/java/android/content/IOnPrimaryClipChangedListener.aidl",
-        "core/java/android/content/IRestrictionsManager.aidl",
-        "core/java/android/content/ISyncAdapter.aidl",
-        "core/java/android/content/ISyncAdapterUnsyncableAccountCallback.aidl",
-        "core/java/android/content/ISyncContext.aidl",
-        "core/java/android/content/ISyncServiceAdapter.aidl",
-        "core/java/android/content/ISyncStatusObserver.aidl",
-        "core/java/android/content/om/IOverlayManager.aidl",
-        "core/java/android/content/pm/ICrossProfileApps.aidl",
-        "core/java/android/content/pm/IDexModuleRegisterCallback.aidl",
-        "core/java/android/content/pm/ILauncherApps.aidl",
-        "core/java/android/content/pm/IOnAppsChangedListener.aidl",
-        "core/java/android/content/pm/IOnPermissionsChangeListener.aidl",
-        "core/java/android/content/pm/IOtaDexopt.aidl",
-        "core/java/android/content/pm/IPackageDataObserver.aidl",
-        "core/java/android/content/pm/IPackageDeleteObserver.aidl",
-        "core/java/android/content/pm/IPackageDeleteObserver2.aidl",
-        "core/java/android/content/pm/IPackageInstallObserver2.aidl",
-        "core/java/android/content/pm/IPackageInstaller.aidl",
-        "core/java/android/content/pm/IPackageInstallerCallback.aidl",
-        "core/java/android/content/pm/IPackageInstallerSession.aidl",
-        "core/java/android/content/pm/IPackageManager.aidl",
+filegroup {
+    name: "framework-telecomm-sources",
+    srcs: [
+        "telecomm/java/**/*.java",
+        "telecomm/java/**/*.aidl",
+    ],
+    path: "telecomm/java",
+}
+
+filegroup {
+    name: "framework-telephony-sources",
+    srcs: [
+        "telephony/java/**/*.java",
+        "telephony/java/**/*.aidl",
+    ],
+    path: "telephony/java",
+}
+
+filegroup {
+    name: "framework-wifi-sources",
+    srcs: [
+        "wifi/java/**/*.java",
+        "wifi/java/**/*.aidl",
+    ],
+    path: "wifi/java",
+}
+
+filegroup {
+    name: "framework-srcs",
+    srcs: [
+        // Java/AIDL sources under frameworks/base
+        ":framework-core-sources",
+        ":framework-drm-sources",
+        ":framework-graphics-sources",
+        ":framework-keystore-sources",
+        ":framework-location-sources",
+        ":framework-lowpan-sources",
+        ":framework-media-sources",
+        ":framework-mca-effect-sources",
+        ":framework-mca-filterfw-sources",
+        ":framework-mca-filterpacks-sources",
+        ":framework-opengl-sources",
+        ":framework-rs-sources",
+        ":framework-sax-sources",
+        ":framework-telecomm-sources",
+        ":framework-telephony-sources",
+        ":framework-wifi-sources",
+        ":PacProcessor-aidl-sources",
+        ":ProxyHandler-aidl-sources",
+
+        // AIDL sources from external directories
+        ":dumpstate_aidl",
+        ":framework_native_aidl",
+        ":gatekeeper_aidl",
+        ":gsiservice_aidl",
+        ":incidentcompanion_aidl",
+        ":installd_aidl",
+        ":keystore_aidl",
+        ":libaudioclient_aidl",
         ":libbinder_aidl",
-        "core/java/android/content/pm/IPackageMoveObserver.aidl",
-        "core/java/android/content/pm/IPackageStatsObserver.aidl",
-        "core/java/android/content/pm/IPinItemRequest.aidl",
-        "core/java/android/content/pm/IShortcutService.aidl",
-        "core/java/android/content/pm/dex/IArtManager.aidl",
-        "core/java/android/content/pm/dex/ISnapshotRuntimeProfileCallback.aidl",
-        "core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl",
-        "core/java/android/database/IContentObserver.aidl",
+        ":libbluetooth-binder-aidl",
         ":libcamera_client_aidl",
         ":libcamera_client_framework_aidl",
-        "core/java/android/hardware/IConsumerIrService.aidl",
-        "core/java/android/hardware/ISerialManager.aidl",
-        "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl",
-        "core/java/android/hardware/display/IDisplayManager.aidl",
-        "core/java/android/hardware/display/IDisplayManagerCallback.aidl",
-        "core/java/android/hardware/display/IVirtualDisplayCallback.aidl",
-        "core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl",
-        "core/java/android/hardware/fingerprint/IFingerprintService.aidl",
-        "core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl",
-        "core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl",
-        "core/java/android/hardware/hdmi/IHdmiControlCallback.aidl",
-        "core/java/android/hardware/hdmi/IHdmiControlService.aidl",
-        "core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl",
-        "core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl",
-        "core/java/android/hardware/hdmi/IHdmiInputChangeListener.aidl",
-        "core/java/android/hardware/hdmi/IHdmiMhlVendorCommandListener.aidl",
-        "core/java/android/hardware/hdmi/IHdmiRecordListener.aidl",
-        "core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl",
-        "core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl",
-        "core/java/android/hardware/input/IInputManager.aidl",
-        "core/java/android/hardware/input/IInputDevicesChangedListener.aidl",
-        "core/java/android/hardware/input/ITabletModeChangedListener.aidl",
-        "core/java/android/hardware/location/IActivityRecognitionHardware.aidl",
-        "core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl",
-        "core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl",
-        "core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl",
-        "core/java/android/hardware/location/IGeofenceHardware.aidl",
-        "core/java/android/hardware/location/IGeofenceHardwareCallback.aidl",
-        "core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl",
-        "core/java/android/hardware/location/IContextHubCallback.aidl",
-        "core/java/android/hardware/location/IContextHubClient.aidl",
-        "core/java/android/hardware/location/IContextHubClientCallback.aidl",
-        "core/java/android/hardware/location/IContextHubService.aidl",
-        "core/java/android/hardware/location/IContextHubTransactionCallback.aidl",
-        "core/java/android/hardware/radio/IAnnouncementListener.aidl",
-        "core/java/android/hardware/radio/ICloseHandle.aidl",
-        "core/java/android/hardware/radio/IRadioService.aidl",
-        "core/java/android/hardware/radio/ITuner.aidl",
-        "core/java/android/hardware/radio/ITunerCallback.aidl",
-        "core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl",
-        "core/java/android/hardware/usb/IUsbManager.aidl",
-        "core/java/android/net/ICaptivePortal.aidl",
-        "core/java/android/net/IConnectivityManager.aidl",
-        "core/java/android/net/IIpConnectivityMetrics.aidl",
-        "core/java/android/net/IEthernetManager.aidl",
-        "core/java/android/net/IEthernetServiceListener.aidl",
-        "core/java/android/net/INetdEventCallback.aidl",
-        "core/java/android/net/IIpSecService.aidl",
-        "core/java/android/net/INetworkManagementEventObserver.aidl",
-        "core/java/android/net/INetworkPolicyListener.aidl",
-        "core/java/android/net/INetworkPolicyManager.aidl",
-        "core/java/android/net/INetworkRecommendationProvider.aidl",
-        "core/java/android/net/INetworkScoreCache.aidl",
-        "core/java/android/net/INetworkScoreService.aidl",
-        "core/java/android/net/INetworkStatsService.aidl",
-        "core/java/android/net/INetworkStatsSession.aidl",
-        "core/java/android/net/ISocketKeepaliveCallback.aidl",
-        "core/java/android/net/ITestNetworkManager.aidl",
-        "core/java/android/net/ITetheringEventCallback.aidl",
-        "core/java/android/net/ITetheringStatsProvider.aidl",
-        "core/java/android/net/nsd/INsdManager.aidl",
-        "core/java/android/nfc/IAppCallback.aidl",
-        "core/java/android/nfc/INfcAdapter.aidl",
-        "core/java/android/nfc/INfcAdapterExtras.aidl",
-        "core/java/android/nfc/INfcTag.aidl",
-        "core/java/android/nfc/INfcCardEmulation.aidl",
-        "core/java/android/nfc/INfcFCardEmulation.aidl",
-        "core/java/android/nfc/INfcUnlockHandler.aidl",
-        "core/java/android/nfc/INfcDta.aidl",
-        "core/java/android/nfc/ITagRemovedCallback.aidl",
-        "core/java/android/se/omapi/ISecureElementService.aidl",
-        "core/java/android/se/omapi/ISecureElementListener.aidl",
-        "core/java/android/se/omapi/ISecureElementChannel.aidl",
-        "core/java/android/se/omapi/ISecureElementReader.aidl",
-        "core/java/android/se/omapi/ISecureElementSession.aidl",
-        "core/java/android/os/IBatteryPropertiesRegistrar.aidl",
-        "core/java/android/os/ICancellationSignal.aidl",
-        "core/java/android/os/IDeviceIdentifiersPolicyService.aidl",
-        "core/java/android/os/IDeviceIdleController.aidl",
-        "core/java/android/os/IDynamicAndroidService.aidl",
-        "core/java/android/os/IHardwarePropertiesManager.aidl",
-        ":libincident_aidl",
-        "core/java/android/os/IMaintenanceActivityListener.aidl",
-        "core/java/android/os/IMessenger.aidl",
-        "core/java/android/os/INetworkActivityListener.aidl",
-        "core/java/android/os/INetworkManagementService.aidl",
-        "core/java/android/os/IPermissionController.aidl",
-        "core/java/android/os/IProcessInfoService.aidl",
-        "core/java/android/os/IProgressListener.aidl",
-        "core/java/android/os/IPowerManager.aidl",
-        "core/java/android/os/IRecoverySystem.aidl",
-        "core/java/android/os/IRecoverySystemProgressListener.aidl",
-        "core/java/android/os/IRemoteCallback.aidl",
-        "core/java/android/os/ISchedulingPolicyService.aidl",
-        ":statsd_aidl",
-        "core/java/android/os/ISystemUpdateManager.aidl",
-        "core/java/android/os/IThermalEventListener.aidl",
-        "core/java/android/os/IThermalService.aidl",
-        "core/java/android/os/IUpdateLock.aidl",
-        "core/java/android/os/IUserManager.aidl",
-        "core/java/android/os/IVibratorService.aidl",
-        "core/java/android/os/storage/IStorageManager.aidl",
-        "core/java/android/os/storage/IStorageEventListener.aidl",
-        "core/java/android/os/storage/IStorageShutdownObserver.aidl",
-        "core/java/android/os/storage/IObbActionListener.aidl",
-        ":keystore_aidl",
-        "core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl",
-        "core/java/android/service/autofill/IAutoFillService.aidl",
-        "core/java/android/service/autofill/IAutofillFieldClassificationService.aidl",
-        "core/java/android/service/autofill/IFillCallback.aidl",
-        "core/java/android/service/autofill/ISaveCallback.aidl",
-        "core/java/android/service/carrier/ICarrierService.aidl",
-        "core/java/android/service/carrier/ICarrierMessagingCallback.aidl",
-        "core/java/android/service/carrier/ICarrierMessagingService.aidl",
-        "core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl",
-        "core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl",
-        "core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl",
-        "core/java/android/service/euicc/IEuiccService.aidl",
-        "core/java/android/service/euicc/IGetDefaultDownloadableSubscriptionListCallback.aidl",
-        "core/java/android/service/euicc/IGetDownloadableSubscriptionMetadataCallback.aidl",
-        "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",
-        "core/java/android/service/gatekeeper/IGateKeeperService.aidl",
-        "core/java/android/service/notification/INotificationListener.aidl",
-        "core/java/android/service/notification/IStatusBarNotificationHolder.aidl",
-        "core/java/android/service/notification/IConditionListener.aidl",
-        "core/java/android/service/notification/IConditionProvider.aidl",
-        "core/java/android/service/settings/suggestions/ISuggestionService.aidl",
-        "core/java/android/service/vr/IPersistentVrStateCallbacks.aidl",
-        "core/java/android/service/vr/IVrListener.aidl",
-        "core/java/android/service/vr/IVrManager.aidl",
-        "core/java/android/service/vr/IVrStateCallbacks.aidl",
-        "core/java/android/print/ILayoutResultCallback.aidl",
-        "core/java/android/print/IPrinterDiscoveryObserver.aidl",
-        "core/java/android/print/IPrintDocumentAdapter.aidl",
-        "core/java/android/print/IPrintDocumentAdapterObserver.aidl",
-        "core/java/android/print/IPrintJobStateChangeListener.aidl",
-        "core/java/android/print/IPrintServicesChangeListener.aidl",
-        "core/java/android/printservice/recommendation/IRecommendationsChangeListener.aidl",
-        "core/java/android/print/IPrintManager.aidl",
-        "core/java/android/print/IPrintSpooler.aidl",
-        "core/java/android/print/IPrintSpoolerCallbacks.aidl",
-        "core/java/android/print/IPrintSpoolerClient.aidl",
-        "core/java/android/printservice/recommendation/IRecommendationServiceCallbacks.aidl",
-        "core/java/android/printservice/recommendation/IRecommendationService.aidl",
-        "core/java/android/print/IWriteResultCallback.aidl",
-        "core/java/android/printservice/IPrintService.aidl",
-        "core/java/android/printservice/IPrintServiceClient.aidl",
-        "core/java/android/companion/ICompanionDeviceManager.aidl",
-        "core/java/android/companion/ICompanionDeviceDiscoveryService.aidl",
-        "core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl",
-        "core/java/android/companion/IFindDeviceCallback.aidl",
-        "core/java/android/service/dreams/IDreamManager.aidl",
-        "core/java/android/service/dreams/IDreamService.aidl",
-        "core/java/android/service/oemlock/IOemLockService.aidl",
-        "core/java/android/service/persistentdata/IPersistentDataBlockService.aidl",
-        "core/java/android/service/trust/ITrustAgentService.aidl",
-        "core/java/android/service/trust/ITrustAgentServiceCallback.aidl",
-        "core/java/android/service/voice/IVoiceInteractionService.aidl",
-        "core/java/android/service/voice/IVoiceInteractionSession.aidl",
-        "core/java/android/service/voice/IVoiceInteractionSessionService.aidl",
-        "core/java/android/service/wallpaper/IWallpaperConnection.aidl",
-        "core/java/android/service/wallpaper/IWallpaperEngine.aidl",
-        "core/java/android/service/wallpaper/IWallpaperService.aidl",
-        "core/java/android/service/chooser/IChooserTargetService.aidl",
-        "core/java/android/service/chooser/IChooserTargetResult.aidl",
-        "core/java/android/service/resolver/IResolverRankerService.aidl",
-        "core/java/android/service/resolver/IResolverRankerResult.aidl",
-        "core/java/android/service/textclassifier/ITextClassificationCallback.aidl",
-        "core/java/android/service/textclassifier/ITextClassifierService.aidl",
-        "core/java/android/service/textclassifier/ITextLinksCallback.aidl",
-        "core/java/android/service/textclassifier/ITextSelectionCallback.aidl",
-        "core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl",
-        "core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl",
-        "core/java/android/view/accessibility/IAccessibilityManager.aidl",
-        "core/java/android/view/accessibility/IAccessibilityManagerClient.aidl",
-        "core/java/android/view/autofill/IAutoFillManager.aidl",
-        "core/java/android/view/autofill/IAutoFillManagerClient.aidl",
-        "core/java/android/view/autofill/IAutofillWindowPresenter.aidl",
-        "core/java/android/view/IApplicationToken.aidl",
-        "core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl",
-        "core/java/android/view/IDockedStackListener.aidl",
-        "core/java/android/view/IGraphicsStats.aidl",
-        "core/java/android/view/IGraphicsStatsCallback.aidl",
-        "core/java/android/view/IInputFilter.aidl",
-        "core/java/android/view/IInputFilterHost.aidl",
-        "core/java/android/view/IOnKeyguardExitResult.aidl",
-        "core/java/android/view/IPinnedStackController.aidl",
-        "core/java/android/view/IPinnedStackListener.aidl",
-        "core/java/android/view/IRemoteAnimationRunner.aidl",
-        "core/java/android/view/IRecentsAnimationController.aidl",
-        "core/java/android/view/IRecentsAnimationRunner.aidl",
-        "core/java/android/view/IRemoteAnimationFinishedCallback.aidl",
-        "core/java/android/view/IRotationWatcher.aidl",
-        "core/java/android/view/IWallpaperVisibilityListener.aidl",
-        "core/java/android/view/IWindow.aidl",
-        "core/java/android/view/IWindowFocusObserver.aidl",
-        "core/java/android/view/IWindowId.aidl",
-        "core/java/android/view/IWindowManager.aidl",
-        "core/java/android/view/IWindowSession.aidl",
-        "core/java/android/view/IWindowSessionCallback.aidl",
-        "core/java/android/webkit/IWebViewUpdateService.aidl",
-        "core/java/android/speech/IRecognitionListener.aidl",
-        "core/java/android/speech/IRecognitionService.aidl",
-        "core/java/android/speech/tts/ITextToSpeechCallback.aidl",
-        "core/java/android/speech/tts/ITextToSpeechService.aidl",
-        "core/java/com/android/internal/app/IAppOpsActiveCallback.aidl",
-        "core/java/com/android/internal/app/IAppOpsCallback.aidl",
-        "core/java/com/android/internal/app/IAppOpsService.aidl",
-        "core/java/com/android/internal/app/IBatteryStats.aidl",
-        "core/java/com/android/internal/app/ISoundTriggerService.aidl",
-        "core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl",
-        "core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl",
-        "core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl",
-        "core/java/com/android/internal/app/IVoiceInteractor.aidl",
-        "core/java/com/android/internal/app/IVoiceInteractorCallback.aidl",
-        "core/java/com/android/internal/app/IVoiceInteractorRequest.aidl",
-        "core/java/com/android/internal/app/IMediaContainerService.aidl",
-        "core/java/com/android/internal/app/procstats/IProcessStats.aidl",
-        "core/java/com/android/internal/appwidget/IAppWidgetService.aidl",
-        "core/java/com/android/internal/appwidget/IAppWidgetHost.aidl",
-        "core/java/com/android/internal/backup/IBackupTransport.aidl",
-        "core/java/com/android/internal/backup/IObbBackupService.aidl",
-        "core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl",
-        "core/java/com/android/internal/net/INetworkWatchlistManager.aidl",
-        "core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl",
-        "core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl",
-        "core/java/com/android/internal/policy/IKeyguardExitCallback.aidl",
-        "core/java/com/android/internal/policy/IKeyguardService.aidl",
-        "core/java/com/android/internal/policy/IKeyguardStateCallback.aidl",
-        "core/java/com/android/internal/policy/IShortcutService.aidl",
-        "core/java/com/android/internal/os/IDropBoxManagerService.aidl",
-        "core/java/com/android/internal/os/IParcelFileDescriptorFactory.aidl",
-        "core/java/com/android/internal/os/IResultReceiver.aidl",
-        "core/java/com/android/internal/os/IShellCallback.aidl",
-        "core/java/com/android/internal/statusbar/IStatusBar.aidl",
-        "core/java/com/android/internal/statusbar/IStatusBarService.aidl",
-        "core/java/com/android/internal/textservice/ISpellCheckerService.aidl",
-        "core/java/com/android/internal/textservice/ISpellCheckerServiceCallback.aidl",
-        "core/java/com/android/internal/textservice/ISpellCheckerSession.aidl",
-        "core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl",
-        "core/java/com/android/internal/textservice/ITextServicesManager.aidl",
-        "core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl",
-        "core/java/com/android/internal/view/IDragAndDropPermissions.aidl",
-        "core/java/com/android/internal/view/IInputContext.aidl",
-        "core/java/com/android/internal/view/IInputContextCallback.aidl",
-        "core/java/com/android/internal/view/IInputMethod.aidl",
-        "core/java/com/android/internal/view/IInputMethodClient.aidl",
-        "core/java/com/android/internal/view/IInputMethodManager.aidl",
-        "core/java/com/android/internal/view/IInputMethodSession.aidl",
-        "core/java/com/android/internal/view/IInputSessionCallback.aidl",
-        "core/java/com/android/internal/widget/ICheckCredentialProgressCallback.aidl",
-        "core/java/com/android/internal/widget/ILockSettings.aidl",
-        "core/java/com/android/internal/widget/IRemoteViewsFactory.aidl",
-        "keystore/java/android/security/IKeyChainAliasCallback.aidl",
-        "keystore/java/android/security/IKeyChainService.aidl",
-        "location/java/android/location/IBatchedLocationCallback.aidl",
-        "location/java/android/location/ICountryDetector.aidl",
-        "location/java/android/location/ICountryListener.aidl",
-        "location/java/android/location/IGeocodeProvider.aidl",
-        "location/java/android/location/IGeofenceProvider.aidl",
-        "location/java/android/location/IGnssStatusListener.aidl",
-        "location/java/android/location/IGnssStatusProvider.aidl",
-        "location/java/android/location/IGnssMeasurementsListener.aidl",
-        "location/java/android/location/IGnssNavigationMessageListener.aidl",
-        "location/java/android/location/ILocationListener.aidl",
-        "location/java/android/location/ILocationManager.aidl",
-        "location/java/android/location/IFusedGeofenceHardware.aidl",
-        "location/java/android/location/IGpsGeofenceHardware.aidl",
-        "location/java/android/location/INetInitiatedListener.aidl",
-        "location/java/com/android/internal/location/ILocationProvider.aidl",
-        "media/java/android/media/IAudioFocusDispatcher.aidl",
-        "media/java/android/media/IAudioRoutesObserver.aidl",
-        "media/java/android/media/IAudioService.aidl",
-        "media/java/android/media/IAudioServerStateDispatcher.aidl",
-        "media/java/android/media/IMediaHTTPConnection.aidl",
-        "media/java/android/media/IMediaHTTPService.aidl",
-        "media/java/android/media/IMediaResourceMonitor.aidl",
-        "media/java/android/media/IMediaRouterClient.aidl",
-        "media/java/android/media/IMediaRouterService.aidl",
-        "media/java/android/media/IMediaScannerListener.aidl",
-        "media/java/android/media/IMediaScannerService.aidl",
-        "media/java/android/media/IPlaybackConfigDispatcher.aidl",
-        "media/java/android/media/ISessionTokensListener.aidl",
-        ":libaudioclient_aidl",
-        "media/java/android/media/IRecordingConfigDispatcher.aidl",
-        "media/java/android/media/IRemoteDisplayCallback.aidl",
-        "media/java/android/media/IRemoteDisplayProvider.aidl",
-        "media/java/android/media/IRemoteVolumeController.aidl",
-        "media/java/android/media/IRemoteVolumeObserver.aidl",
-        "media/java/android/media/IRingtonePlayer.aidl",
-        "media/java/android/media/IVolumeController.aidl",
-        "media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl",
-        "media/java/android/media/midi/IBluetoothMidiService.aidl",
-        "media/java/android/media/midi/IMidiDeviceListener.aidl",
-        "media/java/android/media/midi/IMidiDeviceOpenCallback.aidl",
-        "media/java/android/media/midi/IMidiDeviceServer.aidl",
-        "media/java/android/media/midi/IMidiManager.aidl",
-        "media/java/android/media/projection/IMediaProjection.aidl",
-        "media/java/android/media/projection/IMediaProjectionCallback.aidl",
-        "media/java/android/media/projection/IMediaProjectionManager.aidl",
-        "media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl",
-        "media/java/android/media/session/IActiveSessionsListener.aidl",
-        "media/java/android/media/session/ICallback.aidl",
-        "media/java/android/media/session/IOnMediaKeyListener.aidl",
-        "media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl",
-        "media/java/android/media/session/ISession.aidl",
-        "media/java/android/media/session/ISessionCallback.aidl",
-        "media/java/android/media/session/ISessionController.aidl",
-        "media/java/android/media/session/ISessionControllerCallback.aidl",
-        "media/java/android/media/session/ISessionManager.aidl",
-        "media/java/android/media/soundtrigger/ISoundTriggerDetectionService.aidl",
-        "media/java/android/media/soundtrigger/ISoundTriggerDetectionServiceClient.aidl",
-        "media/java/android/media/tv/ITvInputClient.aidl",
-        "media/java/android/media/tv/ITvInputHardware.aidl",
-        "media/java/android/media/tv/ITvInputHardwareCallback.aidl",
-        "media/java/android/media/tv/ITvInputManager.aidl",
-        "media/java/android/media/tv/ITvInputManagerCallback.aidl",
-        "media/java/android/media/tv/ITvInputService.aidl",
-        "media/java/android/media/tv/ITvInputServiceCallback.aidl",
-        "media/java/android/media/tv/ITvInputSession.aidl",
-        "media/java/android/media/tv/ITvInputSessionCallback.aidl",
-        "media/java/android/media/tv/ITvRemoteProvider.aidl",
-        "media/java/android/media/tv/ITvRemoteServiceInput.aidl",
-        "media/java/android/service/media/IMediaBrowserService.aidl",
-        "media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl",
-        "telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl",
-        "telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl",
-        "telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl",
-        "telecomm/java/com/android/internal/telecom/ICallScreeningService.aidl",
-        "telecomm/java/com/android/internal/telecom/IVideoCallback.aidl",
-        "telecomm/java/com/android/internal/telecom/IVideoProvider.aidl",
-        "telecomm/java/com/android/internal/telecom/IConnectionService.aidl",
-        "telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl",
-        "telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl",
-        "telecomm/java/com/android/internal/telecom/IInCallService.aidl",
-        "telecomm/java/com/android/internal/telecom/IPhoneAccountSuggestionCallback.aidl",
-        "telecomm/java/com/android/internal/telecom/IPhoneAccountSuggestionService.aidl",
-        "telecomm/java/com/android/internal/telecom/ITelecomService.aidl",
-        "telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl",
-        "telephony/java/android/telephony/data/IDataService.aidl",
-        "telephony/java/android/telephony/data/IDataServiceCallback.aidl",
-        "telephony/java/android/telephony/data/IQualifiedNetworksService.aidl",
-        "telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsCapabilityCallback.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsConfig.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsConfigCallback.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl",
-        "telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl",
-        "telephony/java/android/telephony/ims/aidl/IRcs.aidl",
-        "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl",
-        "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl",
-        "telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl",
-        "telephony/java/android/telephony/mbms/IDownloadStatusListener.aidl",
-        "telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl",
-        "telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl",
-        "telephony/java/android/telephony/mbms/IGroupCallCallback.aidl",
-        "telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl",
-        "telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl",
-        "telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl",
-        "telephony/java/android/telephony/ICellInfoCallback.aidl",
-        "telephony/java/android/telephony/INetworkService.aidl",
-        "telephony/java/android/telephony/INetworkServiceCallback.aidl",
-        "telephony/java/com/android/ims/internal/IImsCallSession.aidl",
-        "telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl",
-        "telephony/java/com/android/ims/internal/IImsConfig.aidl",
-        "telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl",
-        "telephony/java/com/android/ims/internal/IImsEcbm.aidl",
-        "telephony/java/com/android/ims/internal/IImsEcbmListener.aidl",
-        "telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl",
-        "telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl",
-        "telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl",
-        "telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl",
-        "telephony/java/com/android/ims/internal/IImsRcsFeature.aidl",
-        "telephony/java/com/android/ims/internal/IImsService.aidl",
-        "telephony/java/com/android/ims/internal/IImsServiceController.aidl",
-        "telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl",
-        "telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl",
-        "telephony/java/com/android/ims/internal/IImsUt.aidl",
-        "telephony/java/com/android/ims/internal/IImsUtListener.aidl",
-        "telephony/java/com/android/ims/internal/IImsVideoCallCallback.aidl",
-        "telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl",
-        "telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl",
-        "telephony/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl",
-        "telephony/java/com/android/ims/internal/uce/options/IOptionsService.aidl",
-        "telephony/java/com/android/ims/internal/uce/options/IOptionsListener.aidl",
-        "telephony/java/com/android/ims/internal/uce/presence/IPresenceService.aidl",
-        "telephony/java/com/android/ims/internal/uce/presence/IPresenceListener.aidl",
-        "telephony/java/com/android/ims/ImsConfigListener.aidl",
-        "telephony/java/com/android/internal/telephony/IApnSourceService.aidl",
-        "telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl",
-        "telephony/java/com/android/internal/telephony/IMms.aidl",
-        "telephony/java/com/android/internal/telephony/INumberVerificationCallback.aidl",
-        "telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl",
-        "telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl",
-        "telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl",
-        "telephony/java/com/android/internal/telephony/ISetOpportunisticDataCallback.aidl",
-        "telephony/java/com/android/internal/telephony/ISms.aidl",
-        "telephony/java/com/android/internal/telephony/ISub.aidl",
-        "telephony/java/com/android/internal/telephony/IOns.aidl",
-        "telephony/java/com/android/internal/telephony/ITelephony.aidl",
-        "telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl",
-        "telephony/java/com/android/internal/telephony/IUpdateAvailableNetworksCallback.aidl",
-        "telephony/java/com/android/internal/telephony/IWapPushManager.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IAuthenticateServerCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/ICancelSessionCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IDeleteProfileCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IDisableProfileCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IGetDefaultSmdpAddressCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IGetEuiccChallengeCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo1Callback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo2Callback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IGetProfileCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IGetRulesAuthTableCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IGetSmdsAddressCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IListNotificationsCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/ILoadBoundProfilePackageCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IPrepareDownloadCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IRemoveNotificationFromListCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IResetMemoryCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationListCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/ISetDefaultSmdpAddressCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/ISetNicknameCallback.aidl",
-        "telephony/java/com/android/internal/telephony/euicc/ISwitchToProfileCallback.aidl",
-        "wifi/java/android/net/wifi/ISoftApCallback.aidl",
-        "wifi/java/android/net/wifi/IWifiManager.aidl",
-        "wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl",
-        "wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl",
-        "wifi/java/android/net/wifi/aware/IWifiAwareMacAddressProvider.aidl",
-        "wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl",
-        "wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl",
-        "wifi/java/android/net/wifi/rtt/IRttCallback.aidl",
-        "wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl",
-        "wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl",
-        "wifi/java/android/net/wifi/IWifiScanner.aidl",
-        "packages/services/PacProcessor/com/android/net/IProxyService.aidl",
-        "packages/services/Proxy/com/android/net/IProxyCallback.aidl",
-        "packages/services/Proxy/com/android/net/IProxyPortListener.aidl",
-        "core/java/android/service/quicksettings/IQSService.aidl",
-        "core/java/android/service/quicksettings/IQSTileService.aidl",
-
         ":libupdate_engine_aidl",
-
         ":storaged_aidl",
         ":vold_aidl",
-        ":gsiservice_aidl",
-        ":installd_aidl",
-        ":dumpstate_aidl",
 
-        "lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl",
-        "lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl",
-        "lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl",
-        "lowpan/java/android/net/lowpan/ILowpanInterface.aidl",
-        "lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl",
-        "lowpan/java/android/net/lowpan/ILowpanManager.aidl",
+        // For the generated R.java and Manifest.java
+        ":framework-res{.aapt.srcjar}",
 
-        "core/java/android/app/admin/SecurityLogTags.logtags",
-        "core/java/android/content/EventLogTags.logtags",
-        "core/java/android/speech/tts/EventLogTags.logtags",
-        "core/java/android/net/EventLogTags.logtags",
-        "core/java/android/os/EventLogTags.logtags",
-        "core/java/android/webkit/EventLogTags.logtags",
-        "core/java/com/android/internal/app/EventLogTags.logtags",
-        "core/java/com/android/internal/logging/EventLogTags.logtags",
-        "core/java/com/android/server/DropboxLogTags.logtags",
-        "core/java/org/chromium/arc/EventLogTags.logtags",
-
-        ":platform-properties",
-
+        // etc.
+        ":framework-javastream-protos",
         ":framework-statslog-gen",
     ],
+}
 
+java_defaults {
+    name: "framework-aidl-export-defaults",
     aidl: {
         export_include_dirs: [
-            // From build/make/core/pathmap.mk FRAMEWORK_BASE_SUBDIRS
             "core/java",
+            "drm/java",
             "graphics/java",
+            "keystore/java",
             "location/java",
             "lowpan/java",
             "media/java",
             "media/mca/effect/java",
             "media/mca/filterfw/java",
             "media/mca/filterpacks/java",
-            "drm/java",
             "opengl/java",
+            "rs/java",
             "sax/java",
             "telecomm/java",
             "telephony/java",
             "wifi/java",
-            "keystore/java",
-            "rs/java",
         ],
-
-        include_dirs: [
-            "system/update_engine/binder_bindings",
-            "frameworks/native/aidl/binder",
-            "frameworks/native/cmds/dumpstate/binder",
-            "frameworks/av/camera/aidl",
-            "frameworks/av/media/libaudioclient/aidl",
-            "frameworks/native/aidl/gui",
-            "system/core/storaged/binder",
-            "system/vold/binder",
-            "system/gsid/aidl",
-            "system/bt/binder",
-            "system/security/keystore/binder",
-        ],
-
-        generate_get_transaction_name: true
     },
+}
 
-    exclude_srcs: [
-        // See comment on framework-atb-backward-compatibility module below
-        "core/java/android/content/pm/AndroidTestBaseUpdater.java",
-    ],
-
-    no_framework_libs: true,
-    libs: [
-        "ext",
-    ],
-
-    jarjar_rules: ":framework-jarjar-rules",
-
+// Collection of classes that are generated from non-Java files that are not listed in
+// framework_srcs. These have no or very limited dependency to the framework.
+java_library {
+    name: "framework-internal-utils",
     static_libs: [
         "apex_aidl_interface-java",
+        "suspend_control_aidl_interface-java",
         "framework-protos",
         "game-driver-protos",
         "android.hidl.base-V1.0-java",
@@ -706,7 +262,40 @@
         "android.hardware.vibrator-V1.1-java",
         "android.hardware.vibrator-V1.2-java",
         "android.hardware.wifi-V1.0-java-constants",
+
+        "PlatformProperties",
     ],
+    sdk_version: "core_platform",
+    installable: false,
+}
+
+java_defaults {
+    name: "framework-defaults",
+    defaults: ["framework-aidl-export-defaults"],
+    installable: true,
+
+    srcs: [
+        ":framework-srcs",
+        "core/java/**/*.logtags",
+    ],
+
+    aidl: {
+        generate_get_transaction_name: true,
+    },
+
+    exclude_srcs: [
+        // See comment on framework-atb-backward-compatibility module below
+        "core/java/android/content/pm/AndroidTestBaseUpdater.java",
+    ],
+
+    sdk_version: "core_platform",
+    libs: [
+        "ext",
+    ],
+
+    jarjar_rules: ":framework-jarjar-rules",
+
+    static_libs: ["framework-internal-utils"],
 
     required: [
         // TODO: remove gps_debug when the build system propagates "required" properly.
@@ -733,6 +322,7 @@
         "core/java/android/os/IIncidentManager.aidl",
         "core/java/android/os/IIncidentReportStatusListener.aidl",
     ],
+    path: "core/java",
 }
 
 filegroup {
@@ -741,30 +331,51 @@
         "core/java/android/os/IStatsCompanionService.aidl",
         "core/java/android/os/IStatsManager.aidl",
     ],
+    path: "core/java",
 }
 
 java_library {
     name: "framework",
     defaults: ["framework-defaults"],
     javac_shard_size: 150,
+    required: [
+        "framework-platform-compat-config",
+        "libcore-platform-compat-config",
+        "services-platform-compat-config",
+    ],
 }
 
 java_library {
     name: "framework-annotation-proc",
     defaults: ["framework-defaults"],
-    // Use UsedByApps annotation processor
-    plugins: ["unsupportedappusage-annotation-processor"],
+    installable: false,
+    plugins: [
+        "unsupportedappusage-annotation-processor",
+        "compat-changeid-annotation-processor",
+    ],
 }
 
-// A host library including just UnsupportedAppUsage.java so that the annotation
-// processor can also use this annotation.
-java_library_host {
+platform_compat_config {
+    name: "framework-platform-compat-config",
+    src: ":framework-annotation-proc",
+}
+
+// A library including just UnsupportedAppUsage.java classes.
+//
+// Provided for target so that libraries can use it without depending on
+// the whole of framework or the core platform API.
+//
+// Built for host so that the annotation processor can also use this annotation.
+java_library {
     name: "unsupportedappusage-annotation",
+    host_supported: true,
     srcs: [
         "core/java/android/annotation/IntDef.java",
         "core/java/android/annotation/UnsupportedAppUsage.java",
         ":unsupportedappusage_annotation_files",
     ],
+
+    sdk_version: "core_current",
 }
 
 // A temporary build target that is conditionally included on the bootclasspath if
@@ -791,7 +402,7 @@
     name: "framework-javastream-protos",
     depfile: true,
 
-    tool_files: [ "tools/genprotos.sh", ],
+    tool_files: ["tools/genprotos.sh"],
     tools: [
         "aprotoc",
         "protoc-gen-javastream",
@@ -802,13 +413,13 @@
     // end up with a command that is extremely long, potentially going passed MAX_ARG_STRLEN due to
     // the way sbox rewrites the command. See b/70221552.
     cmd: "$(location tools/genprotos.sh) " +
-              " $(location aprotoc) " +
-              " $(location protoc-gen-javastream) " +
-              " $(location soong_zip) " +
-              " $(genDir) " +
-              " $(depfile) " +
-              " $(in) " +
-              " $(out)",
+        " $(location aprotoc) " +
+        " $(location protoc-gen-javastream) " +
+        " $(location soong_zip) " +
+        " $(genDir) " +
+        " $(depfile) " +
+        " $(in) " +
+        " $(out)",
     srcs: [
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
@@ -826,7 +437,7 @@
         "core/java/android/annotation/UnsupportedAppUsage.java",
         "core/java/com/android/internal/annotations/GuardedBy.java",
         "core/java/com/android/internal/annotations/VisibleForTesting.java",
-    ]
+    ],
 }
 
 filegroup {
@@ -847,7 +458,7 @@
         "core/java/com/android/internal/util/TrafficStatsConstants.java",
         "core/java/com/android/internal/util/WakeupMessage.java",
         "core/java/android/net/shared/*.java",
-    ]
+    ],
 }
 
 // Build ext.jar
@@ -855,7 +466,7 @@
 java_library {
     name: "ext",
     installable: true,
-    no_framework_libs: true,
+    sdk_version: "core_platform",
     static_libs: [
         "libphonenumber-platform",
         "nist-sip",
@@ -883,7 +494,7 @@
         type: "full",
     },
     errorprone: {
-        javacflags: ["-Xep:MissingOverride:OFF"],  // b/72714520
+        javacflags: ["-Xep:MissingOverride:OFF"], // b/72714520
     },
 }
 
@@ -1014,7 +625,7 @@
 // updated to use hwbinder.stubs.
 java_library {
     name: "hwbinder",
-    no_framework_libs: true,
+    sdk_version: "core_platform",
 
     srcs: [
         "core/java/android/os/HidlSupport.java",
@@ -1066,7 +677,7 @@
     ],
 }
 
-// TODO: Don't rely on this list once droiddoc can take a list of packages to document
+// TODO: Don't rely on this list by switching package.html into package-info.java
 frameworks_base_subdirs = [
     "core/java",
     "graphics/java",
@@ -1086,13 +697,6 @@
     "rs/java",
 ]
 
-packages_to_document = [
-    "android",
-    "javax/microedition/khronos",
-    "org/apache/http/conn",
-    "org/apache/http/params",
-]
-
 // Make the api/current.txt file available for use by modules in other
 // directories.
 filegroup {
@@ -1102,12 +706,30 @@
     ],
 }
 
+// Make the api/system-current.txt file available for use by modules in other
+// directories.
+filegroup {
+    name: "frameworks-base-api-system-current.txt",
+    srcs: [
+        "api/system-current.txt",
+    ],
+}
+
+// Make the api/system-removed.txt file available for use by modules in other
+// directories.
+filegroup {
+    name: "frameworks-base-api-system-removed.txt",
+    srcs: [
+        "api/system-removed.txt",
+    ],
+}
+
 framework_docs_only_args = " -android -manifest $(location core/res/AndroidManifest.xml) " +
-     "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
-     "-overview $(location core/java/overview.html) " +
-     // Federate Support Library references against local API file.
-     "-federate SupportLib https://developer.android.com " +
-     "-federationapi SupportLib $(location :current-support-api) "
+    "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
+    "-overview $(location core/java/overview.html) " +
+    // Federate Support Library references against local API file.
+    "-federate SupportLib https://developer.android.com " +
+    "-federationapi SupportLib $(location :current-support-api) "
 
 framework_docs_only_libs = [
     "voip-common",
@@ -1155,11 +777,28 @@
     "--hide RequiresPermission " +
     "--hide MissingPermission --hide BroadcastBehavior " +
     "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
-    "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo"
+    "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo "
+
+// http://b/129765390 Rewrite links to "platform" or "technotes" folders
+// which are siblings (and thus outside of) {@docRoot}.
+//
+// We have to escape \ as \\ and $ as $$ here because they get resolved by
+// different layers of the build tooling. The arguments are wrapped in '' so
+// that the shell doesn't add yet another level of escaping.
+metalava_framework_docs_args += " --replace-documentation " +
+    // packages whose descendants to apply replacement to (all packages from
+    // libcore/ojluni/src/main/java that contribute to documentation).
+    "com.sun:java:javax:jdk.net:sun " +
+    // regex of the string to replace
+    "'(<a\\s+href\\s?=[\\*\\s]*\")(?:(?:\\{@docRoot\\}/\\.\\./)|(?:(?:\\.\\./)+))((?:platform|technotes).+)\">' " +
+    // replacement (with $1, $2 backreferences to the regex groups)
+    "'$$1https://docs.oracle.com/javase/8/docs/$$2\">' "
 
 stubs_defaults {
     name: "framework-doc-stubs-default",
     srcs: [
+        ":framework-srcs",
+        "core/java/**/*.logtags",
         "test-base/src/**/*.java",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
@@ -1167,9 +806,6 @@
         "test-mock/src/**/*.java",
         "test-runner/src/**/*.java",
     ],
-    srcs_lib: "framework",
-    srcs_lib_whitelist_dirs: frameworks_base_subdirs,
-    srcs_lib_whitelist_pkgs: packages_to_document,
     libs: framework_docs_only_libs,
     local_sourcepaths: frameworks_base_subdirs,
     create_doc_stubs: true,
@@ -1189,7 +825,7 @@
 doc_defaults {
     name: "framework-docs-default",
     libs: framework_docs_only_libs +
-         ["stub-annotations"],
+        ["stub-annotations"],
     html_dirs: [
         "docs/html",
     ],
@@ -1216,21 +852,21 @@
     create_stubs: false,
 }
 
+doc_defaults {
+    name: "framework-dokka-docs-default",
+    create_stubs: false,
+}
+
 stubs_defaults {
     name: "metalava-api-stubs-default",
     srcs: [
+        ":framework-srcs",
+        "core/java/**/*.logtags",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
         ":core_public_api_files",
     ],
-    srcs_lib: "framework",
-    srcs_lib_whitelist_dirs: frameworks_base_subdirs,
-    srcs_lib_whitelist_pkgs: packages_to_document,
-    libs: [
-        "ext",
-        "framework",
-        "voip-common",
-    ],
+    libs: ["framework-internal-utils"],
     local_sourcepaths: frameworks_base_subdirs,
     installable: false,
     annotations_enabled: true,
@@ -1244,6 +880,7 @@
         "sdk-dir",
         "api-versions-jars-dir",
     ],
+    sdk_version: "core_platform",
 }
 
 droidstubs {
@@ -1321,8 +958,8 @@
         "android.whichdoc offline",
     ],
     proofread_file: "offline-system-sdk-referenceonly-docs-proofrerad.txt",
-    args: framework_docs_only_args  + " -hide 101 -hide 104 -hide 108" +
-        " -offlinemode -title \"Android System SDK\" -referenceonly",
+    args: framework_docs_only_args + " -hide 101 -hide 104 -hide 108" +
+    " -offlinemode -title \"Android System SDK\" -referenceonly",
     write_sdk_values: true,
     static_doc_index_redirect: "docs/docs-documentation-redirect.html",
     static_doc_properties: "docs/source.properties",
@@ -1339,7 +976,7 @@
         "android.hasSamples true",
     ],
     proofread_file: "online-sdk-docs-proofrerad.txt",
-    args: framework_docs_only_args  +
+    args: framework_docs_only_args +
         " -toroot / -samplegroup Admin " +
         " -samplegroup Background " +
         " -samplegroup Connectivity " +
@@ -1393,7 +1030,7 @@
 }
 
 droiddoc {
-    name: "ds-docs",
+    name: "ds-docs-java",
     defaults: ["framework-docs-default"],
     srcs: [
         ":framework-doc-stubs",
@@ -1422,6 +1059,58 @@
 }
 
 droiddoc {
+    name: "ds-docs-kt",
+    defaults: ["framework-dokka-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    args: "-noJdkLink -links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list " +
+    "-noStdlibLink",
+    proofread_file: "ds-dokka-proofread.txt",
+    dokka_enabled: true,
+}
+
+java_genrule {
+    name: "ds-docs",
+    tools: [
+        "zip2zip",
+        "merge_zips",
+    ],
+    srcs: [
+        ":ds-docs-java{.docs.zip}",
+        ":ds-docs-kt{.docs.zip}",
+    ],
+    out: ["ds-docs.zip"],
+    dist: {
+        targets: ["docs"],
+    },
+    cmd: "$(location zip2zip) -i $(location :ds-docs-kt{.docs.zip}) -o $(genDir)/ds-docs-kt-moved.zip **/*:en/reference/kotlin && " +
+         "$(location merge_zips) $(out) $(location :ds-docs-java{.docs.zip}) $(genDir)/ds-docs-kt-moved.zip",
+}
+
+java_genrule {
+    name: "ds-docs-switched",
+    tools: [
+        "switcher4",
+        "soong_zip",
+    ],
+    srcs: [
+        ":ds-docs-java{.docs.zip}",
+        ":ds-docs-kt{.docs.zip}",
+    ],
+    out: ["ds-docs-switched.zip"],
+    dist: {
+        targets: ["docs"],
+    },
+    cmd: "unzip $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
+         "unzip $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
+         "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
+         "(cd $(genDir)/en/reference && $$SWITCHER --work platform) && " +
+         "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
+}
+
+
+droiddoc {
     name: "ds-static-docs",
     defaults: ["framework-docs-default"],
     srcs: [
@@ -1432,10 +1121,10 @@
     ],
     proofread_file: "ds-static-docs-proofrerad.txt",
     args: framework_docs_only_args +
-          " -staticonly " +
-          " -toroot / " +
-          " -devsite " +
-          " -ignoreJdLinks ",
+        " -staticonly " +
+        " -toroot / " +
+        " -devsite " +
+        " -ignoreJdLinks ",
 }
 
 droiddoc {
@@ -1449,9 +1138,9 @@
     ],
     proofread_file: "ds-ref-navtree-docs-proofrerad.txt",
     args: framework_docs_only_args +
-          " -toroot / " +
-          " -atLinksNavtree " +
-          " -navtreeonly ",
+        " -toroot / " +
+        " -atLinksNavtree " +
+        " -navtreeonly ",
 }
 
 droiddoc {
@@ -1491,8 +1180,8 @@
     ],
     proofread_file: "hidden-docs-proofrerad.txt",
     args: framework_docs_only_args +
-          " -referenceonly " +
-          " -title \"Android SDK - Including hidden APIs.\"",
+        " -referenceonly " +
+        " -title \"Android SDK - Including hidden APIs.\"",
 }
 
 droidstubs {
@@ -1514,7 +1203,7 @@
         "core/java/android/util/AndroidException.java",
     ],
     installable: false,
-    no_framework_libs: true,
+    sdk_version: "core_platform",
     annotations_enabled: true,
     previous_api: ":last-released-public-api",
     merge_annotations_dirs: [
@@ -1544,19 +1233,18 @@
     args: metalava_framework_docs_args +
         " --show-unannotated " +
         " --show-annotation android.annotation.SystemApi " +
-        " --show-annotation android.annotation.TestApi "
+        " --show-annotation android.annotation.TestApi ",
 }
 
-
 droidstubs {
     name: "hiddenapi-mappings",
     defaults: ["metalava-api-stubs-default"],
     srcs: [
-        ":openjdk_java_files",
         ":non_openjdk_java_files",
+        ":openjdk_java_files",
         ":opt-telephony-common-srcs",
-        "core/java/**/*.java",
     ],
+
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
@@ -1566,7 +1254,7 @@
         " --hide UnhiddenSystemApi " +
         " --show-unannotated " +
         " --show-annotation android.annotation.SystemApi " +
-        " --show-annotation android.annotation.TestApi "
+        " --show-annotation android.annotation.TestApi ",
 }
 
 filegroup {
@@ -1610,6 +1298,7 @@
         last_released: {
             api_file: ":last-released-public-api",
             removed_api_file: "api/removed.txt",
+            baseline_file: ":public-api-incompatibilities-with-last-released",
         },
     },
     jdiff_enabled: true,
@@ -1635,6 +1324,7 @@
         last_released: {
             api_file: ":last-released-system-api",
             removed_api_file: "api/system-removed.txt",
+            baseline_file: ":system-api-incompatibilities-with-last-released"
         },
     },
     jdiff_enabled: true,
@@ -1669,6 +1359,16 @@
 // annotations to private apis
 aidl_mapping {
     name: "framework-aidl-mappings",
-    srcs: [":framework-defaults"],
-    output: "framework-aidl-mappings.txt"
+    srcs: [":framework-srcs"],
+    output: "framework-aidl-mappings.txt",
+}
+
+genrule {
+    name: "framework-annotation-proc-index",
+    srcs: [":framework-annotation-proc"],
+    cmd: "unzip -qp $(in) unsupportedappusage/unsupportedappusage_index.csv > $(out)",
+    out: ["unsupportedappusage_index.csv"],
+    dist: {
+        targets: ["droidcore"],
+    },
 }
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 543f0ed..e731138 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -9,6 +9,8 @@
 
 hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
 
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
+
 owners_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "OWNERS$"
 
 shell_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^packages/Shell/"
diff --git a/api/current.txt b/api/current.txt
index 97c1049..6d7cd69 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -11361,6 +11361,9 @@
     field public static final String FEATURE_SENSOR_RELATIVE_HUMIDITY = "android.hardware.sensor.relative_humidity";
     field public static final String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter";
     field public static final String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector";
+    field public static final String FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese";
+    field public static final String FEATURE_SE_OMAPI_SD = "android.hardware.se.omapi.sd";
+    field public static final String FEATURE_SE_OMAPI_UICC = "android.hardware.se.omapi.uicc";
     field public static final String FEATURE_SIP = "android.software.sip";
     field public static final String FEATURE_SIP_VOIP = "android.software.sip.voip";
     field public static final String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore";
@@ -17489,8 +17492,12 @@
     field public static final int EARLY_DYNASTIC_CUNEIFORM_ID = 257; // 0x101
     field public static final android.icu.lang.UCharacter.UnicodeBlock EGYPTIAN_HIEROGLYPHS;
     field public static final int EGYPTIAN_HIEROGLYPHS_ID = 194; // 0xc2
+    field public static final android.icu.lang.UCharacter.UnicodeBlock EGYPTIAN_HIEROGLYPH_FORMAT_CONTROLS;
+    field public static final int EGYPTIAN_HIEROGLYPH_FORMAT_CONTROLS_ID = 292; // 0x124
     field public static final android.icu.lang.UCharacter.UnicodeBlock ELBASAN;
     field public static final int ELBASAN_ID = 226; // 0xe2
+    field public static final android.icu.lang.UCharacter.UnicodeBlock ELYMAIC;
+    field public static final int ELYMAIC_ID = 293; // 0x125
     field public static final android.icu.lang.UCharacter.UnicodeBlock EMOTICONS;
     field public static final int EMOTICONS_ID = 206; // 0xce
     field public static final android.icu.lang.UCharacter.UnicodeBlock ENCLOSED_ALPHANUMERICS;
@@ -17717,6 +17724,8 @@
     field public static final int MYANMAR_ID = 28; // 0x1c
     field public static final android.icu.lang.UCharacter.UnicodeBlock NABATAEAN;
     field public static final int NABATAEAN_ID = 239; // 0xef
+    field public static final android.icu.lang.UCharacter.UnicodeBlock NANDINAGARI;
+    field public static final int NANDINAGARI_ID = 294; // 0x126
     field public static final android.icu.lang.UCharacter.UnicodeBlock NEWA;
     field public static final int NEWA_ID = 270; // 0x10e
     field public static final android.icu.lang.UCharacter.UnicodeBlock NEW_TAI_LUE;
@@ -17728,6 +17737,8 @@
     field public static final int NUMBER_FORMS_ID = 45; // 0x2d
     field public static final android.icu.lang.UCharacter.UnicodeBlock NUSHU;
     field public static final int NUSHU_ID = 277; // 0x115
+    field public static final android.icu.lang.UCharacter.UnicodeBlock NYIAKENG_PUACHUE_HMONG;
+    field public static final int NYIAKENG_PUACHUE_HMONG_ID = 295; // 0x127
     field public static final android.icu.lang.UCharacter.UnicodeBlock OGHAM;
     field public static final int OGHAM_ID = 34; // 0x22
     field public static final android.icu.lang.UCharacter.UnicodeBlock OLD_HUNGARIAN;
@@ -17758,6 +17769,8 @@
     field public static final int OSAGE_ID = 271; // 0x10f
     field public static final android.icu.lang.UCharacter.UnicodeBlock OSMANYA;
     field public static final int OSMANYA_ID = 122; // 0x7a
+    field public static final android.icu.lang.UCharacter.UnicodeBlock OTTOMAN_SIYAQ_NUMBERS;
+    field public static final int OTTOMAN_SIYAQ_NUMBERS_ID = 296; // 0x128
     field public static final android.icu.lang.UCharacter.UnicodeBlock PAHAWH_HMONG;
     field public static final int PAHAWH_HMONG_ID = 243; // 0xf3
     field public static final android.icu.lang.UCharacter.UnicodeBlock PALMYRENE;
@@ -17806,6 +17819,8 @@
     field public static final int SINHALA_ID = 24; // 0x18
     field public static final android.icu.lang.UCharacter.UnicodeBlock SMALL_FORM_VARIANTS;
     field public static final int SMALL_FORM_VARIANTS_ID = 84; // 0x54
+    field public static final android.icu.lang.UCharacter.UnicodeBlock SMALL_KANA_EXTENSION;
+    field public static final int SMALL_KANA_EXTENSION_ID = 297; // 0x129
     field public static final android.icu.lang.UCharacter.UnicodeBlock SOGDIAN;
     field public static final int SOGDIAN_ID = 291; // 0x123
     field public static final android.icu.lang.UCharacter.UnicodeBlock SORA_SOMPENG;
@@ -17842,6 +17857,8 @@
     field public static final int SUTTON_SIGNWRITING_ID = 262; // 0x106
     field public static final android.icu.lang.UCharacter.UnicodeBlock SYLOTI_NAGRI;
     field public static final int SYLOTI_NAGRI_ID = 143; // 0x8f
+    field public static final android.icu.lang.UCharacter.UnicodeBlock SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A;
+    field public static final int SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A_ID = 298; // 0x12a
     field public static final android.icu.lang.UCharacter.UnicodeBlock SYRIAC;
     field public static final int SYRIAC_ID = 13; // 0xd
     field public static final android.icu.lang.UCharacter.UnicodeBlock SYRIAC_SUPPLEMENT;
@@ -17864,6 +17881,8 @@
     field public static final int TAKRI_ID = 220; // 0xdc
     field public static final android.icu.lang.UCharacter.UnicodeBlock TAMIL;
     field public static final int TAMIL_ID = 20; // 0x14
+    field public static final android.icu.lang.UCharacter.UnicodeBlock TAMIL_SUPPLEMENT;
+    field public static final int TAMIL_SUPPLEMENT_ID = 299; // 0x12b
     field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT;
     field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT_COMPONENTS;
     field public static final int TANGUT_COMPONENTS_ID = 273; // 0x111
@@ -17898,6 +17917,8 @@
     field public static final int VEDIC_EXTENSIONS_ID = 175; // 0xaf
     field public static final android.icu.lang.UCharacter.UnicodeBlock VERTICAL_FORMS;
     field public static final int VERTICAL_FORMS_ID = 145; // 0x91
+    field public static final android.icu.lang.UCharacter.UnicodeBlock WANCHO;
+    field public static final int WANCHO_ID = 300; // 0x12c
     field public static final android.icu.lang.UCharacter.UnicodeBlock WARANG_CITI;
     field public static final int WARANG_CITI_ID = 252; // 0xfc
     field public static final android.icu.lang.UCharacter.UnicodeBlock YIJING_HEXAGRAM_SYMBOLS;
@@ -18210,6 +18231,7 @@
     field public static final int EASTERN_SYRIAC = 97; // 0x61
     field public static final int EGYPTIAN_HIEROGLYPHS = 71; // 0x47
     field public static final int ELBASAN = 136; // 0x88
+    field public static final int ELYMAIC = 185; // 0xb9
     field public static final int ESTRANGELO_SYRIAC = 95; // 0x5f
     field public static final int ETHIOPIC = 11; // 0xb
     field public static final int GEORGIAN = 12; // 0xc
@@ -18289,10 +18311,12 @@
     field public static final int MYANMAR = 28; // 0x1c
     field public static final int NABATAEAN = 143; // 0x8f
     field public static final int NAKHI_GEBA = 132; // 0x84
+    field public static final int NANDINAGARI = 187; // 0xbb
     field public static final int NEWA = 170; // 0xaa
     field public static final int NEW_TAI_LUE = 59; // 0x3b
     field public static final int NKO = 87; // 0x57
     field public static final int NUSHU = 150; // 0x96
+    field public static final int NYIAKENG_PUACHUE_HMONG = 186; // 0xba
     field public static final int OGHAM = 29; // 0x1d
     field public static final int OLD_CHURCH_SLAVONIC_CYRILLIC = 68; // 0x44
     field public static final int OLD_HUNGARIAN = 76; // 0x4c
@@ -18356,6 +18380,7 @@
     field public static final int UNWRITTEN_LANGUAGES = 102; // 0x66
     field public static final int VAI = 99; // 0x63
     field public static final int VISIBLE_SPEECH = 100; // 0x64
+    field public static final int WANCHO = 188; // 0xbc
     field public static final int WARANG_CITI = 146; // 0x92
     field public static final int WESTERN_SYRIAC = 96; // 0x60
     field public static final int WOLEAI = 155; // 0x9b
@@ -19144,6 +19169,7 @@
     method public String getDateTimeFormat();
     method public String getDecimal();
     method public static android.icu.text.DateTimePatternGenerator getEmptyInstance();
+    method public String getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth);
     method public static android.icu.text.DateTimePatternGenerator getInstance();
     method public static android.icu.text.DateTimePatternGenerator getInstance(android.icu.util.ULocale);
     method public static android.icu.text.DateTimePatternGenerator getInstance(java.util.Locale);
@@ -19177,6 +19203,12 @@
     field public static final int ZONE = 15; // 0xf
   }
 
+  public enum DateTimePatternGenerator.DisplayWidth {
+    enum_constant public static final android.icu.text.DateTimePatternGenerator.DisplayWidth ABBREVIATED;
+    enum_constant public static final android.icu.text.DateTimePatternGenerator.DisplayWidth NARROW;
+    enum_constant public static final android.icu.text.DateTimePatternGenerator.DisplayWidth WIDE;
+  }
+
   public static final class DateTimePatternGenerator.PatternInfo {
     ctor public DateTimePatternGenerator.PatternInfo();
     field public static final int BASE_CONFLICT = 1; // 0x1
@@ -20731,6 +20763,7 @@
     method public static boolean isAvailable(String, java.util.Date, java.util.Date);
     method public java.util.Currency toJavaCurrency();
     field public static final int LONG_NAME = 1; // 0x1
+    field public static final int NARROW_SYMBOL_NAME = 3; // 0x3
     field public static final int PLURAL_LONG_NAME = 2; // 0x2
     field public static final int SYMBOL_NAME = 0; // 0x0
   }
@@ -20933,6 +20966,7 @@
     ctor public JapaneseCalendar(int, int, int, int, int, int);
     field public static final int HEISEI;
     field public static final int MEIJI;
+    field public static final int REIWA;
     field public static final int SHOWA;
     field public static final int TAISHO;
   }
@@ -21408,6 +21442,8 @@
     field public static final android.icu.util.VersionInfo UCOL_RUNTIME_VERSION;
     field public static final android.icu.util.VersionInfo UNICODE_10_0;
     field public static final android.icu.util.VersionInfo UNICODE_11_0;
+    field public static final android.icu.util.VersionInfo UNICODE_12_0;
+    field public static final android.icu.util.VersionInfo UNICODE_12_1;
     field public static final android.icu.util.VersionInfo UNICODE_1_0;
     field public static final android.icu.util.VersionInfo UNICODE_1_0_1;
     field public static final android.icu.util.VersionInfo UNICODE_1_1_0;
@@ -26800,7 +26836,7 @@
     method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
     method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
     method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
-    method public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
     method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
     method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
     method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
@@ -29110,7 +29146,6 @@
   }
 
   public final class NfcAdapter {
-    method public boolean deviceSupportsNfcSecure();
     method public void disableForegroundDispatch(android.app.Activity);
     method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
     method public void disableReaderMode(android.app.Activity);
@@ -29122,7 +29157,8 @@
     method @Deprecated public boolean invokeBeam(android.app.Activity);
     method public boolean isEnabled();
     method @Deprecated public boolean isNdefPushEnabled();
-    method public boolean isNfcSecureEnabled();
+    method public boolean isSecureNfcEnabled();
+    method public boolean isSecureNfcSupported();
     method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
     method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
     method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
@@ -37379,6 +37415,7 @@
   }
 
   public static final class Telephony.Mms.Addr implements android.provider.BaseColumns {
+    method @NonNull public static android.net.Uri getAddrUriForMessage(@NonNull String);
     field public static final String ADDRESS = "address";
     field public static final String CHARSET = "charset";
     field public static final String CONTACT_ID = "contact_id";
@@ -37407,12 +37444,13 @@
   }
 
   public static final class Telephony.Mms.Part implements android.provider.BaseColumns {
+    method @NonNull public static android.net.Uri getPartUriForMessage(@NonNull String);
     field public static final String CHARSET = "chset";
     field public static final String CONTENT_DISPOSITION = "cd";
     field public static final String CONTENT_ID = "cid";
     field public static final String CONTENT_LOCATION = "cl";
     field public static final String CONTENT_TYPE = "ct";
-    field public static final android.net.Uri CONTENT_URI;
+    field @NonNull public static final android.net.Uri CONTENT_URI;
     field public static final String CT_START = "ctt_s";
     field public static final String CT_TYPE = "ctt_t";
     field public static final String FILENAME = "fn";
@@ -41143,6 +41181,7 @@
     method public void unregisterCallback(android.telecom.Call.Callback);
     field @Deprecated public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
     field public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
+    field public static final String EXTRA_SILENT_RINGING_REQUESTED = "android.telecom.extra.SILENT_RINGING_REQUESTED";
     field public static final String EXTRA_SUGGESTED_PHONE_ACCOUNTS = "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS";
     field public static final int STATE_ACTIVE = 4; // 0x4
     field public static final int STATE_CONNECTING = 9; // 0x9
@@ -41291,6 +41330,7 @@
   public static class CallScreeningService.CallResponse {
     method public boolean getDisallowCall();
     method public boolean getRejectCall();
+    method public boolean getSilenceCall();
     method public boolean getSkipCallLog();
     method public boolean getSkipNotification();
   }
@@ -41300,6 +41340,7 @@
     method public android.telecom.CallScreeningService.CallResponse build();
     method public android.telecom.CallScreeningService.CallResponse.Builder setDisallowCall(boolean);
     method public android.telecom.CallScreeningService.CallResponse.Builder setRejectCall(boolean);
+    method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setSilenceCall(boolean);
     method public android.telecom.CallScreeningService.CallResponse.Builder setSkipCallLog(boolean);
     method public android.telecom.CallScreeningService.CallResponse.Builder setSkipNotification(boolean);
   }
@@ -42101,6 +42142,7 @@
 
   public class CarrierConfigManager {
     method @Nullable public android.os.PersistableBundle getConfig();
+    method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
     method @Nullable public android.os.PersistableBundle getConfigForSubId(int);
     method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
     method public void notifyConfigChangedForSubId(int);
@@ -42278,6 +42320,10 @@
     field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
   }
 
+  public static final class CarrierConfigManager.Ims {
+    field public static final String KEY_PREFIX = "ims.";
+  }
+
   public abstract class CellIdentity implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public CharSequence getOperatorAlphaLong();
@@ -42460,6 +42506,7 @@
     method public int getBitErrorRate();
     method public int getDbm();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+    method public int getRssi();
     method public int getTimingAdvance();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthGsm> CREATOR;
@@ -43033,6 +43080,7 @@
     method public boolean canChangeDtmfToneLength();
     method @Nullable public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
     method public android.telephony.TelephonyManager createForSubscriptionId(int);
+    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot();
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();
     method public int getCallState();
     method public int getCardIdForDefaultEuicc();
@@ -43064,7 +43112,7 @@
     method public String getNetworkOperator();
     method public String getNetworkOperatorName();
     method public String getNetworkSpecifier();
-    method public int getNetworkType();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType();
     method public int getPhoneCount();
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
@@ -70463,7 +70511,7 @@
     method public void logp(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String);
     method public void logp(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @NonNull java.util.function.Supplier<java.lang.String>);
     method public void logp(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String, @Nullable Object);
-    method public void logp(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String, Object[]);
+    method public void logp(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String, @Nullable Object[]);
     method public void logp(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String, @Nullable Throwable);
     method public void logp(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable Throwable, @NonNull java.util.function.Supplier<java.lang.String>);
     method @Deprecated public void logrb(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String, @Nullable String);
diff --git a/api/removed.txt b/api/removed.txt
index f40b146..0795438 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -278,7 +278,7 @@
 package android.icu.util {
 
   public class JapaneseCalendar extends android.icu.util.GregorianCalendar {
-    field public static final int CURRENT_ERA;
+    field @Deprecated public static final int CURRENT_ERA;
   }
 
 }
diff --git a/api/system-current.txt b/api/system-current.txt
index 3170a0b..af07602 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -695,7 +695,7 @@
   }
 
   public static final class UsageEvents.Event {
-    method public String getNotificationChannelId();
+    method @Nullable public String getNotificationChannelId();
     field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc
     field public static final int NOTIFICATION_SEEN = 10; // 0xa
     field public static final int SLICE_PINNED = 14; // 0xe
@@ -1661,7 +1661,7 @@
     method @Deprecated public void setMsgType(int);
     method @Deprecated public void setVersion(int);
     method @Deprecated public void writeToParcel(android.os.Parcel, int);
-    field @Deprecated public static final android.os.Parcelable.Creator<android.hardware.location.ContextHubMessage> CREATOR;
+    field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.ContextHubMessage> CREATOR;
   }
 
   public class ContextHubTransaction<T> {
@@ -1749,7 +1749,7 @@
     method public int getMonitoringType();
     method public int getSourceTechnologies();
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.hardware.location.GeofenceHardwareMonitorEvent> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.GeofenceHardwareMonitorEvent> CREATOR;
   }
 
   public final class GeofenceHardwareRequest {
@@ -1872,7 +1872,7 @@
     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;
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR;
   }
 
   public final class NanoAppState implements android.os.Parcelable {
@@ -3958,9 +3958,9 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
     method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setNfcSecure(boolean);
     field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
   }
 
@@ -4270,6 +4270,7 @@
   public class UpdateEngine {
     ctor public UpdateEngine();
     method public void applyPayload(String, long, long, String[]);
+    method public void applyPayload(java.io.FileDescriptor, long, long, String[]);
     method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler);
     method public boolean bind(android.os.UpdateEngineCallback);
     method public void cancel();
@@ -4651,6 +4652,30 @@
     field public static final String WAIT_TIME_RETRY = "wait_time";
   }
 
+  public static final class Telephony.CellBroadcasts implements android.provider.BaseColumns {
+    field public static final String CID = "cid";
+    field public static final String CMAS_CATEGORY = "cmas_category";
+    field public static final String CMAS_CERTAINTY = "cmas_certainty";
+    field public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
+    field public static final String CMAS_RESPONSE_TYPE = "cmas_response_type";
+    field public static final String CMAS_SEVERITY = "cmas_severity";
+    field public static final String CMAS_URGENCY = "cmas_urgency";
+    field @NonNull public static final android.net.Uri CONTENT_URI;
+    field public static final String DEFAULT_SORT_ORDER = "date DESC";
+    field public static final String DELIVERY_TIME = "date";
+    field public static final String ETWS_WARNING_TYPE = "etws_warning_type";
+    field public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
+    field public static final String LAC = "lac";
+    field public static final String LANGUAGE_CODE = "language";
+    field public static final String MESSAGE_BODY = "body";
+    field public static final String MESSAGE_FORMAT = "format";
+    field public static final String MESSAGE_PRIORITY = "priority";
+    field public static final String MESSAGE_READ = "read";
+    field public static final String PLMN = "plmn";
+    field public static final String SERIAL_NUMBER = "serial_number";
+    field public static final String SERVICE_CATEGORY = "service_category";
+  }
+
   public final class TimeZoneRulesDataContract {
     field public static final String AUTHORITY = "com.android.timezone";
   }
@@ -6226,7 +6251,125 @@
     field public static final int ROAMING_TYPE_UNKNOWN = 1; // 0x1
   }
 
+  public final class SmsCbCmasInfo implements android.os.Parcelable {
+    ctor public SmsCbCmasInfo(int, int, int, int, int, int);
+    method public int describeContents();
+    method public int getCategory();
+    method public int getCertainty();
+    method public int getMessageClass();
+    method public int getResponseType();
+    method public int getSeverity();
+    method public int getUrgency();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CMAS_CATEGORY_CBRNE = 10; // 0xa
+    field public static final int CMAS_CATEGORY_ENV = 7; // 0x7
+    field public static final int CMAS_CATEGORY_FIRE = 5; // 0x5
+    field public static final int CMAS_CATEGORY_GEO = 0; // 0x0
+    field public static final int CMAS_CATEGORY_HEALTH = 6; // 0x6
+    field public static final int CMAS_CATEGORY_INFRA = 9; // 0x9
+    field public static final int CMAS_CATEGORY_MET = 1; // 0x1
+    field public static final int CMAS_CATEGORY_OTHER = 11; // 0xb
+    field public static final int CMAS_CATEGORY_RESCUE = 4; // 0x4
+    field public static final int CMAS_CATEGORY_SAFETY = 2; // 0x2
+    field public static final int CMAS_CATEGORY_SECURITY = 3; // 0x3
+    field public static final int CMAS_CATEGORY_TRANSPORT = 8; // 0x8
+    field public static final int CMAS_CATEGORY_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_CERTAINTY_LIKELY = 1; // 0x1
+    field public static final int CMAS_CERTAINTY_OBSERVED = 0; // 0x0
+    field public static final int CMAS_CERTAINTY_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 3; // 0x3
+    field public static final int CMAS_CLASS_CMAS_EXERCISE = 5; // 0x5
+    field public static final int CMAS_CLASS_EXTREME_THREAT = 1; // 0x1
+    field public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 6; // 0x6
+    field public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0; // 0x0
+    field public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 4; // 0x4
+    field public static final int CMAS_CLASS_SEVERE_THREAT = 2; // 0x2
+    field public static final int CMAS_CLASS_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_RESPONSE_TYPE_ASSESS = 6; // 0x6
+    field public static final int CMAS_RESPONSE_TYPE_AVOID = 5; // 0x5
+    field public static final int CMAS_RESPONSE_TYPE_EVACUATE = 1; // 0x1
+    field public static final int CMAS_RESPONSE_TYPE_EXECUTE = 3; // 0x3
+    field public static final int CMAS_RESPONSE_TYPE_MONITOR = 4; // 0x4
+    field public static final int CMAS_RESPONSE_TYPE_NONE = 7; // 0x7
+    field public static final int CMAS_RESPONSE_TYPE_PREPARE = 2; // 0x2
+    field public static final int CMAS_RESPONSE_TYPE_SHELTER = 0; // 0x0
+    field public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_SEVERITY_EXTREME = 0; // 0x0
+    field public static final int CMAS_SEVERITY_SEVERE = 1; // 0x1
+    field public static final int CMAS_SEVERITY_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_URGENCY_EXPECTED = 1; // 0x1
+    field public static final int CMAS_URGENCY_IMMEDIATE = 0; // 0x0
+    field public static final int CMAS_URGENCY_UNKNOWN = -1; // 0xffffffff
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbCmasInfo> CREATOR;
+  }
+
+  public final class SmsCbEtwsInfo implements android.os.Parcelable {
+    ctor public SmsCbEtwsInfo(int, boolean, boolean, boolean, @Nullable byte[]);
+    method public int describeContents();
+    method @Nullable public byte[] getPrimaryNotificationSignature();
+    method public long getPrimaryNotificationTimestamp();
+    method public int getWarningType();
+    method public boolean isEmergencyUserAlert();
+    method public boolean isPopupAlert();
+    method public boolean isPrimary();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbEtwsInfo> CREATOR;
+    field public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0; // 0x0
+    field public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 2; // 0x2
+    field public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 4; // 0x4
+    field public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 3; // 0x3
+    field public static final int ETWS_WARNING_TYPE_TSUNAMI = 1; // 0x1
+    field public static final int ETWS_WARNING_TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public final class SmsCbLocation implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getCid();
+    method public int getLac();
+    method @NonNull public String getPlmn();
+    method public boolean isInLocationArea(@NonNull android.telephony.SmsCbLocation);
+    method public boolean isInLocationArea(@Nullable String, int, int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbLocation> CREATOR;
+  }
+
+  public final class SmsCbMessage implements android.os.Parcelable {
+    ctor public SmsCbMessage(int, int, int, @NonNull android.telephony.SmsCbLocation, int, @Nullable String, @Nullable String, int, @Nullable android.telephony.SmsCbEtwsInfo, @Nullable android.telephony.SmsCbCmasInfo);
+    method @NonNull public static android.telephony.SmsCbMessage createFromCursor(@NonNull android.database.Cursor);
+    method public int describeContents();
+    method @Nullable public android.telephony.SmsCbCmasInfo getCmasWarningInfo();
+    method @NonNull public android.content.ContentValues getContentValues();
+    method @Nullable public android.telephony.SmsCbEtwsInfo getEtwsWarningInfo();
+    method public int getGeographicalScope();
+    method @Nullable public String getLanguageCode();
+    method @NonNull public android.telephony.SmsCbLocation getLocation();
+    method @Nullable public String getMessageBody();
+    method public int getMessageFormat();
+    method public int getMessagePriority();
+    method public long getReceivedTime();
+    method public int getSerialNumber();
+    method public int getServiceCategory();
+    method public boolean isCmasMessage();
+    method public boolean isEmergencyMessage();
+    method public boolean isEtwsMessage();
+    method public boolean needGeoFencingCheck();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbMessage> CREATOR;
+    field public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3; // 0x3
+    field public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0; // 0x0
+    field public static final int GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE = 2; // 0x2
+    field public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1; // 0x1
+    field public static final int MESSAGE_FORMAT_3GPP = 1; // 0x1
+    field public static final int MESSAGE_FORMAT_3GPP2 = 2; // 0x2
+    field public static final int MESSAGE_PRIORITY_EMERGENCY = 3; // 0x3
+    field public static final int MESSAGE_PRIORITY_INTERACTIVE = 1; // 0x1
+    field public static final int MESSAGE_PRIORITY_NORMAL = 0; // 0x0
+    field public static final int MESSAGE_PRIORITY_URGENT = 2; // 0x2
+  }
+
   public final class SmsManager {
+    method public boolean disableCellBroadcastRange(int, int, int);
+    method public boolean enableCellBroadcastRange(int, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
     field public static final int RESULT_CANCELLED = 23; // 0x17
     field public static final int RESULT_ENCODING_ERROR = 18; // 0x12
@@ -6256,6 +6399,7 @@
   public class SubscriptionManager {
     method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
+    method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
     method public void requestEmbeddedSubscriptionInfoListRefresh();
     method public void requestEmbeddedSubscriptionInfoListRefresh(int);
@@ -6352,7 +6496,6 @@
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRadioOn();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isRebootRequiredForModemConfigChange();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isVideoCallingEnabled();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
@@ -7423,6 +7566,7 @@
   public abstract class ImsFeature {
     ctor public ImsFeature();
     method public abstract void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+    method public final int getSlotIndex();
     method public abstract void onFeatureReady();
     method public abstract void onFeatureRemoved();
     method public final void setFeatureState(int);
@@ -7436,7 +7580,7 @@
     field public static final int STATE_UNAVAILABLE = 0; // 0x0
   }
 
-  @Deprecated public static class ImsFeature.Capabilities {
+  public static class ImsFeature.Capabilities {
     field @Deprecated protected int mCapabilities;
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 075963e..7ea9a26 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1521,6 +1521,11 @@
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setTransportType(int);
   }
 
+  public class PhoneNumberUtils {
+    method public static int getMinMatchForTest();
+    method public static void setMinMatchForTest(int);
+  }
+
   public class ServiceState implements android.os.Parcelable {
     method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
     method public void setCdmaSystemAndNetworkId(int, int);
@@ -1532,6 +1537,15 @@
     method public void setVoiceRoamingType(int);
   }
 
+  public final class SmsManager {
+    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int checkSmsShortCodeDestination(String, String);
+    field public static final int SMS_CATEGORY_FREE_SHORT_CODE = 1; // 0x1
+    field public static final int SMS_CATEGORY_NOT_SHORT_CODE = 0; // 0x0
+    field public static final int SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3; // 0x3
+    field public static final int SMS_CATEGORY_PREMIUM_SHORT_CODE = 4; // 0x4
+    field public static final int SMS_CATEGORY_STANDARD_SHORT_CODE = 2; // 0x2
+  }
+
   public class TelephonyManager {
     method public int getCarrierIdListVersion();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index d387531..f925023 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -6,11 +6,11 @@
     multilib: {
         lib32: {
             version_script: ":art_sigchain_version_script32.txt",
-            stem: "app_process32",
+            suffix: "32",
         },
         lib64: {
             version_script: ":art_sigchain_version_script64.txt",
-            stem: "app_process64",
+            suffix: "64",
         },
     },
 
@@ -21,6 +21,7 @@
         "libbinder",
         "libcutils",
         "libdl",
+        "libhidlbase",
         "libhwbinder",
         "liblog",
         "libnativeloader",
diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp
index 60a1cfb..3e5877b 100644
--- a/cmds/bootanimation/Android.bp
+++ b/cmds/bootanimation/Android.bp
@@ -40,24 +40,6 @@
         "audioplay.cpp",
     ],
 
-    product_variables: {
-        product_is_iot: {
-            shared_libs: [
-                "libandroidthings",
-                "libchrome",
-            ],
-            srcs: [
-                "iot/iotbootanimation_main.cpp",
-                "iot/BootAction.cpp",
-                "iot/BootParameters.cpp",
-            ],
-            exclude_srcs: [
-                "bootanimation_main.cpp",
-                "audioplay.cpp",
-            ],
-        },
-    },
-
     init_rc: ["bootanim.rc"],
 }
 
@@ -76,12 +58,5 @@
         "libEGL",
         "libGLESv1_CM",
         "libgui",
-        "libtinyalsa",
     ],
-
-    product_variables: {
-        product_is_iot: {
-            init_rc: ["iot/bootanim_iot.rc"],
-        },
-    },
 }
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index ed6c25d..95bdc4a 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -63,6 +63,10 @@
 
 #include "BootAnimation.h"
 
+#define ANIM_PATH_MAX 255
+#define STR(x)   #x
+#define STRTO(x) STR(x)
+
 namespace android {
 
 static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
@@ -94,7 +98,7 @@
 static const int TEXT_CENTER_VALUE = INT_MAX;
 static const int TEXT_MISSING_VALUE = INT_MIN;
 static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
-static const int ANIM_ENTRY_NAME_MAX = 256;
+static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
 static constexpr size_t TEXT_POS_LEN_MAX = 16;
 
 // ---------------------------------------------------------------------------
@@ -658,7 +662,7 @@
             animation.width = width;
             animation.height = height;
             animation.fps = fps;
-        } else if (sscanf(l, " %c %d %d %s #%6s %16s %16s",
+        } else if (sscanf(l, " %c %d %d %" STRTO(ANIM_PATH_MAX) "s #%6s %16s %16s",
                           &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) {
             //ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s",
             //    pathType, count, pause, path, color, clockPos1, clockPos2);
diff --git a/cmds/bootanimation/iot/BootAction.cpp b/cmds/bootanimation/iot/BootAction.cpp
deleted file mode 100644
index fa79744..0000000
--- a/cmds/bootanimation/iot/BootAction.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BootAction.h"
-
-#define LOG_TAG "BootAction"
-
-#include <dlfcn.h>
-
-#include <pio/peripheral_manager_client.h>
-#include <utils/Log.h>
-
-namespace android {
-
-BootAction::~BootAction() {
-    if (mLibHandle != nullptr) {
-        dlclose(mLibHandle);
-    }
-}
-
-bool BootAction::init(const std::string& libraryPath,
-                      const std::vector<ABootActionParameter>& parameters) {
-    APeripheralManagerClient* client = nullptr;
-    ALOGD("Connecting to peripheralmanager");
-    // Wait for peripheral manager to come up.
-    while (client == nullptr) {
-        client = APeripheralManagerClient_new();
-        if (client == nullptr) {
-          ALOGV("peripheralmanager is not up, sleeping before we check again.");
-          usleep(250000);
-        }
-    }
-    ALOGD("Peripheralmanager is up.");
-    APeripheralManagerClient_delete(client);
-
-
-    ALOGI("Loading boot action %s", libraryPath.c_str());
-    mLibHandle = dlopen(libraryPath.c_str(), RTLD_NOW);
-    if (mLibHandle == nullptr) {
-        ALOGE("Unable to load library at %s :: %s",
-              libraryPath.c_str(), dlerror());
-        return false;
-    }
-
-    void* loaded = nullptr;
-    if (!loadSymbol("boot_action_init", &loaded) || loaded == nullptr) {
-        return false;
-    }
-    mLibInit = reinterpret_cast<libInit>(loaded);
-
-    loaded = nullptr;
-    if (!loadSymbol("boot_action_shutdown", &loaded) || loaded == nullptr) {
-        return false;
-    }
-    mLibShutdown = reinterpret_cast<libShutdown>(loaded);
-
-    // StartPart is considered optional, if it isn't exported by the library
-    // we will still call init and shutdown.
-    loaded = nullptr;
-    if (!loadSymbol("boot_action_start_part", &loaded) || loaded == nullptr) {
-        ALOGI("No boot_action_start_part found, action will not be told when "
-              "Animation parts change.");
-    } else {
-        mLibStartPart = reinterpret_cast<libStartPart>(loaded);
-    }
-
-    ALOGD("Entering boot_action_init");
-    bool result = mLibInit(parameters.data(), parameters.size());
-    ALOGD("Returned from boot_action_init");
-    return result;
-}
-
-void BootAction::startPart(int partNumber, int playNumber) {
-    if (mLibStartPart == nullptr) return;
-
-    ALOGD("Entering boot_action_start_part");
-    mLibStartPart(partNumber, playNumber);
-    ALOGD("Returned from boot_action_start_part");
-}
-
-void BootAction::shutdown() {
-    ALOGD("Entering boot_action_shutdown");
-    mLibShutdown();
-    ALOGD("Returned from boot_action_shutdown");
-}
-
-bool BootAction::loadSymbol(const char* symbol, void** loaded) {
-    *loaded = dlsym(mLibHandle, symbol);
-    if (loaded == nullptr) {
-        ALOGE("Unable to load symbol : %s :: %s", symbol, dlerror());
-        return false;
-    }
-    return true;
-}
-
-}  // namespace android
diff --git a/cmds/bootanimation/iot/BootAction.h b/cmds/bootanimation/iot/BootAction.h
deleted file mode 100644
index 5e2495f..0000000
--- a/cmds/bootanimation/iot/BootAction.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _BOOTANIMATION_BOOTACTION_H
-#define _BOOTANIMATION_BOOTACTION_H
-
-#include <string>
-#include <vector>
-
-#include <boot_action/boot_action.h>  // libandroidthings native API.
-#include <utils/RefBase.h>
-
-namespace android {
-
-class BootAction : public RefBase {
-public:
-    ~BootAction();
-
-    // libraryPath is a fully qualified path to the target .so library.
-    bool init(const std::string& libraryPath,
-              const std::vector<ABootActionParameter>& parameters);
-
-    // The animation is going to start playing partNumber for the playCount'th
-    // time, update the action as needed.
-    // This is run in the same thread as the boot animation,
-    // you must not block here.
-    void startPart(int partNumber, int playCount);
-
-    // Shutdown the boot action, this will be called shortly before the
-    // process is shut down to allow time for cleanup.
-    void shutdown();
-
-private:
-    typedef bool (*libInit)(const ABootActionParameter* parameters,
-                            size_t num_parameters);
-    typedef void (*libStartPart)(int partNumber, int playNumber);
-    typedef void (*libShutdown)();
-
-    bool loadSymbol(const char* symbol, void** loaded);
-
-    void* mLibHandle = nullptr;
-    libInit mLibInit = nullptr;
-    libStartPart mLibStartPart = nullptr;
-    libShutdown mLibShutdown = nullptr;
-};
-
-}  // namespace android
-
-
-#endif  // _BOOTANIMATION_BOOTACTION_H
diff --git a/cmds/bootanimation/iot/BootParameters.cpp b/cmds/bootanimation/iot/BootParameters.cpp
deleted file mode 100644
index da6ad0d..0000000
--- a/cmds/bootanimation/iot/BootParameters.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BootParameters.h"
-
-#define LOG_TAG "BootParameters"
-
-#include <fcntl.h>
-
-#include <string>
-
-#include <android-base/file.h>
-#include <base/json/json_parser.h>
-#include <base/json/json_reader.h>
-#include <base/json/json_value_converter.h>
-#include <utils/Log.h>
-
-using android::base::RemoveFileIfExists;
-using android::base::ReadFileToString;
-using base::JSONReader;
-using base::JSONValueConverter;
-using base::Value;
-
-namespace android {
-
-namespace {
-
-// Brightness and volume are stored as integer strings in next_boot.json.
-// They are divided by this constant to produce the actual float values in
-// range [0.0, 1.0]. This constant must match its counterpart in
-// DeviceManager.
-constexpr const float kFloatScaleFactor = 1000.0f;
-
-constexpr const char* kNextBootFile = "/data/misc/bootanimation/next_boot.json";
-constexpr const char* kLastBootFile = "/data/misc/bootanimation/last_boot.json";
-
-void swapBootConfigs() {
-    // rename() will fail if next_boot.json doesn't exist, so delete
-    // last_boot.json manually first.
-    std::string err;
-    if (!RemoveFileIfExists(kLastBootFile, &err))
-        ALOGE("Unable to delete last boot file: %s", err.c_str());
-
-    if (rename(kNextBootFile, kLastBootFile) && errno != ENOENT)
-        ALOGE("Unable to swap boot files: %s", strerror(errno));
-
-    int fd = open(kNextBootFile, O_CREAT, DEFFILEMODE);
-    if (fd == -1) {
-        ALOGE("Unable to create next boot file: %s", strerror(errno));
-    } else {
-        // Make next_boot.json writable to everyone so DeviceManagementService
-        // can save saved_parameters there.
-        if (fchmod(fd, DEFFILEMODE))
-            ALOGE("Unable to set next boot file permissions: %s", strerror(errno));
-        close(fd);
-    }
-}
-
-}  // namespace
-
-BootParameters::SavedBootParameters::SavedBootParameters()
-    : brightness(-kFloatScaleFactor), volume(-kFloatScaleFactor) {}
-
-void BootParameters::SavedBootParameters::RegisterJSONConverter(
-        JSONValueConverter<SavedBootParameters>* converter) {
-    converter->RegisterIntField("brightness", &SavedBootParameters::brightness);
-    converter->RegisterIntField("volume", &SavedBootParameters::volume);
-    converter->RegisterRepeatedString("param_names",
-                                      &SavedBootParameters::param_names);
-    converter->RegisterRepeatedString("param_values",
-                                      &SavedBootParameters::param_values);
-}
-
-BootParameters::BootParameters() {
-    swapBootConfigs();
-    loadParameters();
-}
-
-void BootParameters::loadParameters() {
-    std::string contents;
-    if (!ReadFileToString(kLastBootFile, &contents)) {
-        if (errno != ENOENT)
-            ALOGE("Unable to read from %s: %s", kLastBootFile, strerror(errno));
-
-        return;
-    }
-
-    std::unique_ptr<Value> json = JSONReader::Read(contents);
-    if (json.get() == nullptr) {
-        return;
-    }
-
-    JSONValueConverter<SavedBootParameters> converter;
-    if (converter.Convert(*(json.get()), &mRawParameters)) {
-        mBrightness = mRawParameters.brightness / kFloatScaleFactor;
-        mVolume = mRawParameters.volume / kFloatScaleFactor;
-
-        if (mRawParameters.param_names.size() == mRawParameters.param_values.size()) {
-            for (size_t i = 0; i < mRawParameters.param_names.size(); i++) {
-                mParameters.push_back({
-                        .key = mRawParameters.param_names[i]->c_str(),
-                        .value = mRawParameters.param_values[i]->c_str()
-                });
-            }
-        } else {
-            ALOGW("Parameter names and values size mismatch");
-        }
-    }
-}
-
-}  // namespace android
diff --git a/cmds/bootanimation/iot/BootParameters.h b/cmds/bootanimation/iot/BootParameters.h
deleted file mode 100644
index c10bd44..0000000
--- a/cmds/bootanimation/iot/BootParameters.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _BOOTANIMATION_BOOT_PARAMETERS_H_
-#define _BOOTANIMATION_BOOT_PARAMETERS_H_
-
-#include <list>
-#include <vector>
-
-#include <base/json/json_value_converter.h>
-#include <boot_action/boot_action.h>  // libandroidthings native API.
-
-namespace android {
-
-// Provides access to the parameters set by DeviceManager.reboot().
-class BootParameters {
-public:
-    // Constructor loads the parameters for this boot and swaps the param files
-    // to clear the parameters for next boot.
-    BootParameters();
-
-    // Returns true if volume/brightness were explicitly set on reboot.
-    bool hasVolume() const { return mVolume >= 0; }
-    bool hasBrightness() const { return mBrightness >= 0; }
-
-    // Returns volume/brightness in [0,1], or -1 if unset.
-    float getVolume() const { return mVolume; }
-    float getBrightness() const { return mBrightness; }
-
-    // Returns the additional boot parameters that were set on reboot.
-    const std::vector<ABootActionParameter>& getParameters() const { return mParameters; }
-
-private:
-    // Raw boot saved_parameters loaded from .json.
-    struct SavedBootParameters {
-        int brightness;
-        int volume;
-        std::vector<std::unique_ptr<std::string>> param_names;
-        std::vector<std::unique_ptr<std::string>> param_values;
-
-        SavedBootParameters();
-        static void RegisterJSONConverter(
-                ::base::JSONValueConverter<SavedBootParameters>* converter);
-    };
-
-    void loadParameters();
-
-    float mVolume = -1.f;
-    float mBrightness = -1.f;
-    std::vector<ABootActionParameter> mParameters;
-
-    // ABootActionParameter is just a raw pointer so we need to keep the
-    // original strings around to avoid losing them.
-    SavedBootParameters mRawParameters;
-};
-
-}  // namespace android
-
-
-#endif  // _BOOTANIMATION_BOOT_PARAMETERS_H_
diff --git a/cmds/bootanimation/iot/bootanim_iot.rc b/cmds/bootanimation/iot/bootanim_iot.rc
deleted file mode 100644
index 2fc1336..0000000
--- a/cmds/bootanimation/iot/bootanim_iot.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-on post-fs-data
-    mkdir /data/misc/bootanimation 0777 root root
diff --git a/cmds/bootanimation/iot/iotbootanimation_main.cpp b/cmds/bootanimation/iot/iotbootanimation_main.cpp
deleted file mode 100644
index 00cef43..0000000
--- a/cmds/bootanimation/iot/iotbootanimation_main.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "IotBootAnimation"
-
-#include <base/files/file_util.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
-#include <cutils/properties.h>
-#include <sys/resource.h>
-#include <utils/Log.h>
-#include <utils/threads.h>
-#include <BootAnimation.h>
-
-#include "BootAction.h"
-#include "BootAnimationUtil.h"
-#include "BootParameters.h"
-
-using namespace android;
-
-// Create a typedef for readability.
-typedef android::BootAnimation::Animation Animation;
-
-namespace {
-
-constexpr const char* kDefaultLibName = "libbootaction.so";
-
-class BootActionAnimationCallbacks : public android::BootAnimation::Callbacks {
-public:
-    BootActionAnimationCallbacks(std::unique_ptr<BootParameters> bootParameters)
-        : mBootParameters(std::move(bootParameters)) {}
-
-    void init(const Vector<Animation::Part>&) override {
-        std::string library_path("/oem/lib/");
-
-        // This value is optionally provided by the user and will be written to
-        // /oem/oem.prop.
-        char property[PROP_VALUE_MAX] = {0};
-        property_get("ro.oem.bootactions.lib", property, kDefaultLibName);
-        library_path += property;
-
-        if (!::base::PathExists(::base::FilePath(library_path))) {
-            ALOGI("Skipping boot actions: %s does not exist", library_path.c_str());
-            return;
-        }
-
-        mBootAction = new BootAction();
-        if (!mBootAction->init(library_path, mBootParameters->getParameters())) {
-            mBootAction = NULL;
-        }
-    };
-
-    void playPart(int partNumber, const Animation::Part&, int playNumber) override {
-        if (mBootAction != nullptr) {
-            mBootAction->startPart(partNumber, playNumber);
-        }
-    };
-
-    void shutdown() override {
-        if (mBootAction != nullptr) {
-            // If we have a bootaction we want to wait until we are actually
-            // told to shut down. If the animation exits early keep the action
-            // running.
-            char value[PROPERTY_VALUE_MAX] = {0};
-            for (int exitRequested = 0; exitRequested == 0; ) {
-                property_get("service.bootanim.exit", value, "0");
-                exitRequested = atoi(value);
-
-                // Poll value at 10hz.
-                if (exitRequested == 0) {
-                  usleep(100000);
-                }
-            }
-
-            mBootAction->shutdown();
-            // Give it two seconds to shut down.
-            sleep(2);
-            mBootAction = nullptr;
-        }
-    };
-
-private:
-    std::unique_ptr<BootParameters> mBootParameters;
-    sp<BootAction> mBootAction = nullptr;
-};
-
-}  // namespace
-
-int main() {
-    setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
-
-    // Clear our params for next boot no matter what.
-    std::unique_ptr<BootParameters> bootParameters(new BootParameters());
-
-    if (bootAnimationDisabled()) {
-        ALOGI("boot animation disabled");
-        return 0;
-    }
-
-    waitForSurfaceFlinger();
-
-    sp<ProcessState> proc(ProcessState::self());
-    ProcessState::self()->startThreadPool();
-
-    sp<BootAnimation> boot = new BootAnimation(
-            new BootActionAnimationCallbacks(std::move(bootParameters)));
-
-    IPCThreadState::self()->joinThreadPool();
-    return 0;
-}
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp
index d69dd79..847dda3 100644
--- a/cmds/idmap/scan.cpp
+++ b/cmds/idmap/scan.cpp
@@ -9,7 +9,6 @@
 #include <androidfw/ResourceTypes.h>
 #include <androidfw/StreamingZipInflater.h>
 #include <androidfw/ZipFileRO.h>
-#include <cutils/jstring.h>
 #include <cutils/properties.h>
 #include <private/android_filesystem_config.h> // for AID_SYSTEM
 #include <utils/SortedVector.h>
@@ -84,15 +83,9 @@
     }
 
     bool check_property(String16 property, String16 value) {
-        const char *prop;
-        const char *val;
-
-        prop = strndup16to8(property.string(), property.size());
         char propBuf[PROPERTY_VALUE_MAX];
-        property_get(prop, propBuf, NULL);
-        val = strndup16to8(value.string(), value.size());
-
-        return (strcmp(propBuf, val) == 0);
+        property_get(String8(property).c_str(), propBuf, NULL);
+        return String8(value) == propBuf;
     }
 
     int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name,
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index bb5221c..f19f836 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -255,7 +255,9 @@
     unique_fd fd(open(mFilename, O_RDONLY | O_CLOEXEC));
     if (fd.get() == -1) {
         ALOGW("FileSection '%s' failed to open file", this->name.string());
-        return this->deviceSpecific ? NO_ERROR : -errno;
+        // There may be some devices/architectures that won't have the file.
+        // Just return here without an error.
+        return NO_ERROR;
     }
 
     FdBuffer buffer;
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 3c338b3..5d2f38d 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -145,7 +145,7 @@
 
 TEST_F(SectionTest, FileSectionNotExist) {
     FileSection fs1(NOOP_PARSER, "notexist", false, QUICK_TIMEOUT_MS);
-    ASSERT_EQ(NAME_NOT_FOUND, fs1.Execute(&requests));
+    ASSERT_EQ(NO_ERROR, fs1.Execute(&requests));
 
     FileSection fs2(NOOP_PARSER, "notexist", true, QUICK_TIMEOUT_MS);
     ASSERT_EQ(NO_ERROR, fs2.Execute(&requests));
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index f178fa2..20493e7 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -301,7 +301,7 @@
 // ====  java proto device library (for test only)  ==============================
 java_library {
     name: "statsdprotolite",
-    no_framework_libs: true,
+    sdk_version: "core_platform",
     proto: {
         type: "lite",
         include_dirs: ["external/protobuf/src"],
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f04f017..f9b6219 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -139,6 +139,9 @@
         BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = 125;
         BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed = 126;
         BluetoothScoConnectionStateChanged bluetooth_sco_connection_state_changed = 127;
+        AppDowngraded app_downgraded = 128;
+        AppOptimizedAfterDowngraded app_optimized_after_downgraded = 129;
+        LowStorageStateChanged low_storage_state_changed = 130;
         NfcErrorOccurred nfc_error_occurred = 134;
         NfcStateChanged nfc_state_changed = 135;
         NfcBeamOccurred nfc_beam_occurred = 136;
@@ -166,6 +169,7 @@
         BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = 167;
         ProcessStartTime process_start_time = 169;
         BluetoothSocketConnectionStateChanged bluetooth_socket_connection_state_changed = 171;
+        DeviceIdentifierAccessDenied device_identifier_access_denied = 172;
         NetworkStackReported network_stack_reported = 182 [(log_from_module) = "network_stack"];
     }
 
@@ -2007,6 +2011,47 @@
 }
 
 /**
+ * Logs when a volume entered low Storage state.
+ * Logged from:
+ *      frameworks/base/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+ */
+message LowStorageStateChanged {
+    // Volume that ran out of storage.
+    optional string volume_description = 1;
+
+    enum State {
+        UNKNOWN = 0;
+        OFF = 1;
+        ON = 2;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs when an app is downgraded.
+ * Logged from:
+ *      frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+ */
+message AppDowngraded {
+    optional string package_name = 1;
+    // Size of the package (all data) before being downgraded.
+    optional int64 size_in_bytes_before = 2;
+    // Size of the package (all data) after being downgraded.
+    optional int64 size_in_bytes_after = 3;
+
+    optional bool aggressive = 4;
+}
+
+/**
+ * Logs when an app is optimized after being downgraded.
+ * Logged from:
+ *      frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+ */
+message AppOptimizedAfterDowngraded {
+    optional string package_name = 1;
+}
+
+/**
  * Logs when an app crashes.
  * Logged from:
  *      frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2818,7 +2863,7 @@
     // Only valid for event_type = EVENT_RESNSEND.
     optional int32 res_nsend_flags = 5;
 
-    optional android.stats.dnsresolver.Transport network_type = 6;
+    optional android.stats.dnsresolver.NetworkType network_type = 6;
 
     // The DNS over TLS mode on a specific netId.
     optional android.stats.dnsresolver.PrivateDnsModes private_dns_modes = 7;
@@ -2826,6 +2871,9 @@
     // Additional pass-through fields opaque to statsd.
     // The DNS resolver Mainline module can add new fields here without requiring an OS update.
     optional android.stats.dnsresolver.DnsQueryEvents dns_query_events = 8 [(log_mode) = MODE_BYTES];
+
+    // The sample rate of DNS stats (to statsd) is 1/sampling_rate_denom.
+    optional int32 sampling_rate_denom = 9;
 }
 
 /**
@@ -3054,3 +3102,22 @@
     optional android.stats.connectivity.NetworkStackEventData network_stack_event = 2 [(log_mode) = MODE_BYTES];
 }
 
+/**
+ * Logs when a package is denied access to a device identifier based on the new access requirements.
+ *
+ * Logged from:
+ *     frameworks/base/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+ */
+message DeviceIdentifierAccessDenied {
+    // The name of the package denied access to the requested device identifier.
+    optional string package_name = 1;
+
+    // The name of the device identifier method the package attempted to invoke.
+    optional string method_name = 2;
+
+    // True if the package is preinstalled.
+    optional bool is_preinstalled = 3;
+
+    // True if the package is privileged.
+    optional bool is_priv_app = 4;
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp
index b29e979..8233eee 100644
--- a/cmds/statsd/src/external/StatsPuller.cpp
+++ b/cmds/statsd/src/external/StatsPuller.cpp
@@ -35,8 +35,14 @@
 // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
 StatsPuller::StatsPuller(const int tagId)
     : mTagId(tagId) {
-    mCoolDownNs = StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId)->second.coolDownNs;
-    VLOG("Puller for tag %d created. Cooldown set to %lld", mTagId, (long long)mCoolDownNs);
+    auto pullAtomInfo = StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId);
+    if (pullAtomInfo != StatsPullerManagerImpl::kAllPullAtomInfo.end()) {
+        mCoolDownNs = pullAtomInfo->second.coolDownNs;
+        VLOG("Puller for tag %d created. Cooldown set to %lld", mTagId, (long long)mCoolDownNs);
+    } else {
+        mCoolDownNs = 0;
+        VLOG("Creating puller for a non-recognised tag %d.", mTagId);
+    }
 }
 
 bool StatsPuller::Pull(const int64_t elapsedTimeNs, std::vector<std::shared_ptr<LogEvent>>* data) {
diff --git a/packages/ExtServices/MODULE_LICENSE_APACHE2 b/config/boot-profile.txt
similarity index 100%
rename from packages/ExtServices/MODULE_LICENSE_APACHE2
rename to config/boot-profile.txt
diff --git a/config/hiddenapi-greylist-max-p.txt b/config/hiddenapi-greylist-max-p.txt
index f201063..a32fbec 100644
--- a/config/hiddenapi-greylist-max-p.txt
+++ b/config/hiddenapi-greylist-max-p.txt
@@ -68,6 +68,8 @@
 Lcom/android/internal/R$styleable;->Searchable:[I
 Lcom/android/internal/R$styleable;->SearchableActionKey:[I
 Lcom/android/internal/telephony/IPhoneSubInfo$Stub;-><init>()V
+Lcom/android/internal/telephony/ITelephony;->getDataActivity()I
+Lcom/android/internal/telephony/ITelephony;->getDataState()I
 Lcom/android/internal/telephony/ITelephonyRegistry;->notifyCallForwardingChanged(Z)V
 Lcom/android/internal/telephony/ITelephonyRegistry;->notifyCellLocation(Landroid/os/Bundle;)V
 Lcom/android/internal/telephony/ITelephonyRegistry;->notifyDataActivity(I)V
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 8ccbc907..f3fa991 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -699,30 +699,6 @@
 Landroid/telephony/mbms/IMbmsStreamingSessionCallback$Stub;-><init>()V
 Landroid/telephony/mbms/IStreamingServiceCallback$Stub;-><init>()V
 Landroid/telephony/mbms/vendor/IMbmsStreamingService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/telephony/mbms/vendor/IMbmsStreamingService;
-Landroid/telephony/SmsCbCmasInfo;->getCategory()I
-Landroid/telephony/SmsCbCmasInfo;->getCertainty()I
-Landroid/telephony/SmsCbCmasInfo;->getMessageClass()I
-Landroid/telephony/SmsCbCmasInfo;->getResponseType()I
-Landroid/telephony/SmsCbCmasInfo;->getSeverity()I
-Landroid/telephony/SmsCbCmasInfo;->getUrgency()I
-Landroid/telephony/SmsCbEtwsInfo;->getWarningType()I
-Landroid/telephony/SmsCbLocation;-><init>(Ljava/lang/String;)V
-Landroid/telephony/SmsCbLocation;-><init>(Ljava/lang/String;II)V
-Landroid/telephony/SmsCbLocation;->getCid()I
-Landroid/telephony/SmsCbLocation;->getLac()I
-Landroid/telephony/SmsCbLocation;->getPlmn()Ljava/lang/String;
-Landroid/telephony/SmsCbMessage;-><init>(Landroid/os/Parcel;)V
-Landroid/telephony/SmsCbMessage;->getCmasWarningInfo()Landroid/telephony/SmsCbCmasInfo;
-Landroid/telephony/SmsCbMessage;->getEtwsWarningInfo()Landroid/telephony/SmsCbEtwsInfo;
-Landroid/telephony/SmsCbMessage;->getGeographicalScope()I
-Landroid/telephony/SmsCbMessage;->getLanguageCode()Ljava/lang/String;
-Landroid/telephony/SmsCbMessage;->getLocation()Landroid/telephony/SmsCbLocation;
-Landroid/telephony/SmsCbMessage;->getMessageBody()Ljava/lang/String;
-Landroid/telephony/SmsCbMessage;->getMessageFormat()I
-Landroid/telephony/SmsCbMessage;->getSerialNumber()I
-Landroid/telephony/SmsCbMessage;->getServiceCategory()I
-Landroid/telephony/SmsCbMessage;->isCmasMessage()Z
-Landroid/telephony/SmsCbMessage;->isEmergencyMessage()Z
 Landroid/telephony/TelephonyManager$MultiSimVariants;->values()[Landroid/telephony/TelephonyManager$MultiSimVariants;
 Landroid/util/Singleton;-><init>()V
 Landroid/view/accessibility/IAccessibilityManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d9c82ea..8299492 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -168,6 +168,8 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -703,6 +705,8 @@
 
         boolean autofillCompatibilityEnabled;
 
+        long[] disabledCompatChanges;
+
         public String toString() {
             return "AppBindData{appInfo=" + appInfo + "}";
         }
@@ -920,7 +924,8 @@
                 boolean enableBinderTracking, boolean trackAllocation,
                 boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                 CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
-                String buildSerial, boolean autofillCompatibilityEnabled) {
+                String buildSerial, boolean autofillCompatibilityEnabled,
+                long[] disabledCompatChanges) {
 
             if (services != null) {
                 if (false) {
@@ -968,6 +973,7 @@
             data.initProfilerInfo = profilerInfo;
             data.buildSerial = buildSerial;
             data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
+            data.disabledCompatChanges = disabledCompatChanges;
             sendMessage(H.BIND_APPLICATION, data);
         }
 
@@ -5670,6 +5676,7 @@
         // Note when this process has started.
         Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
 
+        AppCompatCallbacks.install(data.disabledCompatChanges);
         mBoundApplication = data;
         mConfiguration = new Configuration(data.config);
         mCompatConfiguration = new Configuration(data.config);
@@ -5898,6 +5905,26 @@
         NetworkSecurityConfigProvider.install(appContext);
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
+
+        if (isAppDebuggable) {
+            try {
+                // Load all the agents in the code_cache/startup_agents directory.
+                // We pass the absolute path to the data_dir as an argument.
+                Path startup_path = appContext.getCodeCacheDir().toPath().resolve("startup_agents");
+                if (Files.exists(startup_path)) {
+                    for (Path p : Files.newDirectoryStream(startup_path)) {
+                        handleAttachAgent(
+                                p.toAbsolutePath().toString()
+                                + "="
+                                + appContext.getDataDir().toPath().toAbsolutePath().toString(),
+                                data.info);
+                    }
+                }
+            } catch (Exception e) {
+                // Ignored.
+            }
+        }
+
         // Continue loading instrumentation.
         if (ii != null) {
             ApplicationInfo instrApp;
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
new file mode 100644
index 0000000..17697db
--- /dev/null
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.compat.Compatibility;
+import android.os.Process;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * App process implementation of the {@link Compatibility} API.
+ *
+ * @hide
+ */
+public final class AppCompatCallbacks extends Compatibility.Callbacks {
+
+    private static final String TAG = "Compatibility";
+
+    private final long[] mDisabledChanges;
+
+    /**
+     * Install this class into the current process.
+     *
+     * @param disabledChanges Set of compatibility changes that are disabled for this process.
+     */
+    public static void install(long[] disabledChanges) {
+        Compatibility.setCallbacks(new AppCompatCallbacks(disabledChanges));
+    }
+
+    private AppCompatCallbacks(long[] disabledChanges) {
+        mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
+        Arrays.sort(mDisabledChanges);
+    }
+
+    protected void reportChange(long changeId) {
+        Log.d(TAG, "Compat change reported: " + changeId + "; UID " + Process.myUid());
+        // TODO log via StatsLog
+    }
+
+    protected boolean isChangeEnabled(long changeId) {
+        if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
+            // Not present in the disabled array
+            reportChange(changeId);
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index d478cd6..1f45fc5 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -67,7 +67,8 @@
             int debugMode, boolean enableBinderTracking, boolean trackAllocation,
             boolean restrictedBackupMode, boolean persistent, in Configuration config,
             in CompatibilityInfo compatInfo, in Map services,
-            in Bundle coreSettings, in String buildSerial, boolean isAutofillCompatEnabled);
+            in Bundle coreSettings, in String buildSerial, boolean isAutofillCompatEnabled,
+            in long[] disabledCompatChanges);
     void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs);
     void scheduleExit();
     void scheduleServiceArgs(IBinder token, in ParceledListSlice args);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 3603b56..84576d9 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -862,7 +862,7 @@
             }
         }
 
-        // /aepx/com.android.runtime/lib, /vendor/lib, /odm/lib and /product/lib
+        // /apex/com.android.art/lib, /vendor/lib, /odm/lib and /product/lib
         // are added to the native lib search paths of the classloader.
         // Note that this is done AFTER the classloader is
         // created by ApplicationLoaders.getDefault().getClassLoader(...). The
@@ -883,8 +883,8 @@
         // (linker namespace).
         List<String> extraLibPaths = new ArrayList<>(4);
         String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : "";
-        if (!defaultSearchPaths.contains("/apex/com.android.runtime/lib")) {
-            extraLibPaths.add("/apex/com.android.runtime/lib" + abiSuffix);
+        if (!defaultSearchPaths.contains("/apex/com.android.art/lib")) {
+            extraLibPaths.add("/apex/com.android.art/lib" + abiSuffix);
         }
         if (!defaultSearchPaths.contains("/vendor/lib")) {
             extraLibPaths.add("/vendor/lib" + abiSuffix);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f6b7eef..94aa481 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -149,7 +149,8 @@
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.EuiccCardManager;
 import android.telephony.euicc.EuiccManager;
-import android.telephony.ims.RcsManager;
+import android.telephony.ims.RcsMessageManager;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
@@ -171,7 +172,7 @@
 import com.android.internal.os.IDropBoxManagerService;
 import com.android.internal.policy.PhoneLayoutInflater;
 
-import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Manages all of the system services that can be returned by {@link Context#getSystemService}.
@@ -182,10 +183,10 @@
 
     // Service registry information.
     // This information is never changed once static initialization has completed.
-    private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
-            new HashMap<Class<?>, String>();
-    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
-            new HashMap<String, ServiceFetcher<?>>();
+    private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES =
+            new ArrayMap<Class<?>, String>();
+    private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
+            new ArrayMap<String, ServiceFetcher<?>>();
     private static int sServiceCacheSize;
 
     // Not instantiable.
@@ -551,11 +552,11 @@
                 return new SubscriptionManager(ctx.getOuterContext());
             }});
 
-        registerService(Context.TELEPHONY_RCS_SERVICE, RcsManager.class,
-                new CachedServiceFetcher<RcsManager>() {
+        registerService(Context.TELEPHONY_RCS_MESSAGE_SERVICE, RcsMessageManager.class,
+                new CachedServiceFetcher<RcsMessageManager>() {
                     @Override
-                    public RcsManager createService(ContextImpl ctx) {
-                        return new RcsManager(ctx.getOuterContext());
+                    public RcsMessageManager createService(ContextImpl ctx) {
+                        return new RcsMessageManager(ctx.getOuterContext());
                     }
                 });
 
diff --git a/core/java/android/app/Vr2dDisplayProperties.java b/core/java/android/app/Vr2dDisplayProperties.java
index e0b60e0..6273e9b 100644
--- a/core/java/android/app/Vr2dDisplayProperties.java
+++ b/core/java/android/app/Vr2dDisplayProperties.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -63,6 +65,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "Vr2dDisplayProperties{"
@@ -75,7 +78,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e2322f3..79a23d6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5654,6 +5654,31 @@
     }
 
     /**
+     * Returns whether the specified package can read the device identifiers.
+     *
+     * @param packageName The package name of the app to check for device identifier access.
+     * @param pid The process id of the package to be checked.
+     * @param uid The uid of the package to be checked.
+     * @return whether the package can read the device identifiers.
+     *
+     * @hide
+     */
+    public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) {
+        throwIfParentInstance("checkDeviceIdentifierAccess");
+        if (packageName == null) {
+            return false;
+        }
+        if (mService != null) {
+            try {
+                return mService.checkDeviceIdentifierAccess(packageName, pid, uid);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * @hide
      * @return the human readable name of the organisation associated with this DPM or {@code null}
      *         if one is not set.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 0e95e639..d74943a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -146,6 +146,7 @@
     int getDeviceOwnerUserId();
 
     boolean setProfileOwner(in ComponentName who, String ownerName, int userHandle);
+    ComponentName getProfileOwnerAsUser(int userHandle);
     ComponentName getProfileOwner(int userHandle);
     String getProfileOwnerName(int userHandle);
     void setProfileEnabled(in ComponentName who);
@@ -153,6 +154,8 @@
     void clearProfileOwner(in ComponentName who);
     boolean hasUserSetupCompleted();
 
+    boolean checkDeviceIdentifierAccess(in String packageName, int pid, int uid);
+
     void setDeviceOwnerLockScreenInfo(in ComponentName who, CharSequence deviceOwnerInfo);
     CharSequence getDeviceOwnerLockScreenInfo();
 
diff --git a/core/java/android/app/backup/OWNERS b/core/java/android/app/backup/OWNERS
index 1c9a43a..9c21e8f 100644
--- a/core/java/android/app/backup/OWNERS
+++ b/core/java/android/app/backup/OWNERS
@@ -1,7 +1,9 @@
-artikz@google.com
+alsutton@google.com
+anniemeng@google.com
 brufino@google.com
 bryanmawhinney@google.com
 ctate@google.com
 jorlow@google.com
-mkarpinski@google.com
+nathch@google.com
+rthakohov@google.com
 
diff --git a/core/java/android/app/backup/RestoreDescription.java b/core/java/android/app/backup/RestoreDescription.java
index 0250326..693fd0d 100644
--- a/core/java/android/app/backup/RestoreDescription.java
+++ b/core/java/android/app/backup/RestoreDescription.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -52,6 +53,7 @@
     /** This package's restore data is a tarball-type full data stream */
     public static final int TYPE_FULL_STREAM = 2;
 
+    @NonNull
     @Override
     public String toString() {
         return "RestoreDescription{" + mPackageName + " : "
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 556ffa24..1c1fad3 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -1592,5 +1592,15 @@
             }
             return new JobInfo(this);
         }
+
+        /**
+         * @hide
+         */
+        public String summarize() {
+            final String service = (mJobService != null)
+                    ? mJobService.flattenToShortString()
+                    : "null";
+            return "JobInfo.Builder{job:" + mJobId + "/" + service + "}";
+        }
     }
 }
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
index 1d5c2b0..17fbdf7 100644
--- a/core/java/android/app/usage/CacheQuotaHint.java
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -81,7 +81,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o instanceof CacheQuotaHint) {
             final CacheQuotaHint other = (CacheQuotaHint) o;
             return Objects.equals(mUuid, other.mUuid)
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 4864ece..a38111a 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -16,6 +16,7 @@
 package android.app.usage;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.res.Configuration;
@@ -366,6 +367,7 @@
          * event is of type {@link #NOTIFICATION_INTERRUPTION}, otherwise it returns null;
          * @hide
          */
+        @Nullable
         @SystemApi
         public String getNotificationChannelId() {
             return mNotificationChannelId;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 31bbd16..e7ba85a 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -858,7 +858,10 @@
         if (DBG) {
             Log.d(TAG, "isLeEnabled(): " + BluetoothAdapter.nameForState(state));
         }
-        return (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON);
+        return (state == BluetoothAdapter.STATE_ON
+                || state == BluetoothAdapter.STATE_BLE_ON
+                || state == BluetoothAdapter.STATE_TURNING_ON
+                || state == BluetoothAdapter.STATE_TURNING_OFF);
     }
 
     /**
@@ -1028,7 +1031,8 @@
      */
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     @AdapterState
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(publicAlternatives = "Use {@link #getState()} instead to determine "
+            + "whether you can use BLE & BT classic.")
     public int getLeState() {
         int state = BluetoothAdapter.STATE_OFF;
 
@@ -1484,7 +1488,8 @@
      * @return true if the scan mode was set, false otherwise
      * @hide
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(publicAlternatives = "Use {@link #ACTION_REQUEST_DISCOVERABLE}, which "
+            + "shows UI that confirms the user wants to go into discoverable mode.")
     public boolean setScanMode(@ScanMode int mode, int duration) {
         if (getState() != STATE_ON) {
             return false;
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index 79c0a3a..c79df17 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -428,6 +428,43 @@
     }
 
     /**
+     * Checks whether a value set presented by a bitmask has zero or single bit
+     *
+     * @param valueSet the value set presented by a bitmask
+     * @return true if the valueSet contains zero or single bit, otherwise false.
+     */
+    private static boolean hasSingleBit(int valueSet) {
+        return (valueSet == 0 || (valueSet & (valueSet - 1)) == 0);
+    }
+
+    /**
+     * Checks whether the object contains none or single sample rate.
+     *
+     * @return true if the object contains none or single sample rate, otherwise false.
+     */
+    public boolean hasSingleSampleRate() {
+        return hasSingleBit(mSampleRate);
+    }
+
+    /**
+     * Checks whether the object contains none or single bits per sample.
+     *
+     * @return true if the object contains none or single bits per sample, otherwise false.
+     */
+    public boolean hasSingleBitsPerSample() {
+        return hasSingleBit(mBitsPerSample);
+    }
+
+    /**
+     * Checks whether the object contains none or single channel mode.
+     *
+     * @return true if the object contains none or single channel mode, otherwise false.
+     */
+    public boolean hasSingleChannelMode() {
+        return hasSingleBit(mChannelMode);
+    }
+
+    /**
      * Checks whether the audio feeding parameters are same.
      *
      * @param other the codec config to compare against
@@ -438,4 +475,58 @@
                 && other.mBitsPerSample == mBitsPerSample
                 && other.mChannelMode == mChannelMode);
     }
+
+    /**
+     * Checks whether another codec config has the similar feeding parameters.
+     * Any parameters with NONE value will be considered to be a wildcard matching.
+     *
+     * @param other the codec config to compare against
+     * @return true if the audio feeding parameters are similar, otherwise false.
+     */
+    public boolean similarCodecFeedingParameters(BluetoothCodecConfig other) {
+        if (other == null || mCodecType != other.mCodecType) {
+            return false;
+        }
+        int sampleRate = other.mSampleRate;
+        if (mSampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE
+                || sampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE) {
+            sampleRate = mSampleRate;
+        }
+        int bitsPerSample = other.mBitsPerSample;
+        if (mBitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE
+                || bitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) {
+            bitsPerSample = mBitsPerSample;
+        }
+        int channelMode = other.mChannelMode;
+        if (mChannelMode == BluetoothCodecConfig.CHANNEL_MODE_NONE
+                || channelMode == BluetoothCodecConfig.CHANNEL_MODE_NONE) {
+            channelMode = mChannelMode;
+        }
+        return sameAudioFeedingParameters(new BluetoothCodecConfig(
+                mCodecType, /* priority */ 0, sampleRate, bitsPerSample, channelMode,
+                /* specific1 */ 0, /* specific2 */ 0, /* specific3 */ 0,
+                /* specific4 */ 0));
+    }
+
+    /**
+     * Checks whether the codec specific parameters are the same.
+     *
+     * @param other the codec config to compare against
+     * @return true if the codec specific parameters are the same, otherwise false.
+     */
+    public boolean sameCodecSpecificParameters(BluetoothCodecConfig other) {
+        if (other == null && mCodecType != other.mCodecType) {
+            return false;
+        }
+        // Currently we only care about the LDAC Playback Quality at CodecSpecific1
+        switch (mCodecType) {
+            case SOURCE_CODEC_TYPE_LDAC:
+                if (mCodecSpecific1 != other.mCodecSpecific1) {
+                    return false;
+                }
+                // fall through
+            default:
+                return true;
+        }
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java
index 32bb681..8237d6a 100644
--- a/core/java/android/bluetooth/BluetoothCodecStatus.java
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.java
@@ -88,6 +88,43 @@
         return Arrays.asList(c1).containsAll(Arrays.asList(c2));
     }
 
+    /**
+     * Checks whether the codec config matches the selectable capabilities.
+     * Any parameters of the codec config with NONE value will be considered a wildcard matching.
+     *
+     * @param codecConfig the codec config to compare against
+     * @return true if the codec config matches, otherwise false
+     */
+    public boolean isCodecConfigSelectable(BluetoothCodecConfig codecConfig) {
+        if (codecConfig == null || !codecConfig.hasSingleSampleRate()
+                || !codecConfig.hasSingleBitsPerSample() || !codecConfig.hasSingleChannelMode()) {
+            return false;
+        }
+        for (BluetoothCodecConfig selectableConfig : mCodecsSelectableCapabilities) {
+            if (codecConfig.getCodecType() != selectableConfig.getCodecType()) {
+                continue;
+            }
+            int sampleRate = codecConfig.getSampleRate();
+            if ((sampleRate & selectableConfig.getSampleRate()) == 0
+                    && sampleRate != BluetoothCodecConfig.SAMPLE_RATE_NONE) {
+                continue;
+            }
+            int bitsPerSample = codecConfig.getBitsPerSample();
+            if ((bitsPerSample & selectableConfig.getBitsPerSample()) == 0
+                    && bitsPerSample != BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) {
+                continue;
+            }
+            int channelMode = codecConfig.getChannelMode();
+            if ((channelMode & selectableConfig.getChannelMode()) == 0
+                    && channelMode != BluetoothCodecConfig.CHANNEL_MODE_NONE) {
+                continue;
+            }
+            return true;
+        }
+        return false;
+    }
+
+
     @Override
     public int hashCode() {
         return Objects.hash(mCodecConfig, mCodecsLocalCapabilities,
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 34c7372..ee33103 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1051,7 +1051,7 @@
      * @return the Bluetooth alias, or null if no alias or there was a problem
      * @hide
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(publicAlternatives = "Use {@link #getName()} instead.")
     public String getAlias() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1100,7 +1100,7 @@
      * @see #getAlias()
      * @see #getName()
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(publicAlternatives = "Use {@link #getName()} instead.")
     public String getAliasName() {
         String name = getAlias();
         if (name == null) {
@@ -1975,7 +1975,8 @@
      * permissions.
      * @hide
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(publicAlternatives = "Use "
+            + "{@link #createInsecureRfcommSocketToServiceRecord} instead.")
     public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
         if (!isBluetoothEnabled()) {
             Log.e(TAG, "Bluetooth is not enabled");
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 9862a63..672174f 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -280,6 +280,7 @@
      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
      */
+    public static final int STATE_AUDIO_CONNECTED = 12;
 
     /**
      * Intent used to broadcast the headset's indicator status
@@ -322,8 +323,6 @@
     public static final String EXTRA_HF_INDICATORS_IND_VALUE =
             "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
 
-    public static final int STATE_AUDIO_CONNECTED = 12;
-
     private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
     private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
 
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index 05833b5..5d00f09 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -126,6 +126,17 @@
             "android.bluetooth.headsetclient.profile.action.RESULT";
 
     /**
+     * Intent that notifies about vendor specific event arrival. Events not defined in
+     * HFP spec will be matched with supported vendor event list and this intent will
+     * be broadcasted upon a match. Supported vendor events are of format of
+     * of "+eventCode" or "+eventCode=xxxx" or "+eventCode:=xxxx".
+     * Vendor event can be a response to an vendor specific command or unsolicited.
+     *
+     */
+    public static final String ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT =
+            "android.bluetooth.headsetclient.profile.action.VENDOR_SPECIFIC_EVENT";
+
+    /**
      * Intent that notifies about the number attached to the last voice tag
      * recorded on AG.
      *
@@ -243,6 +254,28 @@
     public static final String EXTRA_CME_CODE =
             "android.bluetooth.headsetclient.extra.CME_CODE";
 
+    /**
+     * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
+     * indicates vendor ID.
+     */
+    public static final String EXTRA_VENDOR_ID =
+            "android.bluetooth.headsetclient.extra.VENDOR_ID";
+
+     /**
+     * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
+     * indicates vendor event code.
+     */
+    public static final String EXTRA_VENDOR_EVENT_CODE =
+            "android.bluetooth.headsetclient.extra.VENDOR_EVENT_CODE";
+
+     /**
+     * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
+     * contains full vendor event including event code and full arguments.
+     */
+    public static final String EXTRA_VENDOR_EVENT_FULL_ARGS =
+            "android.bluetooth.headsetclient.extra.VENDOR_EVENT_FULL_ARGS";
+
+
     /* Extras for AG_FEATURES, extras type is boolean */
     // TODO verify if all of those are actually useful
     /**
@@ -588,6 +621,31 @@
     }
 
     /**
+     * Send vendor specific AT command.
+     *
+     * @param device remote device
+     * @param vendorId vendor number by Bluetooth SIG
+     * @param atCommand command to be sent. It start with + prefix and only one command at one time.
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise.
+     */
+    public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId,
+                                             String atCommand) {
+        if (DBG) log("sendVendorSpecificCommand()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.sendVendorAtCommand(device, vendorId, atCommand);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
      * Stops voice recognition.
      *
      * @param device remote device
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index ec0180c5..69682c6 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -53,6 +53,10 @@
      * NOTE: HANDLE is only valid for a single session with the device. */
     public static final String EXTRA_MESSAGE_HANDLE =
             "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
+    public static final String EXTRA_MESSAGE_TIMESTAMP =
+            "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP";
+    public static final String EXTRA_MESSAGE_READ_STATUS =
+            "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS";
     public static final String EXTRA_SENDER_CONTACT_URI =
             "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
     public static final String EXTRA_SENDER_CONTACT_NAME =
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index fb78789..cfb363a08 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -118,6 +118,8 @@
      */
     public static final int PAN_OPERATION_SUCCESS = 1004;
 
+    private final Context mContext;
+
     private BluetoothAdapter mAdapter;
     private final BluetoothProfileConnector<IBluetoothPan> mProfileConnector =
             new BluetoothProfileConnector(this, BluetoothProfile.PAN,
@@ -136,6 +138,7 @@
     @UnsupportedAppUsage
     /*package*/ BluetoothPan(Context context, ServiceListener listener) {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mContext = context;
         mProfileConnector.connect(context, listener);
     }
 
@@ -287,11 +290,12 @@
 
     @UnsupportedAppUsage
     public void setBluetoothTethering(boolean value) {
-        if (DBG) log("setBluetoothTethering(" + value + ")");
+        String pkgName = mContext.getOpPackageName();
+        if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName);
         final IBluetoothPan service = getService();
         if (service != null && isEnabled()) {
             try {
-                service.setBluetoothTethering(value);
+                service.setBluetoothTethering(value, pkgName);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             }
diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java
index d9987249..863fd36 100644
--- a/core/java/android/bluetooth/BluetoothProfileConnector.java
+++ b/core/java/android/bluetooth/BluetoothProfileConnector.java
@@ -32,12 +32,12 @@
  * @hide
  */
 public abstract class BluetoothProfileConnector<T> {
-    private int mProfileId;
+    private final int mProfileId;
     private BluetoothProfile.ServiceListener mServiceListener;
-    private BluetoothProfile mProfileProxy;
+    private final BluetoothProfile mProfileProxy;
     private Context mContext;
-    private String mProfileName;
-    private String mServiceName;
+    private final String mProfileName;
+    private final String mServiceName;
     private volatile T mService;
 
     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
@@ -65,7 +65,7 @@
             logDebug("Proxy object disconnected");
             doUnbind();
             if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP);
+                mServiceListener.onServiceDisconnected(mProfileId);
             }
         }
     };
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index c06b837..3a23808 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -77,7 +77,8 @@
 
     private static final String TAG = "BluetoothServerSocket";
     private static final boolean DBG = false;
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(publicAlternatives = "Use public {@link BluetoothServerSocket} API "
+            + "instead.")
     /*package*/ final BluetoothSocket mSocket;
     private Handler mHandler;
     private int mMessage;
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 3a1e2f5..a6e3153 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -131,7 +131,7 @@
     private boolean mExcludeSdp = false; /* when true no SPP SDP record will be created */
     private boolean mAuthMitm = false;   /* when true Man-in-the-middle protection will be enabled*/
     private boolean mMin16DigitPin = false; /* Minimum 16 digit pin for sec mode 2 connections */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(publicAlternatives = "Use {@link BluetoothSocket} public API instead.")
     private ParcelFileDescriptor mPfd;
     @UnsupportedAppUsage
     private LocalSocket mSocket;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9223f71..7baca9b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -71,6 +71,8 @@
 import android.view.autofill.AutofillManager.AutofillClient;
 import android.view.textclassifier.TextClassificationManager;
 
+import com.android.internal.compat.IPlatformCompat;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -2948,7 +2950,7 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(trackingBug = 136728678)
     public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
             Handler handler, UserHandle user) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
@@ -3037,6 +3039,7 @@
             TELEPHONY_SERVICE,
             TELEPHONY_SUBSCRIPTION_SERVICE,
             CARRIER_CONFIG_SERVICE,
+            EUICC_SERVICE,
             TELECOM_SERVICE,
             CLIPBOARD_SERVICE,
             INPUT_METHOD_SERVICE,
@@ -3063,6 +3066,7 @@
             RESTRICTIONS_SERVICE,
             APP_OPS_SERVICE,
             CAMERA_SERVICE,
+            //@hide: PLATFORM_COMPAT_SERVICE,
             PRINT_SERVICE,
             CONSUMER_IR_SERVICE,
             //@hide: TRUST_SERVICE,
@@ -3216,6 +3220,8 @@
      * @see android.telephony.SubscriptionManager
      * @see #CARRIER_CONFIG_SERVICE
      * @see android.telephony.CarrierConfigManager
+     * @see #EUICC_SERVICE
+     * @see android.telephony.euicc.EuiccManager
      * @see #INPUT_METHOD_SERVICE
      * @see android.view.inputmethod.InputMethodManager
      * @see #UI_MODE_SERVICE
@@ -4223,6 +4229,13 @@
     public static final String STATS_MANAGER = "stats";
 
     /**
+     * Use with {@link android.os.ServiceManager.getService()} to retrieve a
+     * {@link IPlatformCompat} IBinder for communicating with the platform compat service.
+     * @hide
+     */
+    public static final String PLATFORM_COMPAT_SERVICE = "platform_compat";
+
+    /**
      * Service to capture a bugreport.
      * @see #getSystemService(String)
      * @see android.os.BugreportManager
@@ -4290,10 +4303,10 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve an
-     * {@link android.telephony.ims.RcsManager}.
+     * {@link android.telephony.ims.RcsMessageManager}.
      * @hide
      */
-    public static final String TELEPHONY_RCS_SERVICE = "ircs";
+    public static final String TELEPHONY_RCS_MESSAGE_SERVICE = "ircsmessage";
 
      /**
      * Use with {@link #getSystemService(String)} to retrieve an
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b879047..43cbfe9 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -52,6 +52,8 @@
 import android.provider.DocumentsProvider;
 import android.provider.MediaStore;
 import android.provider.OpenableColumns;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -1108,6 +1110,12 @@
      * <p>Input: {@link #getData} is URI of a phone number to be dialed or a
      * tel: URI of an explicit phone number.
      * <p>Output: nothing.
+     *
+     * <p class="note"><strong>Note:</strong> It is not guaranteed that the call will be placed on
+     * the {@link PhoneAccount} provided in the {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE}
+     * extra (if specified) and may be placed on another {@link PhoneAccount} with the
+     * {@link PhoneAccount#CAPABILITY_PLACE_EMERGENCY_CALLS} capability, depending on external
+     * factors, such as network conditions and Modem/SIM status.
      * @hide
      */
     @SystemApi
@@ -2999,7 +3007,10 @@
      *
      * @deprecated Apps that redirect outgoing calls should use the
      * {@link android.telecom.CallRedirectionService} API.  Apps that perform call screening
-     * should use the {@link android.telecom.CallScreeningService} API.
+     * should use the {@link android.telecom.CallScreeningService} API.  Apps which need to be
+     * notified of basic call state should use
+     * {@link android.telephony.PhoneStateListener#onCallStateChanged(int, String)} to determine
+     * when a new outgoing call is placed.
      */
     @Deprecated
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index dd55003..2884dcb 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -310,7 +311,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -342,6 +343,7 @@
         return true;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "OverlayInfo { overlay=" + packageName + ", target=" + targetPackageName + ", state="
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index cd8dc63..965b064 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -556,7 +556,7 @@
      * @hide
      */
     public PackageInfo(ApexInfo apexInfo) {
-        packageName = apexInfo.packageName;
+        packageName = apexInfo.moduleName;
         setLongVersionCode(apexInfo.versionCode);
         isApex = true;
     }
diff --git a/core/java/android/content/pm/PackageList.java b/core/java/android/content/pm/PackageList.java
index f781758..e3eb2c5 100644
--- a/core/java/android/content/pm/PackageList.java
+++ b/core/java/android/content/pm/PackageList.java
@@ -52,6 +52,13 @@
     }
 
     @Override
+    public void onPackageChanged(String packageName, int uid) {
+        if (mWrappedObserver != null) {
+            mWrappedObserver.onPackageChanged(packageName, uid);
+        }
+    }
+
+    @Override
     public void onPackageRemoved(String packageName, int uid) {
         if (mWrappedObserver != null) {
             mWrappedObserver.onPackageRemoved(packageName, uid);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4f7f07b..f7c9635 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1949,6 +1949,30 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports Open Mobile API capable UICC-based secure
+     * elements.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SE_OMAPI_UICC = "android.hardware.se.omapi.uicc";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports Open Mobile API capable eSE-based secure
+     * elements.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports Open Mobile API capable SD-based secure
+     * elements.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SE_OMAPI_SD = "android.hardware.se.omapi.sd";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports the OpenGL ES
      * <a href="http://www.khronos.org/registry/gles/extensions/ANDROID/ANDROID_extension_pack_es31a.txt">
      * Android Extension Pack</a>.
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index c299369..0694c5f 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -63,6 +63,8 @@
     public interface PackageListObserver {
         /** A package was added to the system. */
         void onPackageAdded(@NonNull String packageName, int uid);
+        /** A package was changed - either installed for a specific user or updated. */
+        default void onPackageChanged(@NonNull String packageName, int uid) {}
         /** A package was removed from the system. */
         void onPackageRemoved(@NonNull String packageName, int uid);
     }
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 7d101b8..888380b 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -32,8 +32,8 @@
 import android.view.WindowManager.LayoutParams;
 
 /**
- * CompatibilityInfo class keeps the information about compatibility mode that the application is
- * running under.
+ * CompatibilityInfo class keeps the information about the screen compatibility mode that the
+ * application is running under.
  * 
  *  {@hide} 
  */
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 81abdea0..10fe52a 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -812,7 +812,10 @@
      *
      * @throws RuntimeException if starting preview fails; usually this would be
      *    because of a hardware or other low-level error, or because release()
-     *    has been called on this Camera instance.
+     *    has been called on this Camera instance. The QCIF (176x144) exception
+     *    mentioned in {@link Parameters#setPreviewSize setPreviewSize} and
+     *    {@link Parameters#setPictureSize setPictureSize} can also cause this
+     *    exception be thrown.
      */
     public native final void startPreview();
 
@@ -2863,6 +2866,16 @@
          * orientation should also be considered while setting picture size and
          * thumbnail size.
          *
+         * Exception on 176x144 (QCIF) resolution:
+         * Camera devices usually have a fixed capability for downscaling from
+         * larger resolution to smaller, and the QCIF resolution sometimes
+         * is not fully supported due to this limitation on devices with
+         * high-resolution image sensors. Therefore, trying to configure a QCIF
+         * preview size with any picture or video size larger than 1920x1080
+         * (either width or height) might not be supported, and
+         * {@link #setParameters(Camera.Parameters)} might throw a
+         * RuntimeException if it is not.
+         *
          * @param width  the width of the pictures, in pixels
          * @param height the height of the pictures, in pixels
          * @see #setDisplayOrientation(int)
@@ -2908,6 +2921,16 @@
          * preview can be different from the resolution of the recorded video
          * during video recording.</p>
          *
+         * <p>Exception on 176x144 (QCIF) resolution:
+         * Camera devices usually have a fixed capability for downscaling from
+         * larger resolution to smaller, and the QCIF resolution sometimes
+         * is not fully supported due to this limitation on devices with
+         * high-resolution image sensors. Therefore, trying to configure a QCIF
+         * video resolution with any preview or picture size larger than
+         * 1920x1080  (either width or height) might not be supported, and
+         * {@link #setParameters(Camera.Parameters)} will throw a
+         * RuntimeException if it is not.</p>
+         *
          * @return a list of Size object if camera has separate preview and
          *         video output; otherwise, null is returned.
          * @see #getPreferredPreviewSizeForVideo()
@@ -3202,6 +3225,16 @@
          * <p>Applications need to consider the display orientation. See {@link
          * #setPreviewSize(int,int)} for reference.</p>
          *
+         * <p>Exception on 176x144 (QCIF) resolution:
+         * Camera devices usually have a fixed capability for downscaling from
+         * larger resolution to smaller, and the QCIF resolution sometimes
+         * is not fully supported due to this limitation on devices with
+         * high-resolution image sensors. Therefore, trying to configure a QCIF
+         * picture size with any preview or video size larger than 1920x1080
+         * (either width or height) might not be supported, and
+         * {@link #setParameters(Camera.Parameters)} might throw a
+         * RuntimeException if it is not.</p>
+         *
          * @param width  the width for pictures, in pixels
          * @param height the height for pictures, in pixels
          * @see #setPreviewSize(int,int)
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a787d77..e5c5328 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2136,6 +2136,12 @@
      * </table>
      * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} for additional
      * mandatory stream configurations on a per-capability basis.</p>
+     * <p>Exception on 176x144 (QCIF) resolution: camera devices usually have a fixed capability for
+     * downscaling from larger resolution to smaller, and the QCIF resolution sometimes is not
+     * fully supported due to this limitation on devices with high-resolution image sensors.
+     * Therefore, trying to configure a QCIF resolution stream together with any other
+     * stream larger than 1920x1080 resolution (either width or height) might not be supported,
+     * and capture session creation will fail if it is not.</p>
      * <p>This key is available on all devices.</p>
      *
      * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
@@ -2331,6 +2337,12 @@
      * ratio 4:3, and the JPEG encoder alignment requirement is 16, the maximum JPEG size will be
      * 3264x2448.</li>
      * </ul>
+     * <p>Exception on 176x144 (QCIF) resolution: camera devices usually have a fixed capability on
+     * downscaling from larger resolution to smaller ones, and the QCIF resolution can sometimes
+     * not be fully supported due to this limitation on devices with high-resolution image
+     * sensors. Therefore, trying to configure a QCIF resolution stream together with any other
+     * stream larger than 1920x1080 resolution (either width or height) might not be supported,
+     * and capture session creation will fail if it is not.</p>
      * <p>This key is available on all devices.</p>
      *
      * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
@@ -3188,7 +3200,7 @@
      * <li><code>LEVEL_3</code> devices additionally support YUV reprocessing and RAW image capture, along
      *   with additional output stream configurations.</li>
      * <li><code>EXTERNAL</code> devices are similar to <code>LIMITED</code> devices with exceptions like some sensor or
-     *   lens information not reorted or less stable framerates.</li>
+     *   lens information not reported or less stable framerates.</li>
      * </ul>
      * <p>See the individual level enums for full descriptions of the supported capabilities.  The
      * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} entry describes the device's capabilities at a
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index ce88697..04e9246 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -433,6 +433,14 @@
      * target combinations with sizes outside of these guarantees, but this can only be tested for
      * by attempting to create a session with such targets.</p>
      *
+     * <p>Exception on 176x144 (QCIF) resolution:
+     * Camera devices usually have a fixed capability for downscaling from larger resolution to
+     * smaller, and the QCIF resolution sometimes is not fully supported due to this
+     * limitation on devices with high-resolution image sensors. Therefore, trying to configure a
+     * QCIF resolution stream together with any other stream larger than 1920x1080 resolution
+     * (either width or height) might not be supported, and capture session creation will fail if it
+     * is not.</p>
+     *
      * @param outputs The new set of Surfaces that should be made available as
      *                targets for captured image data.
      * @param callback The callback to notify about the status of the new capture session.
diff --git a/core/java/android/hardware/display/AmbientBrightnessDayStats.java b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
index 1aa2557..9d6e8eb 100644
--- a/core/java/android/hardware/display/AmbientBrightnessDayStats.java
+++ b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
@@ -17,6 +17,7 @@
 package android.hardware.display;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -136,7 +137,7 @@
             };
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -161,6 +162,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder bucketBoundariesString = new StringBuilder();
diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java
index 6d9ba77..8f0e32f 100644
--- a/core/java/android/hardware/display/BrightnessConfiguration.java
+++ b/core/java/android/hardware/display/BrightnessConfiguration.java
@@ -16,6 +16,7 @@
 
 package android.hardware.display;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -75,6 +76,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("BrightnessConfiguration{[");
@@ -105,7 +107,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o == this) {
             return true;
         }
diff --git a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
index 48ea9a6..1711ad2 100644
--- a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
@@ -16,6 +16,8 @@
 
 package android.hardware.hdmi;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -458,6 +460,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuffer s = new StringBuffer();
@@ -493,7 +496,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (!(obj instanceof HdmiDeviceInfo)) {
             return false;
         }
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java
index 1f0f45a..8eca6626 100644
--- a/core/java/android/hardware/hdmi/HdmiPortInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java
@@ -15,6 +15,8 @@
  */
 package android.hardware.hdmi;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -162,6 +164,7 @@
         dest.writeInt(mMhlSupported ? 1 : 0);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuffer s = new StringBuffer();
@@ -174,7 +177,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (!(o instanceof HdmiPortInfo)) {
             return false;
         }
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 36123e3..2d592ca 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.os.Parcel;
@@ -247,6 +248,7 @@
         return mChrePatchVersion;
     }
 
+    @NonNull
     @Override
     public String toString() {
         String retVal = "";
diff --git a/core/java/android/hardware/location/ContextHubMessage.java b/core/java/android/hardware/location/ContextHubMessage.java
index f078ff9..6777c53 100644
--- a/core/java/android/hardware/location/ContextHubMessage.java
+++ b/core/java/android/hardware/location/ContextHubMessage.java
@@ -16,6 +16,7 @@
 
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -127,7 +128,7 @@
         out.writeByteArray(mData);
     }
 
-    public static final Parcelable.Creator<ContextHubMessage> CREATOR
+    public static final @NonNull Parcelable.Creator<ContextHubMessage> CREATOR
             = new Parcelable.Creator<ContextHubMessage>() {
         public ContextHubMessage createFromParcel(Parcel in) {
             return new ContextHubMessage(in);
@@ -138,6 +139,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         int length = mData.length;
diff --git a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
index 7079237..78cca96 100644
--- a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
+++ b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
@@ -16,6 +16,7 @@
 
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.location.Location;
 import android.os.Parcel;
@@ -72,7 +73,7 @@
         return mLocation;
     }
 
-    public static final Creator<GeofenceHardwareMonitorEvent> CREATOR =
+    public static final @NonNull Creator<GeofenceHardwareMonitorEvent> CREATOR =
             new Creator<GeofenceHardwareMonitorEvent>() {
                 @Override
                 public GeofenceHardwareMonitorEvent createFromParcel(Parcel source) {
@@ -108,6 +109,7 @@
         parcel.writeParcelable(mLocation, flags);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return String.format(
diff --git a/core/java/android/hardware/location/MemoryRegion.java b/core/java/android/hardware/location/MemoryRegion.java
index 857434e..9b63c19 100644
--- a/core/java/android/hardware/location/MemoryRegion.java
+++ b/core/java/android/hardware/location/MemoryRegion.java
@@ -16,6 +16,7 @@
 
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -78,6 +79,7 @@
         return mIsExecutable;
     }
 
+    @NonNull
     @Override
     public String toString() {
         String mask = "";
diff --git a/core/java/android/hardware/location/NanoApp.java b/core/java/android/hardware/location/NanoApp.java
index ded1bb8..6a3b032 100644
--- a/core/java/android/hardware/location/NanoApp.java
+++ b/core/java/android/hardware/location/NanoApp.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -366,6 +367,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         String retVal = "Id : " + mAppId;
diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java
index 562065e..8a251f60 100644
--- a/core/java/android/hardware/location/NanoAppFilter.java
+++ b/core/java/android/hardware/location/NanoAppFilter.java
@@ -16,6 +16,7 @@
 
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -131,6 +132,7 @@
                 (versionsMatch(mVersionRestrictionMask, mAppVersion, info.getAppVersion()));
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "nanoAppId: 0x" + Long.toHexString(mAppId)
diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java
index 2db6a79..c7df31a 100644
--- a/core/java/android/hardware/location/NanoAppInstanceInfo.java
+++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java
@@ -219,6 +219,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         String retVal = "handle : " + mHandle;
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index fec1f71..0f89d66 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.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -133,7 +134,7 @@
         out.writeByteArray(mMessageBody);
     }
 
-    public static final Creator<NanoAppMessage> CREATOR =
+    public static final @NonNull Creator<NanoAppMessage> CREATOR =
             new Creator<NanoAppMessage>() {
                 @Override
                 public NanoAppMessage createFromParcel(Parcel in) {
@@ -146,6 +147,7 @@
                 }
             };
 
+    @NonNull
     @Override
     public String toString() {
         int length = mMessageBody.length;
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 90d407c..4fcc740 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -485,6 +485,7 @@
         return new ProgramSelector(programType, primary, secondary, null);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType)
@@ -502,7 +503,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) return true;
         if (!(obj instanceof ProgramSelector)) return false;
         ProgramSelector other = (ProgramSelector) obj;
@@ -598,6 +599,7 @@
             return mValue;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "Identifier(" + mType + ", " + mValue + ")";
@@ -609,7 +611,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) return true;
             if (!(obj instanceof Identifier)) return false;
             Identifier other = (Identifier) obj;
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 8263bb8..c72bb37 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -485,6 +485,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "ModuleProperties [mId=" + mId
@@ -507,7 +508,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) return true;
             if (!(obj instanceof ModuleProperties)) return false;
             ModuleProperties other = (ModuleProperties) obj;
@@ -660,6 +661,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "BandDescriptor [mRegion=" + mRegion + ", mType=" + mType + ", mLowerLimit="
@@ -679,7 +681,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!(obj instanceof BandDescriptor))
@@ -788,6 +790,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "FmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo
@@ -808,7 +811,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!super.equals(obj))
@@ -877,6 +880,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "AmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo + "]";
@@ -891,7 +895,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!super.equals(obj))
@@ -997,6 +1001,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "BandConfig [ " + mDescriptor.toString() + "]";
@@ -1011,7 +1016,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!(obj instanceof BandConfig))
@@ -1125,6 +1130,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "FmBandConfig [" + super.toString()
@@ -1145,7 +1151,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!super.equals(obj))
@@ -1317,6 +1323,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "AmBandConfig [" + super.toString()
@@ -1332,7 +1339,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!super.equals(obj))
@@ -1656,6 +1663,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "ProgramInfo"
@@ -1676,7 +1684,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) return true;
             if (!(obj instanceof ProgramInfo)) return false;
             ProgramInfo other = (ProgramInfo) obj;
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index baa7a50..a17413a 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -269,6 +269,7 @@
         mBundle = in.readBundle();
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("RadioMetadata[");
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 007f4bc..7c12737 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -22,6 +22,7 @@
 import static android.system.OsConstants.EPERM;
 import static android.system.OsConstants.EPIPE;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
@@ -821,7 +822,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (obj == null)
@@ -861,6 +862,7 @@
             return true;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "RecognitionEvent [status=" + status + ", soundModelHandle=" + soundModelHandle
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index afdb202..c8d5774 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -16,6 +16,7 @@
 
 package android.hardware.usb;
 
+import android.annotation.NonNull;
 import android.hardware.usb.V1_0.Constants;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -271,7 +272,7 @@
         return false;
     }
 
-
+    @NonNull
     @Override
     public String toString() {
         return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes) + "}";
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 2cd8209..b09708b 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -16,6 +16,7 @@
 
 package android.hardware.usb;
 
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -107,6 +108,7 @@
         return mSupportedRoleCombinations;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "UsbPortStatus{connected=" + isConnected()
@@ -131,7 +133,7 @@
         dest.writeInt(mSupportedRoleCombinations);
     }
 
-    public static final Parcelable.Creator<UsbPortStatus> CREATOR =
+    public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
             new Parcelable.Creator<UsbPortStatus>() {
         @Override
         public UsbPortStatus createFromParcel(Parcel in) {
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a69ca99..111a8c4 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -655,7 +655,7 @@
      * {@hide}
      */
     @Deprecated
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+    @UnsupportedAppUsage
     public static final int TYPE_WIFI_P2P    = 13;
 
     /**
@@ -3449,6 +3449,11 @@
             final NetworkCallback callback;
             synchronized (sCallbacks) {
                 callback = sCallbacks.get(request);
+                if (callback == null) {
+                    Log.w(TAG,
+                            "callback not found for " + getCallbackName(message.what) + " message");
+                    return;
+                }
                 if (message.what == CALLBACK_UNAVAIL) {
                     sCallbacks.remove(request);
                     callback.networkRequest = ALREADY_UNREGISTERED;
@@ -3457,10 +3462,6 @@
             if (DBG) {
                 Log.d(TAG, getCallbackName(message.what) + " for network " + network);
             }
-            if (callback == null) {
-                Log.w(TAG, "callback not found for " + getCallbackName(message.what) + " message");
-                return;
-            }
 
             switch (message.what) {
                 case CALLBACK_PRECHECK: {
@@ -3612,8 +3613,9 @@
      * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
      *                        the callback must not be shared - it uniquely specifies this request.
      *                        The callback is invoked on the default internal Handler.
-     * @throws IllegalArgumentException if {@code request} specifies any mutable
-     *         {@code NetworkCapabilities}.
+     * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+     * @throws SecurityException if missing the appropriate permissions.
+     * @throws RuntimeException if request limit per UID is exceeded.
      */
     public void requestNetwork(@NonNull NetworkRequest request,
             @NonNull NetworkCallback networkCallback) {
@@ -3648,8 +3650,9 @@
      * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
      *                        the callback must not be shared - it uniquely specifies this request.
      * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
-     * @throws IllegalArgumentException if {@code request} specifies any mutable
-     *         {@code NetworkCapabilities}.
+     * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+     * @throws SecurityException if missing the appropriate permissions.
+     * @throws RuntimeException if request limit per UID is exceeded.
      */
     public void requestNetwork(@NonNull NetworkRequest request,
             @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
@@ -3685,6 +3688,9 @@
      * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
      *                  before {@link NetworkCallback#onUnavailable()} is called. The timeout must
      *                  be a positive value (i.e. >0).
+     * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+     * @throws SecurityException if missing the appropriate permissions.
+     * @throws RuntimeException if request limit per UID is exceeded.
      */
     public void requestNetwork(@NonNull NetworkRequest request,
             @NonNull NetworkCallback networkCallback, int timeoutMs) {
@@ -3719,6 +3725,9 @@
      * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
      * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
      *                  before {@link NetworkCallback#onUnavailable} is called.
+     * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+     * @throws SecurityException if missing the appropriate permissions.
+     * @throws RuntimeException if request limit per UID is exceeded.
      */
     public void requestNetwork(@NonNull NetworkRequest request,
             @NonNull NetworkCallback networkCallback, @NonNull Handler handler, int timeoutMs) {
@@ -3789,9 +3798,9 @@
      * @param operation Action to perform when the network is available (corresponds
      *                  to the {@link NetworkCallback#onAvailable} call.  Typically
      *                  comes from {@link PendingIntent#getBroadcast}. Cannot be null.
-     * @throws IllegalArgumentException if {@code request} contains either
-     *         {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
-     *         {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}.
+     * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+     * @throws SecurityException if missing the appropriate permissions.
+     * @throws RuntimeException if request limit per UID is exceeded.
      */
     public void requestNetwork(@NonNull NetworkRequest request,
             @NonNull PendingIntent operation) {
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
index 68826cb..0b1a845 100644
--- a/core/java/android/net/DnsResolver.java
+++ b/core/java/android/net/DnsResolver.java
@@ -16,16 +16,17 @@
 
 package android.net;
 
+import static android.net.NetworkUtils.getDnsNetwork;
 import static android.net.NetworkUtils.resNetworkCancel;
 import static android.net.NetworkUtils.resNetworkQuery;
 import static android.net.NetworkUtils.resNetworkResult;
 import static android.net.NetworkUtils.resNetworkSend;
+import static android.net.util.DnsUtils.haveIpv4;
+import static android.net.util.DnsUtils.haveIpv6;
+import static android.net.util.DnsUtils.rfc6724Sort;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.ENONET;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
@@ -33,19 +34,14 @@
 import android.annotation.Nullable;
 import android.os.CancellationSignal;
 import android.os.Looper;
+import android.os.MessageQueue;
 import android.system.ErrnoException;
-import android.system.Os;
 import android.util.Log;
 
-import libcore.io.IoUtils;
-
 import java.io.FileDescriptor;
-import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
@@ -196,8 +192,8 @@
         final Object lock = new Object();
         final FileDescriptor queryfd;
         try {
-            queryfd = resNetworkSend((network != null
-                ? network.getNetIdForResolv() : NETID_UNSET), query, query.length, flags);
+            queryfd = resNetworkSend((network != null)
+                    ? network.getNetIdForResolv() : NETID_UNSET, query, query.length, flags);
         } catch (ErrnoException e) {
             executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
             return;
@@ -237,8 +233,8 @@
         final Object lock = new Object();
         final FileDescriptor queryfd;
         try {
-            queryfd = resNetworkQuery((network != null
-                    ? network.getNetIdForResolv() : NETID_UNSET), domain, nsClass, nsType, flags);
+            queryfd = resNetworkQuery((network != null)
+                    ? network.getNetIdForResolv() : NETID_UNSET, domain, nsClass, nsType, flags);
         } catch (ErrnoException e) {
             executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
             return;
@@ -252,14 +248,16 @@
 
     private class InetAddressAnswerAccumulator implements Callback<byte[]> {
         private final List<InetAddress> mAllAnswers;
+        private final Network mNetwork;
         private int mRcode;
         private DnsException mDnsException;
         private final Callback<? super List<InetAddress>> mUserCallback;
         private final int mTargetAnswerCount;
         private int mReceivedAnswerCount = 0;
 
-        InetAddressAnswerAccumulator(int size,
+        InetAddressAnswerAccumulator(@NonNull Network network, int size,
                 @NonNull Callback<? super List<InetAddress>> callback) {
+            mNetwork = network;
             mTargetAnswerCount = size;
             mAllAnswers = new ArrayList<>();
             mUserCallback = callback;
@@ -280,8 +278,7 @@
         private void maybeReportAnswer() {
             if (++mReceivedAnswerCount != mTargetAnswerCount) return;
             if (mAllAnswers.isEmpty() && maybeReportError()) return;
-            // TODO: Do RFC6724 sort.
-            mUserCallback.onAnswer(mAllAnswers, mRcode);
+            mUserCallback.onAnswer(rfc6724Sort(mNetwork, mAllAnswers), mRcode);
         }
 
         @Override
@@ -308,7 +305,7 @@
 
     /**
      * Send a DNS query with the specified name on a network with both IPv4 and IPv6,
-     * get back a set of InetAddresses asynchronously.
+     * get back a set of InetAddresses with rfc6724 sorting style asynchronously.
      *
      * This method will examine the connection ability on given network, and query IPv4
      * and IPv6 if connection is available.
@@ -335,8 +332,23 @@
             return;
         }
         final Object lock = new Object();
-        final boolean queryIpv6 = haveIpv6(network);
-        final boolean queryIpv4 = haveIpv4(network);
+        final Network queryNetwork;
+        try {
+            queryNetwork = (network != null) ? network : getDnsNetwork();
+        } catch (ErrnoException e) {
+            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+            return;
+        }
+        final boolean queryIpv6 = haveIpv6(queryNetwork);
+        final boolean queryIpv4 = haveIpv4(queryNetwork);
+
+        // This can only happen if queryIpv4 and queryIpv6 are both false.
+        // This almost certainly means that queryNetwork does not exist or no longer exists.
+        if (!queryIpv6 && !queryIpv4) {
+            executor.execute(() -> callback.onError(
+                    new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET))));
+            return;
+        }
 
         final FileDescriptor v4fd;
         final FileDescriptor v6fd;
@@ -345,9 +357,8 @@
 
         if (queryIpv6) {
             try {
-                v6fd = resNetworkQuery((network != null
-                        ? network.getNetIdForResolv() : NETID_UNSET),
-                        domain, CLASS_IN, TYPE_AAAA, flags);
+                v6fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN,
+                        TYPE_AAAA, flags);
             } catch (ErrnoException e) {
                 executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
                 return;
@@ -355,7 +366,6 @@
             queryCount++;
         } else v6fd = null;
 
-        // TODO: Use device flag to control the sleep time.
         // Avoiding gateways drop packets if queries are sent too close together
         try {
             Thread.sleep(SLEEP_TIME_MS);
@@ -365,9 +375,8 @@
 
         if (queryIpv4) {
             try {
-                v4fd = resNetworkQuery((network != null
-                        ? network.getNetIdForResolv() : NETID_UNSET),
-                        domain, CLASS_IN, TYPE_A, flags);
+                v4fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, TYPE_A,
+                        flags);
             } catch (ErrnoException e) {
                 if (queryIpv6) resNetworkCancel(v6fd);  // Closes fd, marks it invalid.
                 executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
@@ -377,7 +386,7 @@
         } else v4fd = null;
 
         final InetAddressAnswerAccumulator accumulator =
-                new InetAddressAnswerAccumulator(queryCount, callback);
+                new InetAddressAnswerAccumulator(queryNetwork, queryCount, callback);
 
         synchronized (lock)  {
             if (queryIpv6) {
@@ -398,7 +407,7 @@
 
     /**
      * Send a DNS query with the specified name and query type, get back a set of
-     * InetAddresses asynchronously.
+     * InetAddresses with rfc6724 sorting style asynchronously.
      *
      * The answer will be provided asynchronously through the provided {@link Callback}.
      *
@@ -423,15 +432,17 @@
         }
         final Object lock = new Object();
         final FileDescriptor queryfd;
+        final Network queryNetwork;
         try {
-            queryfd = resNetworkQuery((network != null
-                    ? network.getNetIdForResolv() : NETID_UNSET), domain, CLASS_IN, nsType, flags);
+            queryNetwork = (network != null) ? network : getDnsNetwork();
+            queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, nsType,
+                    flags);
         } catch (ErrnoException e) {
             executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
             return;
         }
         final InetAddressAnswerAccumulator accumulator =
-                new InetAddressAnswerAccumulator(1, callback);
+                new InetAddressAnswerAccumulator(queryNetwork, 1, callback);
         synchronized (lock)  {
             registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock);
             if (cancellationSignal == null) return;
@@ -456,10 +467,20 @@
     private void registerFDListener(@NonNull Executor executor,
             @NonNull FileDescriptor queryfd, @NonNull Callback<? super byte[]> answerCallback,
             @Nullable CancellationSignal cancellationSignal, @NonNull Object lock) {
-        Looper.getMainLooper().getQueue().addOnFileDescriptorEventListener(
+        final MessageQueue mainThreadMessageQueue = Looper.getMainLooper().getQueue();
+        mainThreadMessageQueue.addOnFileDescriptorEventListener(
                 queryfd,
                 FD_EVENTS,
                 (fd, events) -> {
+                    // b/134310704
+                    // Unregister fd event listener before resNetworkResult is called to prevent
+                    // race condition caused by fd reused.
+                    // For example when querying v4 and v6, it's possible that the first query ends
+                    // and the fd is closed before the second request starts, which might return
+                    // the same fd for the second request. By that time, the looper must have
+                    // unregistered the fd, otherwise another event listener can't be registered.
+                    mainThreadMessageQueue.removeOnFileDescriptorEventListener(fd);
+
                     executor.execute(() -> {
                         DnsResponse resp = null;
                         ErrnoException exception = null;
@@ -480,7 +501,11 @@
                         }
                         answerCallback.onAnswer(resp.answerbuf, resp.rcode);
                     });
-                    // Unregister this fd listener
+
+                    // The file descriptor has already been unregistered, so it does not really
+                    // matter what is returned here. In spirit 0 (meaning "unregister this FD")
+                    // is still the closest to what the looper needs to do. When returning 0,
+                    // Looper knows to ignore the fd if it has already been unregistered.
                     return 0;
                 });
     }
@@ -500,38 +525,6 @@
         });
     }
 
-    // These two functions match the behaviour of have_ipv4 and have_ipv6 in the native resolver.
-    private boolean haveIpv4(@Nullable Network network) {
-        final SocketAddress addrIpv4 =
-                new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0);
-        return checkConnectivity(network, AF_INET, addrIpv4);
-    }
-
-    private boolean haveIpv6(@Nullable Network network) {
-        final SocketAddress addrIpv6 =
-                new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0);
-        return checkConnectivity(network, AF_INET6, addrIpv6);
-    }
-
-    private boolean checkConnectivity(@Nullable Network network,
-            int domain, @NonNull SocketAddress addr) {
-        final FileDescriptor socket;
-        try {
-            socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
-        } catch (ErrnoException e) {
-            return false;
-        }
-        try {
-            if (network != null) network.bindSocket(socket);
-            Os.connect(socket, addr);
-        } catch (IOException | ErrnoException e) {
-            return false;
-        } finally {
-            IoUtils.closeQuietly(socket);
-        }
-        return true;
-    }
-
     private static class DnsAddressAnswer extends DnsPacket {
         private static final String TAG = "DnsResolver.DnsAddressAnswer";
         private static final boolean DBG = false;
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 41efc50..9994f9f 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -66,9 +66,9 @@
     /** Force update of ifaces. */
     void forceUpdateIfaces(
          in Network[] defaultNetworks,
-         in VpnInfo[] vpnArray,
          in NetworkState[] networkStates,
-         in String activeIface);
+         in String activeIface,
+         in VpnInfo[] vpnInfos);
     /** Force update of statistics. */
     @UnsupportedAppUsage
     void forceUpdate();
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 3552655..43c8ff2 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -15,6 +15,7 @@
  */
 package android.net;
 
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -333,25 +334,25 @@
                 }
             };
 
-    @VisibleForTesting
-    /** Equals method used for testing */
-    public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) {
-        if (lhs == null || rhs == null) return (lhs == rhs);
-        return (lhs.mMode == rhs.mMode
-                && lhs.mSourceAddress.equals(rhs.mSourceAddress)
-                && lhs.mDestinationAddress.equals(rhs.mDestinationAddress)
-                && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork))
-                        || (lhs.mNetwork == rhs.mNetwork))
-                && lhs.mEncapType == rhs.mEncapType
-                && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId
-                && lhs.mEncapRemotePort == rhs.mEncapRemotePort
-                && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
-                && lhs.mSpiResourceId == rhs.mSpiResourceId
-                && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
-                && IpSecAlgorithm.equals(lhs.mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
-                && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication)
-                && lhs.mMarkValue == rhs.mMarkValue
-                && lhs.mMarkMask == rhs.mMarkMask
-                && lhs.mXfrmInterfaceId == rhs.mXfrmInterfaceId);
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!(other instanceof IpSecConfig)) return false;
+        final IpSecConfig rhs = (IpSecConfig) other;
+        return (mMode == rhs.mMode
+                && mSourceAddress.equals(rhs.mSourceAddress)
+                && mDestinationAddress.equals(rhs.mDestinationAddress)
+                && ((mNetwork != null && mNetwork.equals(rhs.mNetwork))
+                        || (mNetwork == rhs.mNetwork))
+                && mEncapType == rhs.mEncapType
+                && mEncapSocketResourceId == rhs.mEncapSocketResourceId
+                && mEncapRemotePort == rhs.mEncapRemotePort
+                && mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
+                && mSpiResourceId == rhs.mSpiResourceId
+                && IpSecAlgorithm.equals(mEncryption, rhs.mEncryption)
+                && IpSecAlgorithm.equals(mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
+                && IpSecAlgorithm.equals(mAuthentication, rhs.mAuthentication)
+                && mMarkValue == rhs.mMarkValue
+                && mMarkMask == rhs.mMarkMask
+                && mXfrmInterfaceId == rhs.mXfrmInterfaceId);
     }
 }
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 889e9bc..2262a04 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -853,6 +853,7 @@
             return mResourceId;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return new StringBuilder()
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index a12df28..93ae4f1 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -148,15 +148,13 @@
     }
 
     /**
-     * Equals method used for testing
-     *
-     * @hide
+     * Standard equals.
      */
-    @VisibleForTesting
-    public static boolean equals(IpSecTransform lhs, IpSecTransform rhs) {
-        if (lhs == null || rhs == null) return (lhs == rhs);
-        return IpSecConfig.equals(lhs.getConfig(), rhs.getConfig())
-                && lhs.mResourceId == rhs.mResourceId;
+    public boolean equals(Object other) {
+        if (this == other) return true;
+        if (!(other instanceof IpSecTransform)) return false;
+        final IpSecTransform rhs = (IpSecTransform) other;
+        return getConfig().equals(rhs.getConfig()) && mResourceId == rhs.mResourceId;
     }
 
     /**
diff --git a/core/java/android/net/LinkQualityInfo.java b/core/java/android/net/LinkQualityInfo.java
index b6f8825..78d7787 100644
--- a/core/java/android/net/LinkQualityInfo.java
+++ b/core/java/android/net/LinkQualityInfo.java
@@ -24,8 +24,8 @@
  *  Class that represents useful attributes of generic network links
  *  such as the upload/download throughput or packet error rate.
  *  Generally speaking, you should be dealing with instances of
- *  LinkQualityInfo subclasses, such as {@link android.net.#WifiLinkQualityInfo}
- *  or {@link android.net.#MobileLinkQualityInfo} which provide additional
+ *  LinkQualityInfo subclasses, such as {@link android.net.WifiLinkQualityInfo}
+ *  or {@link android.net.MobileLinkQualityInfo} which provide additional
  *  information.
  *  @hide
  */
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index c2b7d2c..52d485d 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
+import android.net.wifi.WifiInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -364,7 +365,12 @@
         long addr = r.nextLong() & VALID_LONG_MASK;
         addr |= LOCALLY_ASSIGNED_MASK;
         addr &= ~MULTICAST_MASK;
-        return new MacAddress(addr);
+        MacAddress mac = new MacAddress(addr);
+        // WifiInfo.DEFAULT_MAC_ADDRESS is being used for another purpose, so do not use it here.
+        if (mac.toString().equals(WifiInfo.DEFAULT_MAC_ADDRESS)) {
+            return createRandomUnicastAddress();
+        }
+        return mac;
     }
 
     /**
@@ -383,7 +389,12 @@
         long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
         addr |= LOCALLY_ASSIGNED_MASK;
         addr &= ~MULTICAST_MASK;
-        return new MacAddress(addr);
+        MacAddress mac = new MacAddress(addr);
+        // WifiInfo.DEFAULT_MAC_ADDRESS is being used for another purpose, so do not use it here.
+        if (mac.toString().equals(WifiInfo.DEFAULT_MAC_ADDRESS)) {
+            return createRandomUnicastAddress(base, r);
+        }
+        return mac;
     }
 
     // Convenience function for working around the lack of byte literals.
diff --git a/services/net/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java
similarity index 67%
rename from services/net/java/android/net/NattKeepalivePacketData.java
rename to core/java/android/net/NattKeepalivePacketData.java
index 27ed11e..a77c244 100644
--- a/services/net/java/android/net/NattKeepalivePacketData.java
+++ b/core/java/android/net/NattKeepalivePacketData.java
@@ -19,9 +19,9 @@
 import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
 import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
 
-import android.annotation.NonNull;
 import android.net.SocketKeepalive.InvalidPacketException;
 import android.net.util.IpUtils;
+import android.os.Parcel;
 import android.os.Parcelable;
 import android.system.OsConstants;
 
@@ -79,17 +79,40 @@
         return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
     }
 
-     /**
-     * Convert this NattKeepalivePacketData to a NattKeepalivePacketDataParcelable.
-     */
-    @NonNull
-    public NattKeepalivePacketDataParcelable toStableParcelable() {
-        final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable();
-
-        parcel.srcAddress = srcAddress.getAddress();
-        parcel.srcPort = srcPort;
-        parcel.dstAddress = dstAddress.getAddress();
-        parcel.dstPort = dstPort;
-        return parcel;
+    /** Parcelable Implementation */
+    public int describeContents() {
+        return 0;
     }
+
+    /** Write to parcel */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(srcAddress.getHostAddress());
+        out.writeString(dstAddress.getHostAddress());
+        out.writeInt(srcPort);
+        out.writeInt(dstPort);
+    }
+
+    /** Parcelable Creator */
+    public static final Parcelable.Creator<NattKeepalivePacketData> CREATOR =
+            new Parcelable.Creator<NattKeepalivePacketData>() {
+                public NattKeepalivePacketData createFromParcel(Parcel in) {
+                    final InetAddress srcAddress =
+                            InetAddresses.parseNumericAddress(in.readString());
+                    final InetAddress dstAddress =
+                            InetAddresses.parseNumericAddress(in.readString());
+                    final int srcPort = in.readInt();
+                    final int dstPort = in.readInt();
+                    try {
+                        return NattKeepalivePacketData.nattKeepalivePacket(srcAddress, srcPort,
+                                    dstAddress, dstPort);
+                    } catch (InvalidPacketException e) {
+                        throw new IllegalArgumentException(
+                                "Invalid NAT-T keepalive data: " + e.error);
+                    }
+                }
+
+                public NattKeepalivePacketData[] newArray(int size) {
+                    return new NattKeepalivePacketData[size];
+                }
+            };
 }
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 2ff6043..b3f829a 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -433,7 +433,32 @@
      * {@link #saveAcceptUnvalidated} to respect the user's choice.
      */
     public void explicitlySelected(boolean acceptUnvalidated) {
-        queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, acceptUnvalidated ? 1 : 0, 0);
+        explicitlySelected(true /* explicitlySelected */, acceptUnvalidated);
+    }
+
+    /**
+     * Called by the bearer to indicate whether the network was manually selected by the user.
+     * This should be called before the NetworkInfo is marked CONNECTED so that this
+     * Network can be given special treatment at that time.
+     *
+     * If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true},
+     * then the system will switch to this network. If {@code explicitlySelected} is {@code true}
+     * and {@code acceptUnvalidated} is {@code false}, and the  network cannot be validated, the
+     * system will ask the user whether to switch to this network.  If the user confirms and selects
+     * "don't ask again", then the system will call {@link #saveAcceptUnvalidated} to persist the
+     * user's choice. Thus, if the transport ever calls this method with {@code explicitlySelected}
+     * set to {@code true} and {@code acceptUnvalidated} set to {@code false}, it must also
+     * implement {@link #saveAcceptUnvalidated} to respect the user's choice.
+     *
+     * If  {@code explicitlySelected} is {@code false} and {@code acceptUnvalidated} is
+     * {@code true}, the system will interpret this as the user having accepted partial connectivity
+     * on this network. Thus, the system will switch to the network and consider it validated even
+     * if it only provides partial connectivity, but the network is not otherwise treated specially.
+     */
+    public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
+        queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED,
+                explicitlySelected ? 1 : 0,
+                acceptUnvalidated ? 1 : 0);
     }
 
     /**
@@ -510,7 +535,6 @@
      * override this method.
      */
     protected void addKeepalivePacketFilter(Message msg) {
-        onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
     }
 
     /**
@@ -519,7 +543,6 @@
      * must override this method.
      */
     protected void removeKeepalivePacketFilter(Message msg) {
-        onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
     }
 
     /**
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
index 31a74dc..5f0c7b7 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.wifi.ScanResult;
@@ -152,7 +153,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -166,6 +167,7 @@
         return Objects.hash(type, wifiKey);
     }
 
+    @NonNull
     @Override
     public String toString() {
         switch (type) {
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 27e0414..14a0cbf 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -23,7 +23,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
-import android.util.Slog;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -34,10 +33,10 @@
 import java.io.CharArrayWriter;
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.function.Predicate;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Predicate;
 
 /**
  * Collection of active network statistics. Can contain summary details across
@@ -996,8 +995,8 @@
             return;
         }
         filter(e -> (limitUid == UID_ALL || limitUid == e.uid)
-            && (limitTag == TAG_ALL || limitTag == e.tag)
-            && (limitIfaces == INTERFACES_ALL
+                && (limitTag == TAG_ALL || limitTag == e.tag)
+                && (limitIfaces == INTERFACES_ALL
                     || ArrayUtils.contains(limitIfaces, e.iface)));
     }
 
@@ -1300,7 +1299,8 @@
         }
 
         final Entry tmpEntry = new Entry();
-        for (int i = 0; i < size; i++) {
+        final int origSize = size;
+        for (int i = 0; i < origSize; i++) {
             if (!Objects.equals(iface[i], tunIface)) {
                 // Consider only entries that go onto the VPN interface.
                 continue;
@@ -1316,8 +1316,9 @@
             tmpEntry.roaming = roaming[i];
             tmpEntry.defaultNetwork = defaultNetwork[i];
 
-            // In a first pass, compute each UID's total share of data across all underlyingIfaces.
-            // This is computed on the basis of the share of each UID's usage over tunIface.
+            // In a first pass, compute this entry's total share of data across all
+            // underlyingIfaces. This is computed on the basis of the share of this entry's usage
+            // over tunIface.
             // TODO: Consider refactoring first pass into a separate helper method.
             long totalRxBytes = 0;
             if (tunIfaceTotal.rxBytes > 0) {
@@ -1394,9 +1395,11 @@
                                     * perInterfaceTotal[j].operations
                                     / underlyingIfacesTotal.operations;
                 }
-
+                // tmpEntry now contains the migrated data of the i-th entry for the j-th underlying
+                // interface. Add that data usage to this object.
                 combineValues(tmpEntry);
                 if (tag[i] == TAG_NONE) {
+                    // Add the migrated data to moved so it is deducted from the VPN app later.
                     moved[j].add(tmpEntry);
                     // Add debug info
                     tmpEntry.set = SET_DBG_VPN_IN;
@@ -1412,8 +1415,8 @@
             @NonNull String[] underlyingIfaces,
             @NonNull Entry[] moved) {
         for (int i = 0; i < underlyingIfaces.length; i++) {
-            // Add debug info
             moved[i].uid = tunUid;
+            // Add debug info
             moved[i].set = SET_DBG_VPN_OUT;
             moved[i].tag = TAG_NONE;
             moved[i].iface = underlyingIfaces[i];
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index d07ff13..228e62d 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -152,6 +152,12 @@
     public static native void resNetworkCancel(FileDescriptor fd);
 
     /**
+     * DNS resolver series jni method.
+     * Attempts to get network which resolver will use if no network is explicitly selected.
+     */
+    public static native Network getDnsNetwork() throws ErrnoException;
+
+    /**
      * Get the tcp repair window associated with the {@code fd}.
      *
      * @param fd the tcp socket's {@link FileDescriptor}.
diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java
index 5b81f52..4b4451c 100644
--- a/core/java/android/net/RssiCurve.java
+++ b/core/java/android/net/RssiCurve.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -170,7 +172,7 @@
      * not considered equal to each other.
      */
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -187,6 +189,7 @@
         return Objects.hash(start, bucketWidth, activeNetworkRssiBoost) ^ Arrays.hashCode(rssiBuckets);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java
index e38d227..6649789 100644
--- a/core/java/android/net/ScoredNetwork.java
+++ b/core/java/android/net/ScoredNetwork.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Bundle;
@@ -182,7 +183,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -220,6 +221,7 @@
         return Objects.hash(networkKey, rssiCurve, meteredHint, attributes);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder out = new StringBuilder(
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index 46eddde..ec73866 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -44,9 +44,11 @@
  * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
  * {@link SocketKeepalive.Callback#onError} if an error occurred.
  *
- * The device SHOULD support keepalive offload. If it does not, it MUST reply with
+ * For cellular, the device MUST support at least 1 keepalive slot.
+ *
+ * For WiFi, the device SHOULD support keepalive offload. If it does not, it MUST reply with
  * {@link SocketKeepalive.Callback#onError} with {@code ERROR_UNSUPPORTED} to any keepalive offload
- * request. If it does, it MUST support at least 3 concurrent keepalive slots per transport.
+ * request. If it does, it MUST support at least 3 concurrent keepalive slots.
  */
 public abstract class SocketKeepalive implements AutoCloseable {
     static final String TAG = "SocketKeepalive";
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index f01e213..9ce6bae 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -22,7 +22,6 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.net.shared.InetAddressUtils;
-import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -54,19 +53,19 @@
 @TestApi
 public final class StaticIpConfiguration implements Parcelable {
     /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    @UnsupportedAppUsage
     @Nullable
     public LinkAddress ipAddress;
     /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    @UnsupportedAppUsage
     @Nullable
     public InetAddress gateway;
     /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    @UnsupportedAppUsage
     @NonNull
     public final ArrayList<InetAddress> dnsServers;
     /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    @UnsupportedAppUsage
     @Nullable
     public String domains;
 
@@ -237,6 +236,7 @@
         return lp;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuffer str = new StringBuffer();
@@ -268,7 +268,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) return true;
 
         if (!(obj instanceof StaticIpConfiguration)) return false;
diff --git a/core/java/android/net/WebAddress.java b/core/java/android/net/WebAddress.java
index fbc281f..994c794 100644
--- a/core/java/android/net/WebAddress.java
+++ b/core/java/android/net/WebAddress.java
@@ -18,6 +18,7 @@
 
 import static android.util.Patterns.GOOD_IRI_CHAR;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -132,6 +133,7 @@
         if (mScheme.equals("")) mScheme = "http";
     }
 
+    @NonNull
     @Override
     public String toString() {
         String port = "";
diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java
index 68b505d..d9e0cf1 100644
--- a/core/java/android/net/WifiKey.java
+++ b/core/java/android/net/WifiKey.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -91,7 +93,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -105,6 +107,7 @@
         return Objects.hash(ssid, bssid);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "WifiKey[SSID=" + ssid + ",BSSID=" + bssid + "]";
diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java
index 4dd2ace..b1de74e 100644
--- a/core/java/android/net/apf/ApfCapabilities.java
+++ b/core/java/android/net/apf/ApfCapabilities.java
@@ -17,6 +17,7 @@
 package android.net.apf;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.res.Resources;
@@ -91,6 +92,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
@@ -98,7 +100,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (!(obj instanceof  ApfCapabilities)) return false;
         final ApfCapabilities other = (ApfCapabilities) obj;
         return apfVersionSupported == other.apfVersionSupported
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index cd8ce8d..fbe4ac0 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
@@ -185,6 +186,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         String lifetimeString = (lifetime < Long.MAX_VALUE) ? lifetime + "s" : "forever";
@@ -193,7 +195,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(ApfProgramEvent.class))) return false;
         final ApfProgramEvent other = (ApfProgramEvent) obj;
         return lifetime == other.lifetime
diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java
index 2e78469..191303f 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -17,6 +17,7 @@
 package android.net.metrics;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
@@ -260,6 +261,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return new StringBuilder("ApfStats(")
@@ -276,7 +278,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(ApfStats.class))) return false;
         final ApfStats other = (ApfStats) obj;
         return durationMs == other.durationMs
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index fa6bff3..0361eac 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -17,6 +17,7 @@
 package android.net.metrics;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
@@ -97,13 +98,14 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(DhcpClientEvent.class))) return false;
         final DhcpClientEvent other = (DhcpClientEvent) obj;
         return TextUtils.equals(msg, other.msg)
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index 8482346..7512190 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -16,6 +16,7 @@
 
 package android.net.metrics;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -108,6 +109,7 @@
         return (0xFFFF0000 & errorCode) | (0xFF & option);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("DhcpErrorEvent(%s)", Decoder.constants.get(errorCode));
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index 77908e6..66588a7 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -17,6 +17,8 @@
 package android.net.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -95,6 +97,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("IpManagerEvent(%s, %dms)",
@@ -102,7 +105,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(IpManagerEvent.class))) return false;
         final IpManagerEvent other = (IpManagerEvent) obj;
         return eventType == other.eventType
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index f9ee39b..8b856fb 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -16,6 +16,8 @@
 
 package android.net.metrics;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -85,6 +87,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         int hi = eventType & 0xff00;
@@ -94,7 +97,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(IpReachabilityEvent.class))) return false;
         final IpReachabilityEvent other = (IpReachabilityEvent) obj;
         return eventType == other.eventType;
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index ec0f82a..ebdc2bf 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -17,6 +17,8 @@
 package android.net.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -115,6 +117,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("NetworkEvent(%s, %dms)",
@@ -122,7 +125,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(NetworkEvent.class))) return false;
         final NetworkEvent other = (NetworkEvent) obj;
         return eventType == other.eventType
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index 6ccca7d..e62154d 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -17,6 +17,7 @@
 package android.net.metrics;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -85,6 +86,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return new StringBuilder("RaEvent(lifetimes: ")
@@ -98,7 +100,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(RaEvent.class))) return false;
         final RaEvent other = (RaEvent) obj;
         return routerLifetime == other.routerLifetime
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 6784420..199c9d2 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -164,6 +165,7 @@
         return Decoder.constants.get(probeType & 0xff00, "UNKNOWN");
     }
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("ValidationProbeEvent(%s:%d %s, %dms)",
@@ -171,7 +173,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(ValidationProbeEvent.class))) return false;
         final ValidationProbeEvent other = (ValidationProbeEvent) obj;
         return durationMs == other.durationMs
diff --git a/core/java/android/net/util/DnsUtils.java b/core/java/android/net/util/DnsUtils.java
new file mode 100644
index 0000000..7908353
--- /dev/null
+++ b/core/java/android/net/util/DnsUtils.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.Network;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.internal.util.BitUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class DnsUtils {
+    private static final String TAG = "DnsUtils";
+    private static final int CHAR_BIT = 8;
+    public static final int IPV6_ADDR_SCOPE_NODELOCAL = 0x01;
+    public static final int IPV6_ADDR_SCOPE_LINKLOCAL = 0x02;
+    public static final int IPV6_ADDR_SCOPE_SITELOCAL = 0x05;
+    public static final int IPV6_ADDR_SCOPE_GLOBAL = 0x0e;
+    private static final Comparator<SortableAddress> sRfc6724Comparator = new Rfc6724Comparator();
+
+    /**
+     * Comparator to sort SortableAddress in Rfc6724 style.
+     */
+    public static class Rfc6724Comparator implements Comparator<SortableAddress> {
+        // This function matches the behaviour of _rfc6724_compare in the native resolver.
+        @Override
+        public int compare(SortableAddress span1, SortableAddress span2) {
+            // Rule 1: Avoid unusable destinations.
+            if (span1.hasSrcAddr != span2.hasSrcAddr) {
+                return span2.hasSrcAddr - span1.hasSrcAddr;
+            }
+
+            // Rule 2: Prefer matching scope.
+            if (span1.scopeMatch != span2.scopeMatch) {
+                return span2.scopeMatch - span1.scopeMatch;
+            }
+
+            // TODO: Implement rule 3: Avoid deprecated addresses.
+            // TODO: Implement rule 4: Prefer home addresses.
+
+            // Rule 5: Prefer matching label.
+            if (span1.labelMatch != span2.labelMatch) {
+                return span2.labelMatch - span1.labelMatch;
+            }
+
+            // Rule 6: Prefer higher precedence.
+            if (span1.precedence != span2.precedence) {
+                return span2.precedence - span1.precedence;
+            }
+
+            // TODO: Implement rule 7: Prefer native transport.
+
+            // Rule 8: Prefer smaller scope.
+            if (span1.scope != span2.scope) {
+                return span1.scope - span2.scope;
+            }
+
+            // Rule 9: Use longest matching prefix. IPv6 only.
+            if (span1.prefixMatchLen != span2.prefixMatchLen) {
+                return span2.prefixMatchLen - span1.prefixMatchLen;
+            }
+
+            // Rule 10: Leave the order unchanged. Collections.sort is a stable sort.
+            return 0;
+        }
+    }
+
+    /**
+     * Class used to sort with RFC 6724
+     */
+    public static class SortableAddress {
+        public final int label;
+        public final int labelMatch;
+        public final int scope;
+        public final int scopeMatch;
+        public final int precedence;
+        public final int prefixMatchLen;
+        public final int hasSrcAddr;
+        public final InetAddress address;
+
+        public SortableAddress(@NonNull InetAddress addr, @Nullable InetAddress srcAddr) {
+            address = addr;
+            hasSrcAddr = (srcAddr != null) ? 1 : 0;
+            label = findLabel(addr);
+            scope = findScope(addr);
+            precedence = findPrecedence(addr);
+            labelMatch = ((srcAddr != null) && (label == findLabel(srcAddr))) ? 1 : 0;
+            scopeMatch = ((srcAddr != null) && (scope == findScope(srcAddr))) ? 1 : 0;
+            if (isIpv6Address(addr) && isIpv6Address(srcAddr)) {
+                prefixMatchLen = compareIpv6PrefixMatchLen(srcAddr, addr);
+            } else {
+                prefixMatchLen = 0;
+            }
+        }
+    }
+
+    /**
+     * Sort the given address list in RFC6724 order.
+     * Will leave the list unchanged if an error occurs.
+     *
+     * This function matches the behaviour of _rfc6724_sort in the native resolver.
+     */
+    public static @NonNull List<InetAddress> rfc6724Sort(@Nullable Network network,
+            @NonNull List<InetAddress> answers) {
+        final ArrayList<SortableAddress> sortableAnswerList = new ArrayList<>();
+        for (InetAddress addr : answers) {
+            sortableAnswerList.add(new SortableAddress(addr, findSrcAddress(network, addr)));
+        }
+
+        Collections.sort(sortableAnswerList, sRfc6724Comparator);
+
+        final List<InetAddress> sortedAnswers = new ArrayList<>();
+        for (SortableAddress ans : sortableAnswerList) {
+            sortedAnswers.add(ans.address);
+        }
+
+        return sortedAnswers;
+    }
+
+    private static @Nullable InetAddress findSrcAddress(@Nullable Network network,
+            @NonNull InetAddress addr) {
+        final int domain;
+        if (isIpv4Address(addr)) {
+            domain = AF_INET;
+        } else if (isIpv6Address(addr)) {
+            domain = AF_INET6;
+        } else {
+            return null;
+        }
+        final FileDescriptor socket;
+        try {
+            socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+        } catch (ErrnoException e) {
+            Log.e(TAG, "findSrcAddress:" + e.toString());
+            return null;
+        }
+        try {
+            if (network != null) network.bindSocket(socket);
+            Os.connect(socket, new InetSocketAddress(addr, 0));
+            return ((InetSocketAddress) Os.getsockname(socket)).getAddress();
+        } catch (IOException | ErrnoException e) {
+            return null;
+        } finally {
+            IoUtils.closeQuietly(socket);
+        }
+    }
+
+    /**
+     * Get the label for a given IPv4/IPv6 address.
+     * RFC 6724, section 2.1.
+     *
+     * Note that Java will return an IPv4-mapped address as an IPv4 address.
+     */
+    private static int findLabel(@NonNull InetAddress addr) {
+        if (isIpv4Address(addr)) {
+            return 4;
+        } else if (isIpv6Address(addr)) {
+            if (addr.isLoopbackAddress()) {
+                return 0;
+            } else if (isIpv6Address6To4(addr)) {
+                return 2;
+            } else if (isIpv6AddressTeredo(addr)) {
+                return 5;
+            } else if (isIpv6AddressULA(addr)) {
+                return 13;
+            } else if (((Inet6Address) addr).isIPv4CompatibleAddress()) {
+                return 3;
+            } else if (addr.isSiteLocalAddress()) {
+                return 11;
+            } else if (isIpv6Address6Bone(addr)) {
+                return 12;
+            } else {
+                // All other IPv6 addresses, including global unicast addresses.
+                return 1;
+            }
+        } else {
+            // This should never happen.
+            return 1;
+        }
+    }
+
+    private static boolean isIpv6Address(@Nullable InetAddress addr) {
+        return addr instanceof Inet6Address;
+    }
+
+    private static boolean isIpv4Address(@Nullable InetAddress addr) {
+        return addr instanceof Inet4Address;
+    }
+
+    private static boolean isIpv6Address6To4(@NonNull InetAddress addr) {
+        if (!isIpv6Address(addr)) return false;
+        final byte[] byteAddr = addr.getAddress();
+        return byteAddr[0] == 0x20 && byteAddr[1] == 0x02;
+    }
+
+    private static boolean isIpv6AddressTeredo(@NonNull InetAddress addr) {
+        if (!isIpv6Address(addr)) return false;
+        final byte[] byteAddr = addr.getAddress();
+        return byteAddr[0] == 0x20 && byteAddr[1] == 0x01 && byteAddr[2] == 0x00
+                && byteAddr[3] == 0x00;
+    }
+
+    private static boolean isIpv6AddressULA(@NonNull InetAddress addr) {
+        return isIpv6Address(addr) && (addr.getAddress()[0] & 0xfe) == 0xfc;
+    }
+
+    private static boolean isIpv6Address6Bone(@NonNull InetAddress addr) {
+        if (!isIpv6Address(addr)) return false;
+        final byte[] byteAddr = addr.getAddress();
+        return byteAddr[0] == 0x3f && byteAddr[1] == (byte) 0xfe;
+    }
+
+    private static int getIpv6MulticastScope(@NonNull InetAddress addr) {
+        return !isIpv6Address(addr) ? 0 : (addr.getAddress()[1] & 0x0f);
+    }
+
+    private static int findScope(@NonNull InetAddress addr) {
+        if (isIpv6Address(addr)) {
+            if (addr.isMulticastAddress()) {
+                return getIpv6MulticastScope(addr);
+            } else if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+                /**
+                 * RFC 4291 section 2.5.3 says loopback is to be treated as having
+                 * link-local scope.
+                 */
+                return IPV6_ADDR_SCOPE_LINKLOCAL;
+            } else if (addr.isSiteLocalAddress()) {
+                return IPV6_ADDR_SCOPE_SITELOCAL;
+            } else {
+                return IPV6_ADDR_SCOPE_GLOBAL;
+            }
+        } else if (isIpv4Address(addr)) {
+            if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+                return IPV6_ADDR_SCOPE_LINKLOCAL;
+            } else {
+                /**
+                 * RFC 6724 section 3.2. Other IPv4 addresses, including private addresses
+                 * and shared addresses (100.64.0.0/10), are assigned global scope.
+                 */
+                return IPV6_ADDR_SCOPE_GLOBAL;
+            }
+        } else {
+            /**
+             * This should never happen.
+             * Return a scope with low priority as a last resort.
+             */
+            return IPV6_ADDR_SCOPE_NODELOCAL;
+        }
+    }
+
+    /**
+     * Get the precedence for a given IPv4/IPv6 address.
+     * RFC 6724, section 2.1.
+     *
+     * Note that Java will return an IPv4-mapped address as an IPv4 address.
+     */
+    private static int findPrecedence(@NonNull InetAddress addr) {
+        if (isIpv4Address(addr)) {
+            return 35;
+        } else if (isIpv6Address(addr)) {
+            if (addr.isLoopbackAddress()) {
+                return 50;
+            } else if (isIpv6Address6To4(addr)) {
+                return 30;
+            } else if (isIpv6AddressTeredo(addr)) {
+                return 5;
+            } else if (isIpv6AddressULA(addr)) {
+                return 3;
+            } else if (((Inet6Address) addr).isIPv4CompatibleAddress() || addr.isSiteLocalAddress()
+                    || isIpv6Address6Bone(addr)) {
+                return 1;
+            } else {
+                // All other IPv6 addresses, including global unicast addresses.
+                return 40;
+            }
+        } else {
+            return 1;
+        }
+    }
+
+    /**
+     * Find number of matching initial bits between the two addresses.
+     */
+    private static int compareIpv6PrefixMatchLen(@NonNull InetAddress srcAddr,
+            @NonNull InetAddress dstAddr) {
+        final byte[] srcByte = srcAddr.getAddress();
+        final byte[] dstByte = dstAddr.getAddress();
+
+        // This should never happen.
+        if (srcByte.length != dstByte.length) return 0;
+
+        for (int i = 0; i < dstByte.length; ++i) {
+            if (srcByte[i] == dstByte[i]) {
+                continue;
+            }
+            int x = BitUtils.uint8(srcByte[i]) ^ BitUtils.uint8(dstByte[i]);
+            return i * CHAR_BIT + (Integer.numberOfLeadingZeros(x) - 24);  // Java ints are 32 bits
+        }
+        return dstByte.length * CHAR_BIT;
+    }
+
+    /**
+     * Check if given network has Ipv4 capability
+     * This function matches the behaviour of have_ipv4 in the native resolver.
+     */
+    public static boolean haveIpv4(@Nullable Network network) {
+        final SocketAddress addrIpv4 =
+                new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0);
+        return checkConnectivity(network, AF_INET, addrIpv4);
+    }
+
+    /**
+     * Check if given network has Ipv6 capability
+     * This function matches the behaviour of have_ipv6 in the native resolver.
+     */
+    public static boolean haveIpv6(@Nullable Network network) {
+        final SocketAddress addrIpv6 =
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0);
+        return checkConnectivity(network, AF_INET6, addrIpv6);
+    }
+
+    private static boolean checkConnectivity(@Nullable Network network,
+            int domain, @NonNull SocketAddress addr) {
+        final FileDescriptor socket;
+        try {
+            socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+        } catch (ErrnoException e) {
+            return false;
+        }
+        try {
+            if (network != null) network.bindSocket(socket);
+            Os.connect(socket, addr);
+        } catch (IOException | ErrnoException e) {
+            return false;
+        } finally {
+            IoUtils.closeQuietly(socket);
+        }
+        return true;
+    }
+}
diff --git a/core/java/android/net/util/KeepaliveUtils.java b/core/java/android/net/util/KeepaliveUtils.java
index 569fed1..bfc4563 100644
--- a/core/java/android/net/util/KeepaliveUtils.java
+++ b/core/java/android/net/util/KeepaliveUtils.java
@@ -34,9 +34,6 @@
 
     public static final String TAG = "KeepaliveUtils";
 
-    // Minimum supported keepalive count per transport if the network supports keepalive.
-    public static final int MIN_SUPPORTED_KEEPALIVE_COUNT = 3;
-
     public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException {
         public KeepaliveDeviceConfigurationException(final String msg) {
             super(msg);
@@ -84,10 +81,7 @@
                 throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport);
             }
 
-            // Customized values should be either 0 to indicate the network doesn't support
-            // keepalive offload, or a positive value that is at least
-            // MIN_SUPPORTED_KEEPALIVE_COUNT if supported.
-            if (supported != 0 && supported < MIN_SUPPORTED_KEEPALIVE_COUNT) {
+            if (supported < 0) {
                 throw new KeepaliveDeviceConfigurationException(
                         "Invalid supported count " + supported + " for "
                                 + NetworkCapabilities.transportNameOf(transport));
diff --git a/core/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java
index 30c5cd9..4e88149 100644
--- a/core/java/android/net/util/MultinetworkPolicyTracker.java
+++ b/core/java/android/net/util/MultinetworkPolicyTracker.java
@@ -16,29 +16,32 @@
 
 package android.net.util;
 
+import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
+import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+
+import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.net.ConnectivityManager;
 import android.net.Uri;
 import android.os.Handler;
-import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.telephony.PhoneStateListener;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.Slog;
 
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.Arrays;
 import java.util.List;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
-
-import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
-import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
-
 /**
  * A class to encapsulate management of the "Smart Networking" capability of
  * avoiding bad Wi-Fi when, for example upstream connectivity is lost or
@@ -61,7 +64,7 @@
 
     private final Context mContext;
     private final Handler mHandler;
-    private final Runnable mReevaluateRunnable;
+    private final Runnable mAvoidBadWifiCallback;
     private final List<Uri> mSettingsUris;
     private final ContentResolver mResolver;
     private final SettingObserver mSettingObserver;
@@ -69,6 +72,7 @@
 
     private volatile boolean mAvoidBadWifi = true;
     private volatile int mMeteredMultipathPreference;
+    private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     public MultinetworkPolicyTracker(Context ctx, Handler handler) {
         this(ctx, handler, null);
@@ -77,12 +81,7 @@
     public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
         mContext = ctx;
         mHandler = handler;
-        mReevaluateRunnable = () -> {
-            if (updateAvoidBadWifi() && avoidBadWifiCallback != null) {
-                avoidBadWifiCallback.run();
-            }
-            updateMeteredMultipathPreference();
-        };
+        mAvoidBadWifiCallback = avoidBadWifiCallback;
         mSettingsUris = Arrays.asList(
             Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
             Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
@@ -91,10 +90,18 @@
         mBroadcastReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                reevaluate();
+                reevaluateInternal();
             }
         };
 
+        TelephonyManager.from(ctx).listen(new PhoneStateListener(handler.getLooper()) {
+            @Override
+            public void onActiveDataSubscriptionIdChanged(int subId) {
+                mActiveSubId = subId;
+                reevaluateInternal();
+            }
+        }, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+
         updateAvoidBadWifi();
         updateMeteredMultipathPreference();
     }
@@ -107,7 +114,7 @@
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         mContext.registerReceiverAsUser(
-                mBroadcastReceiver, UserHandle.ALL, intentFilter, null, null);
+                mBroadcastReceiver, UserHandle.ALL, intentFilter, null, mHandler);
 
         reevaluate();
     }
@@ -131,7 +138,12 @@
      * Whether the device or carrier configuration disables avoiding bad wifi by default.
      */
     public boolean configRestrictsAvoidBadWifi() {
-        return (mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi) == 0);
+        return (getResourcesForActiveSubId().getInteger(R.integer.config_networkAvoidBadWifi) == 0);
+    }
+
+    @NonNull
+    private Resources getResourcesForActiveSubId() {
+        return SubscriptionManager.getResourcesForSubId(mContext, mActiveSubId);
     }
 
     /**
@@ -147,7 +159,17 @@
 
     @VisibleForTesting
     public void reevaluate() {
-        mHandler.post(mReevaluateRunnable);
+        mHandler.post(this::reevaluateInternal);
+    }
+
+    /**
+     * Reevaluate the settings. Must be called on the handler thread.
+     */
+    private void reevaluateInternal() {
+        if (updateAvoidBadWifi() && mAvoidBadWifiCallback != null) {
+            mAvoidBadWifiCallback.run();
+        }
+        updateMeteredMultipathPreference();
     }
 
     public boolean updateAvoidBadWifi() {
diff --git a/core/java/android/net/util/SocketUtils.java b/core/java/android/net/util/SocketUtils.java
index 1364d8c..489a292 100644
--- a/core/java/android/net/util/SocketUtils.java
+++ b/core/java/android/net/util/SocketUtils.java
@@ -69,7 +69,10 @@
      */
     @NonNull
     public static SocketAddress makePacketSocketAddress(int protocol, int ifIndex) {
-        return new PacketSocketAddress((short) protocol, ifIndex);
+        return new PacketSocketAddress(
+                protocol /* sll_protocol */,
+                ifIndex /* sll_ifindex */,
+                null /* sll_addr */);
     }
 
     /**
@@ -77,7 +80,10 @@
      */
     @NonNull
     public static SocketAddress makePacketSocketAddress(int ifIndex, @NonNull byte[] hwAddr) {
-        return new PacketSocketAddress(ifIndex, hwAddr);
+        return new PacketSocketAddress(
+                0 /* sll_protocol */,
+                ifIndex /* sll_ifindex */,
+                hwAddr /* sll_addr */);
     }
 
     /**
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index eb347e7..bc698f9 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -1714,11 +1714,12 @@
     /**
      * Sets Secure NFC feature.
      * <p>This API is for the Settings application.
+     * @return True if successful
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean setNfcSecure(boolean enable) {
+    public boolean enableSecureNfc(boolean enable) {
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
@@ -1736,7 +1737,7 @@
      * @return True if device supports Secure NFC, false otherwise
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
-    public boolean deviceSupportsNfcSecure() {
+    public boolean isSecureNfcSupported() {
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
@@ -1751,12 +1752,12 @@
     /**
      * Checks Secure NFC feature is enabled.
      *
-     * @return True if device supports Secure NFC is enabled, false otherwise
+     * @return True if Secure NFC is enabled, false otherwise
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      * @throws UnsupportedOperationException if device doesn't support
-     *         Secure NFC functionality. {@link #deviceSupportsNfcSecure}
+     *         Secure NFC functionality. {@link #isSecureNfcSupported}
      */
-    public boolean isNfcSecureEnabled() {
+    public boolean isSecureNfcEnabled() {
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index ab0a0ef..a839ec1 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -114,7 +114,7 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public ApduServiceInfo(ResolveInfo info, String description,
+    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
             ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
             boolean requiresUnlock, int bannerResource, int uid,
             String settingsActivityName, String offHost, String staticOffHost) {
@@ -124,7 +124,7 @@
         this.mDynamicAidGroups = new HashMap<String, AidGroup>();
         this.mOffHostName = offHost;
         this.mStaticOffHostName = staticOffHost;
-        this.mOnHost = (offHost == null);
+        this.mOnHost = onHost;
         this.mRequiresDeviceUnlock = requiresUnlock;
         for (AidGroup aidGroup : staticAidGroups) {
             this.mStaticAidGroups.put(aidGroup.category, aidGroup);
@@ -570,7 +570,7 @@
             int bannerResource = source.readInt();
             int uid = source.readInt();
             String settingsActivityName = source.readString();
-            return new ApduServiceInfo(info, description, staticAidGroups,
+            return new ApduServiceInfo(info, onHost, description, staticAidGroups,
                     dynamicAidGroups, requiresUnlock, bannerResource, uid,
                     settingsActivityName, offHostName, staticOffHostName);
         }
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 3de1183..bf17671 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -752,6 +752,8 @@
                     Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
                 }
             } else {
+                // Clear the parcel before writing the exception
+                reply.setDataSize(0);
                 reply.setDataPosition(0);
                 reply.writeException(e);
             }
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 32735aa..4583483 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -233,22 +233,5 @@
                 Binder.restoreCallingIdentity(identity);
             }
         }
-
-        // Old methods; should go away
-        @Override
-        public void onProgressUpdated(int progress) throws RemoteException {
-            // TODO(b/111441001): remove from interface
-        }
-
-        @Override
-        public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
-            // TODO(b/111441001): remove from interface
-        }
-
-        @Override
-        public void onSectionComplete(String title, int status, int size, int durationMs)
-                throws RemoteException {
-            // TODO(b/111441001): remove from interface
-        }
     }
 }
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 7f60b9c..9b8a40a 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -150,6 +150,14 @@
     void startTethering(in String[] dhcpRanges);
 
     /**
+     * Start tethering services with the specified dhcp server range and
+     * DNS proxy config.
+     * {@code boolean} is used to control legacy DNS proxy server.
+     * {@code String[]} is a set of start end pairs defining the ranges.
+     */
+    void startTetheringWithConfiguration(boolean usingLegacyDnsProxy, in String[] dhcpRanges);
+
+    /**
      * Stop currently running tethering services
      */
     @UnsupportedAppUsage
@@ -246,27 +254,6 @@
      **/
 
     /**
-     * Return global network statistics summarized at an interface level,
-     * without any UID-level granularity.
-     */
-    NetworkStats getNetworkStatsSummaryDev();
-    NetworkStats getNetworkStatsSummaryXt();
-
-    /**
-     * Return detailed network statistics with UID-level granularity,
-     * including interface and tag details.
-     */
-    NetworkStats getNetworkStatsDetail();
-
-    /**
-     * Return detailed network statistics for the requested UID and interfaces,
-     * including interface and tag details.
-     * @param uid UID to obtain statistics for, or {@link NetworkStats#UID_ALL}.
-     * @param ifaces Interfaces to obtain statistics for, or {@link NetworkStats#INTERFACES_ALL}.
-     */
-    NetworkStats getNetworkStatsUidDetail(int uid, in String[] ifaces);
-
-    /**
      * Return summary of network statistics all tethering interfaces.
      */
     NetworkStats getNetworkStatsTethering(int how);
diff --git a/core/java/android/os/IServiceManager.java b/core/java/android/os/IServiceManager.java
index bc0690d..053c5ed 100644
--- a/core/java/android/os/IServiceManager.java
+++ b/core/java/android/os/IServiceManager.java
@@ -57,13 +57,6 @@
      */
     String[] listServices(int dumpFlags) throws RemoteException;
 
-    /**
-     * Assign a permission controller to the service manager.  After set, this
-     * interface is checked before any services are added.
-     */
-    void setPermissionController(IPermissionController controller)
-            throws RemoteException;
-
     static final String descriptor = "android.os.IServiceManager";
 
     int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java
index 1aeac5f..6a56a26 100644
--- a/core/java/android/os/IncidentReportArgs.java
+++ b/core/java/android/os/IncidentReportArgs.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -110,6 +111,7 @@
     /**
      * Print this report as a string.
      */
+    @NonNull
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("Incident(");
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index ae743fb..fe2fbb1 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -2797,65 +2797,67 @@
             return null;
         }
         Parcelable.Creator<?> creator;
+        HashMap<String, Parcelable.Creator<?>> map;
         synchronized (mCreators) {
-            HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);
+            map = mCreators.get(loader);
             if (map == null) {
                 map = new HashMap<>();
                 mCreators.put(loader, map);
             }
             creator = map.get(name);
-            if (creator == null) {
-                try {
-                    // If loader == null, explicitly emulate Class.forName(String) "caller
-                    // classloader" behavior.
-                    ClassLoader parcelableClassLoader =
-                            (loader == null ? getClass().getClassLoader() : loader);
-                    // Avoid initializing the Parcelable class until we know it implements
-                    // Parcelable and has the necessary CREATOR field. http://b/1171613.
-                    Class<?> parcelableClass = Class.forName(name, false /* initialize */,
-                            parcelableClassLoader);
-                    if (!Parcelable.class.isAssignableFrom(parcelableClass)) {
-                        throw new BadParcelableException("Parcelable protocol requires subclassing "
-                                + "from Parcelable on class " + name);
-                    }
-                    Field f = parcelableClass.getField("CREATOR");
-                    if ((f.getModifiers() & Modifier.STATIC) == 0) {
-                        throw new BadParcelableException("Parcelable protocol requires "
-                                + "the CREATOR object to be static on class " + name);
-                    }
-                    Class<?> creatorType = f.getType();
-                    if (!Parcelable.Creator.class.isAssignableFrom(creatorType)) {
-                        // Fail before calling Field.get(), not after, to avoid initializing
-                        // parcelableClass unnecessarily.
-                        throw new BadParcelableException("Parcelable protocol requires a "
-                                + "Parcelable.Creator object called "
-                                + "CREATOR on class " + name);
-                    }
-                    creator = (Parcelable.Creator<?>) f.get(null);
-                }
-                catch (IllegalAccessException e) {
-                    Log.e(TAG, "Illegal access when unmarshalling: " + name, e);
-                    throw new BadParcelableException(
-                            "IllegalAccessException when unmarshalling: " + name);
-                }
-                catch (ClassNotFoundException e) {
-                    Log.e(TAG, "Class not found when unmarshalling: " + name, e);
-                    throw new BadParcelableException(
-                            "ClassNotFoundException when unmarshalling: " + name);
-                }
-                catch (NoSuchFieldException e) {
-                    throw new BadParcelableException("Parcelable protocol requires a "
-                            + "Parcelable.Creator object called "
-                            + "CREATOR on class " + name);
-                }
-                if (creator == null) {
-                    throw new BadParcelableException("Parcelable protocol requires a "
-                            + "non-null Parcelable.Creator object called "
-                            + "CREATOR on class " + name);
-                }
+        }
+        if (creator != null) {
+            return creator;
+        }
 
-                map.put(name, creator);
+        try {
+            // If loader == null, explicitly emulate Class.forName(String) "caller
+            // classloader" behavior.
+            ClassLoader parcelableClassLoader =
+                    (loader == null ? getClass().getClassLoader() : loader);
+            // Avoid initializing the Parcelable class until we know it implements
+            // Parcelable and has the necessary CREATOR field. http://b/1171613.
+            Class<?> parcelableClass = Class.forName(name, false /* initialize */,
+                    parcelableClassLoader);
+            if (!Parcelable.class.isAssignableFrom(parcelableClass)) {
+                throw new BadParcelableException("Parcelable protocol requires subclassing "
+                        + "from Parcelable on class " + name);
             }
+            Field f = parcelableClass.getField("CREATOR");
+            if ((f.getModifiers() & Modifier.STATIC) == 0) {
+                throw new BadParcelableException("Parcelable protocol requires "
+                        + "the CREATOR object to be static on class " + name);
+            }
+            Class<?> creatorType = f.getType();
+            if (!Parcelable.Creator.class.isAssignableFrom(creatorType)) {
+                // Fail before calling Field.get(), not after, to avoid initializing
+                // parcelableClass unnecessarily.
+                throw new BadParcelableException("Parcelable protocol requires a "
+                        + "Parcelable.Creator object called "
+                        + "CREATOR on class " + name);
+            }
+            creator = (Parcelable.Creator<?>) f.get(null);
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Illegal access when unmarshalling: " + name, e);
+            throw new BadParcelableException(
+                    "IllegalAccessException when unmarshalling: " + name);
+        } catch (ClassNotFoundException e) {
+            Log.e(TAG, "Class not found when unmarshalling: " + name, e);
+            throw new BadParcelableException(
+                    "ClassNotFoundException when unmarshalling: " + name);
+        } catch (NoSuchFieldException e) {
+            throw new BadParcelableException("Parcelable protocol requires a "
+                    + "Parcelable.Creator object called "
+                    + "CREATOR on class " + name);
+        }
+        if (creator == null) {
+            throw new BadParcelableException("Parcelable protocol requires a "
+                    + "non-null Parcelable.Creator object called "
+                    + "CREATOR on class " + name);
+        }
+
+        synchronized (mCreators) {
+            map.put(name, creator);
         }
 
         return creator;
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index b13e68d..0c3f291 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -123,6 +123,7 @@
             IBinder binder = callback.asBinder();
             try {
                 Callback cb = new Callback(callback, cookie);
+                unregister(callback);
                 binder.linkToDeath(cb, 0);
                 mCallbacks.put(binder, cb);
                 return true;
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index b2ba928..9a9b030 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -17,13 +17,13 @@
 package android.os;
 
 import android.annotation.UnsupportedAppUsage;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.util.StatLogger;
 
-import java.util.HashMap;
 import java.util.Map;
 
 /** @hide */
@@ -38,7 +38,7 @@
      * Cache for the "well known" services, such as WM and AM.
      */
     @UnsupportedAppUsage
-    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
+    private static Map<String, IBinder> sCache = new ArrayMap<String, IBinder>();
 
     /**
      * We do the "slow log" at most once every this interval.
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index b7c026c..011dfa0 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -17,16 +17,18 @@
 package android.os;
 
 import android.annotation.UnsupportedAppUsage;
-import java.util.ArrayList;
 
+import java.util.ArrayList;
 
 /**
  * Native implementation of the service manager.  Most clients will only
- * care about getDefault() and possibly asInterface().
+ * care about asInterface().
+ *
  * @hide
  */
-public abstract class ServiceManagerNative extends Binder implements IServiceManager
-{
+public final class ServiceManagerNative {
+    private ServiceManagerNative() {}
+
     /**
      * Cast a Binder object into a service manager interface, generating
      * a proxy if needed.
@@ -38,76 +40,13 @@
             return null;
         }
         IServiceManager in =
-            (IServiceManager)obj.queryLocalInterface(descriptor);
+                (IServiceManager) obj.queryLocalInterface(IServiceManager.descriptor);
         if (in != null) {
             return in;
         }
 
         return new ServiceManagerProxy(obj);
     }
-
-    public ServiceManagerNative()
-    {
-        attachInterface(this, descriptor);
-    }
-
-    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
-    {
-        try {
-            switch (code) {
-                case IServiceManager.GET_SERVICE_TRANSACTION: {
-                    data.enforceInterface(IServiceManager.descriptor);
-                    String name = data.readString();
-                    IBinder service = getService(name);
-                    reply.writeStrongBinder(service);
-                    return true;
-                }
-
-                case IServiceManager.CHECK_SERVICE_TRANSACTION: {
-                    data.enforceInterface(IServiceManager.descriptor);
-                    String name = data.readString();
-                    IBinder service = checkService(name);
-                    reply.writeStrongBinder(service);
-                    return true;
-                }
-
-                case IServiceManager.ADD_SERVICE_TRANSACTION: {
-                    data.enforceInterface(IServiceManager.descriptor);
-                    String name = data.readString();
-                    IBinder service = data.readStrongBinder();
-                    boolean allowIsolated = data.readInt() != 0;
-                    int dumpPriority = data.readInt();
-                    addService(name, service, allowIsolated, dumpPriority);
-                    return true;
-                }
-
-                case IServiceManager.LIST_SERVICES_TRANSACTION: {
-                    data.enforceInterface(IServiceManager.descriptor);
-                    int dumpPriority = data.readInt();
-                    String[] list = listServices(dumpPriority);
-                    reply.writeStringArray(list);
-                    return true;
-                }
-
-                case IServiceManager.SET_PERMISSION_CONTROLLER_TRANSACTION: {
-                    data.enforceInterface(IServiceManager.descriptor);
-                    IPermissionController controller =
-                            IPermissionController.Stub.asInterface(
-                                    data.readStrongBinder());
-                    setPermissionController(controller);
-                    return true;
-                }
-            }
-        } catch (RemoteException e) {
-        }
-
-        return false;
-    }
-
-    public IBinder asBinder()
-    {
-        return this;
-    }
 }
 
 class ServiceManagerProxy implements IServiceManager {
@@ -188,17 +127,6 @@
         return array;
     }
 
-    public void setPermissionController(IPermissionController controller)
-            throws RemoteException {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-        data.writeInterfaceToken(IServiceManager.descriptor);
-        data.writeStrongBinder(controller.asBinder());
-        mRemote.transact(SET_PERMISSION_CONTROLLER_TRANSACTION, data, reply, 0);
-        reply.recycle();
-        data.recycle();
-    }
-
     @UnsupportedAppUsage
     private IBinder mRemote;
 }
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 03d5d3e..49ce40b 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -15,6 +15,7 @@
  */
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 
@@ -44,6 +45,7 @@
         this.errorCode = errorCode;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return super.toString() + " (code " + errorCode + ")";
diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java
index 6025c34..2ba3982 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -25,6 +25,8 @@
 
 import dalvik.system.VMRuntime;
 
+import libcore.io.IoUtils;
+
 import java.io.Closeable;
 import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
@@ -62,7 +64,7 @@
 
         mMemoryRegistration = new MemoryRegistration(mSize);
         mCleaner = Cleaner.create(mFileDescriptor,
-                new Closer(mFileDescriptor, mMemoryRegistration));
+                new Closer(mFileDescriptor.getInt$(), mMemoryRegistration));
     }
 
     /**
@@ -259,6 +261,9 @@
             mCleaner.clean();
             mCleaner = null;
         }
+
+        // Cleaner.clean doesn't clear the value of the file descriptor.
+        mFileDescriptor.setInt$(-1);
     }
 
     @Override
@@ -290,19 +295,24 @@
      * Cleaner that closes the FD
      */
     private static final class Closer implements Runnable {
+        // This is a copy of the FileDescriptor we're attached to, in order to avoid a reference
+        // cycle.
         private FileDescriptor mFd;
         private MemoryRegistration mMemoryReference;
 
-        private Closer(FileDescriptor fd, MemoryRegistration memoryReference) {
-            mFd = fd;
+        private Closer(int fd, MemoryRegistration memoryReference) {
+            mFd = new FileDescriptor();
+            mFd.setInt$(fd);
+            IoUtils.setFdOwner(mFd, this);
+
             mMemoryReference = memoryReference;
         }
 
         @Override
         public void run() {
-            try {
-                Os.close(mFd);
-            } catch (ErrnoException e) { /* swallow error */ }
+            IoUtils.closeQuietly(mFd);
+            mFd = null;
+
             mMemoryReference.release();
             mMemoryReference = null;
         }
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 8f2826c..69bdde3 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -21,6 +21,8 @@
 import android.os.IUpdateEngineCallback;
 import android.os.RemoteException;
 
+import java.io.FileDescriptor;
+
 /**
  * UpdateEngine handles calls to the update engine which takes care of A/B OTA
  * updates. It wraps up the update engine Binder APIs and exposes them as
@@ -187,6 +189,22 @@
     }
 
     /**
+     * Applies the payload passed as file descriptor {@code fd} instead of
+     * using the {@code file://} scheme.
+     *
+     * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and
+     * {@code headerKeyValuePairs} parameters.
+     */
+    public void applyPayload(FileDescriptor fd, long offset, long size,
+            String[] headerKeyValuePairs) {
+        try {
+            mUpdateEngine.applyPayloadFd(fd, offset, size, headerKeyValuePairs);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Permanently cancels an in-progress update.
      *
      * <p>See {@link #resetStatus} to undo a finshed update (only available
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 43f579d..0458b5e 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,5 +1,6 @@
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
@@ -186,7 +187,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o instanceof WorkSource) {
             WorkSource other = (WorkSource) o;
 
@@ -968,6 +969,7 @@
             mTags = tags;
         }
 
+        @NonNull
         @Override
         public String toString() {
             StringBuilder result = new StringBuilder("WorkChain{");
@@ -994,7 +996,7 @@
         }
 
         @Override
-        public boolean equals(Object o) {
+        public boolean equals(@Nullable Object o) {
             if (o instanceof WorkChain) {
                 WorkChain other = (WorkChain) o;
 
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index e8f4641..d438103 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -648,6 +648,31 @@
     }
 
     /**
+     * Notify the Zygote processes that boot completed.
+     */
+    public void bootCompleted() {
+        // Notify both the 32-bit and 64-bit zygote.
+        if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
+            bootCompleted(Build.SUPPORTED_32_BIT_ABIS[0]);
+        }
+        if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
+            bootCompleted(Build.SUPPORTED_64_BIT_ABIS[0]);
+        }
+    }
+
+    private void bootCompleted(String abi) {
+        try {
+            synchronized (mLock) {
+                ZygoteState state = openZygoteSocketIfNeeded(abi);
+                state.mZygoteOutputWriter.write("1\n--boot-completed\n");
+                state.mZygoteOutputWriter.flush();
+            }
+        } catch (Exception ex) {
+            throw new RuntimeException("Failed to inform zygote of boot_completed", ex);
+        }
+    }
+
+    /**
      * Push hidden API blacklisting exemptions into the zygote process(es).
      *
      * <p>The list of exemptions will take affect for all new processes forked from the zygote after
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index 57f1229..1562e6b 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -17,6 +17,7 @@
 package android.printservice;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
@@ -292,7 +293,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -313,6 +314,7 @@
         return true;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/provider/OWNERS b/core/java/android/provider/OWNERS
new file mode 100644
index 0000000..8b7d6ad
--- /dev/null
+++ b/core/java/android/provider/OWNERS
@@ -0,0 +1,4 @@
+per-file DeviceConfig.java = svetoslavganov@google.com
+per-file DeviceConfig.java = hackbod@google.com
+
+
diff --git a/core/java/android/provider/SearchIndexableData.java b/core/java/android/provider/SearchIndexableData.java
index a60be53..87f9af3 100644
--- a/core/java/android/provider/SearchIndexableData.java
+++ b/core/java/android/provider/SearchIndexableData.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.content.Context;
 
@@ -139,6 +140,7 @@
         context = ctx;
     }
 
+    @NonNull
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/provider/SearchIndexableResource.java b/core/java/android/provider/SearchIndexableResource.java
index 1eb1734..0765b6b 100644
--- a/core/java/android/provider/SearchIndexableResource.java
+++ b/core/java/android/provider/SearchIndexableResource.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.content.Context;
 
@@ -66,6 +67,7 @@
         super(context);
     }
 
+    @NonNull
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/service/euicc/EuiccProfileInfo.java b/core/java/android/service/euicc/EuiccProfileInfo.java
index 4a39782..94610e7 100644
--- a/core/java/android/service/euicc/EuiccProfileInfo.java
+++ b/core/java/android/service/euicc/EuiccProfileInfo.java
@@ -16,6 +16,7 @@
 package android.service.euicc;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
@@ -395,7 +396,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -430,6 +431,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "EuiccProfileInfo (nickname="
diff --git a/core/java/android/service/gatekeeper/IGateKeeperService.aidl b/core/java/android/service/gatekeeper/IGateKeeperService.aidl
deleted file mode 100644
index abc6466..0000000
--- a/core/java/android/service/gatekeeper/IGateKeeperService.aidl
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.gatekeeper;
-
-import android.service.gatekeeper.GateKeeperResponse;
-
-/**
- * Interface for communication with GateKeeper, the
- * secure password storage daemon.
- *
- * This must be kept manually in sync with system/core/gatekeeperd
- * until AIDL can generate both C++ and Java bindings.
- *
- * @hide
- */
-interface IGateKeeperService {
-    /**
-     * Enrolls a password, returning the handle to the enrollment to be stored locally.
-     * @param uid The Android user ID associated to this enrollment
-     * @param currentPasswordHandle The previously enrolled handle, or null if none
-     * @param currentPassword The previously enrolled plaintext password, or null if none.
-     *                        If provided, must verify against the currentPasswordHandle.
-     * @param desiredPassword The new desired password, for which a handle will be returned
-     *                        upon success.
-     * @return an EnrollResponse or null on failure
-     */
-    GateKeeperResponse enroll(int uid, in byte[] currentPasswordHandle, in byte[] currentPassword,
-            in byte[] desiredPassword);
-
-    /**
-     * Verifies an enrolled handle against a provided, plaintext blob.
-     * @param uid The Android user ID associated to this enrollment
-     * @param enrolledPasswordHandle The handle against which the provided password will be
-     *                               verified.
-     * @param The plaintext blob to verify against enrolledPassword.
-     * @return a VerifyResponse, or null on failure.
-     */
-    GateKeeperResponse verify(int uid, in byte[] enrolledPasswordHandle, in byte[] providedPassword);
-
-    /**
-     * Verifies an enrolled handle against a provided, plaintext blob.
-     * @param uid The Android user ID associated to this enrollment
-     * @param challenge a challenge to authenticate agaisnt the device credential. If successful
-     *                  authentication occurs, this value will be written to the returned
-     *                  authentication attestation.
-     * @param enrolledPasswordHandle The handle against which the provided password will be
-     *                               verified.
-     * @param The plaintext blob to verify against enrolledPassword.
-     * @return a VerifyResponse with an attestation, or null on failure.
-     */
-    GateKeeperResponse verifyChallenge(int uid, long challenge, in byte[] enrolledPasswordHandle,
-            in byte[] providedPassword);
-
-    /**
-     * Retrieves the secure identifier for the user with the provided Android ID,
-     * or 0 if none is found.
-     * @param uid the Android user id
-     */
-    long getSecureUserId(int uid);
-
-    /**
-     * Clears secure user id associated with the provided Android ID.
-     * Must be called when password is set to NONE.
-     * @param uid the Android user id.
-     */
-    void clearSecureUserId(int uid);
-
-    /**
-     * Notifies gatekeeper that device setup has been completed and any potentially still existing
-     * state from before a factory reset can be cleaned up (if it has not been already).
-     */
-    void reportDeviceSetupComplete();
-}
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 7348cf6..882659f 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -15,6 +15,7 @@
  */
 package android.service.notification;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.Notification;
@@ -162,6 +163,7 @@
         dest.writeInt(mUser);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "Adjustment{"
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index 76d5328..1df34a3 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -16,6 +16,8 @@
 package android.service.notification;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.RemoteInput;
@@ -213,7 +215,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -240,6 +242,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("NotificationStats{");
diff --git a/core/java/android/service/notification/SnoozeCriterion.java b/core/java/android/service/notification/SnoozeCriterion.java
index bd93eff..ab93cda 100644
--- a/core/java/android/service/notification/SnoozeCriterion.java
+++ b/core/java/android/service/notification/SnoozeCriterion.java
@@ -15,6 +15,7 @@
  */
 package android.service.notification;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -118,7 +119,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
diff --git a/core/java/android/service/resolver/ResolverTarget.java b/core/java/android/service/resolver/ResolverTarget.java
index fb3e2d7..149d2fd 100644
--- a/core/java/android/service/resolver/ResolverTarget.java
+++ b/core/java/android/service/resolver/ResolverTarget.java
@@ -16,13 +16,10 @@
 
 package android.service.resolver;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
-import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.ArrayMap;
-
-import java.util.Map;
 
 /**
  * A ResolverTarget contains features by which an app or option will be ranked, in
@@ -173,6 +170,7 @@
     }
 
     // serialize the class to a string.
+    @NonNull
     @Override
     public String toString() {
         return "ResolverTarget{"
diff --git a/core/java/android/util/ByteStringUtils.java b/core/java/android/util/ByteStringUtils.java
deleted file mode 100644
index e4798f0..0000000
--- a/core/java/android/util/ByteStringUtils.java
+++ /dev/null
@@ -1,57 +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.util;
-
-import libcore.util.HexEncoding;
-
-/**
- * A utility class for common byte array to hex string operations and vise versa.
- *
- * @hide
- */
-public final class ByteStringUtils {
-
-    private ByteStringUtils() {
-    /* hide constructor */
-    }
-
-    /**
-     * Returns the hex encoded string representation of bytes.
-     * @param bytes Byte array to encode.
-     * @return Hex encoded string representation of bytes.
-     */
-    public static String toHexString(byte[] bytes) {
-        if (bytes == null || bytes.length == 0 || bytes.length % 2 != 0) {
-            return null;
-        }
-
-        return HexEncoding.encodeToString(bytes, true /* upperCase */);
-    }
-
-    /**
-     * Returns the decoded byte array representation of str.
-     * @param str Hex encoded string to decode.
-     * @return Decoded byte array representation of str.
-     */
-    public static byte[] fromHexToByteArray(String str) {
-        if (str == null || str.length() == 0 || str.length() % 2 != 0) {
-            return null;
-        }
-
-        return HexEncoding.decode(str, false /* allowSingleChar */);
-    }
-}
diff --git a/core/java/android/util/Half.java b/core/java/android/util/Half.java
index 70d049a..fe536a6 100644
--- a/core/java/android/util/Half.java
+++ b/core/java/android/util/Half.java
@@ -20,6 +20,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import libcore.util.FP16;
+
 /**
  * <p>The {@code Half} class is a wrapper and a utility class to manipulate half-precision 16-bit
  * <a href="https://en.wikipedia.org/wiki/Half-precision_floating-point_format">IEEE 754</a>
@@ -148,25 +150,6 @@
      */
     public static final @HalfFloat short POSITIVE_ZERO = (short) 0x0000;
 
-    private static final int FP16_SIGN_SHIFT        = 15;
-    private static final int FP16_SIGN_MASK         = 0x8000;
-    private static final int FP16_EXPONENT_SHIFT    = 10;
-    private static final int FP16_EXPONENT_MASK     = 0x1f;
-    private static final int FP16_SIGNIFICAND_MASK  = 0x3ff;
-    private static final int FP16_EXPONENT_BIAS     = 15;
-    private static final int FP16_COMBINED          = 0x7fff;
-    private static final int FP16_EXPONENT_MAX      = 0x7c00;
-
-    private static final int FP32_SIGN_SHIFT        = 31;
-    private static final int FP32_EXPONENT_SHIFT    = 23;
-    private static final int FP32_EXPONENT_MASK     = 0xff;
-    private static final int FP32_SIGNIFICAND_MASK  = 0x7fffff;
-    private static final int FP32_EXPONENT_BIAS     = 127;
-    private static final int FP32_QNAN_MASK         = 0x400000;
-
-    private static final int FP32_DENORMAL_MAGIC = 126 << 23;
-    private static final float FP32_DENORMAL_FLOAT = Float.intBitsToFloat(FP32_DENORMAL_MAGIC);
-
     private final @HalfFloat short mValue;
 
     /**
@@ -414,16 +397,7 @@
      *          than {@code y}
      */
     public static int compare(@HalfFloat short x, @HalfFloat short y) {
-        if (less(x, y)) return -1;
-        if (greater(x, y)) return 1;
-
-        // Collapse NaNs, akin to halfToIntBits(), but we want to keep
-        // (signed) short value types to preserve the ordering of -0.0
-        // and +0.0
-        short xBits = (x & FP16_COMBINED) > FP16_EXPONENT_MAX ? NaN : x;
-        short yBits = (y & FP16_COMBINED) > FP16_EXPONENT_MAX ? NaN : y;
-
-        return (xBits == yBits ? 0 : (xBits < yBits ? -1 : 1));
+        return FP16.compare(x, y);
     }
 
     /**
@@ -440,7 +414,7 @@
      * @see #halfToIntBits(short)
      */
     public static @HalfFloat short halfToShortBits(@HalfFloat short h) {
-        return (h & FP16_COMBINED) > FP16_EXPONENT_MAX ? NaN : h;
+        return (h & FP16.EXPONENT_SIGNIFICAND_MASK) > FP16.POSITIVE_INFINITY ? NaN : h;
     }
 
     /**
@@ -459,7 +433,7 @@
      * @see #intBitsToHalf(int)
      */
     public static int halfToIntBits(@HalfFloat short h) {
-        return (h & FP16_COMBINED) > FP16_EXPONENT_MAX ? NaN : h & 0xffff;
+        return (h & FP16.EXPONENT_SIGNIFICAND_MASK) > FP16.POSITIVE_INFINITY ? NaN : h & 0xffff;
     }
 
     /**
@@ -505,7 +479,7 @@
      *         of the second parameter
      */
     public static @HalfFloat short copySign(@HalfFloat short magnitude, @HalfFloat short sign) {
-        return (short) ((sign & FP16_SIGN_MASK) | (magnitude & FP16_COMBINED));
+        return (short) ((sign & FP16.SIGN_MASK) | (magnitude & FP16.EXPONENT_SIGNIFICAND_MASK));
     }
 
     /**
@@ -523,7 +497,7 @@
      * @return The absolute value of the specified half-precision float
      */
     public static @HalfFloat short abs(@HalfFloat short h) {
-        return (short) (h & FP16_COMBINED);
+        return (short) (h & FP16.EXPONENT_SIGNIFICAND_MASK);
     }
 
     /**
@@ -538,26 +512,18 @@
      * the result is zero (with the same sign)</li>
      * </ul>
      *
+     * <p class=note>
+     * <strong>Note:</strong> Unlike the identically named
+     * <code class=prettyprint>int java.lang.Math.round(float)</code> method,
+     * this returns a Half value stored in a short, <strong>not</strong> an
+     * actual short integer result.
+     *
      * @param h A half-precision float value
      * @return The value of the specified half-precision float rounded to the nearest
      *         half-precision float value
      */
     public static @HalfFloat short round(@HalfFloat short h) {
-        int bits = h & 0xffff;
-        int e = bits & 0x7fff;
-        int result = bits;
-
-        if (e < 0x3c00) {
-            result &= FP16_SIGN_MASK;
-            result |= (0x3c00 & (e >= 0x3800 ? 0xffff : 0x0));
-        } else if (e < 0x6400) {
-            e = 25 - (e >> 10);
-            int mask = (1 << e) - 1;
-            result += (1 << (e - 1));
-            result &= ~mask;
-        }
-
-        return (short) result;
+        return FP16.rint(h);
     }
 
     /**
@@ -577,21 +543,7 @@
      *         greater than or equal to the specified half-precision float value
      */
     public static @HalfFloat short ceil(@HalfFloat short h) {
-        int bits = h & 0xffff;
-        int e = bits & 0x7fff;
-        int result = bits;
-
-        if (e < 0x3c00) {
-            result &= FP16_SIGN_MASK;
-            result |= 0x3c00 & -(~(bits >> 15) & (e != 0 ? 1 : 0));
-        } else if (e < 0x6400) {
-            e = 25 - (e >> 10);
-            int mask = (1 << e) - 1;
-            result += mask & ((bits >> 15) - 1);
-            result &= ~mask;
-        }
-
-        return (short) result;
+        return FP16.ceil(h);
     }
 
     /**
@@ -611,21 +563,7 @@
      *         less than or equal to the specified half-precision float value
      */
     public static @HalfFloat short floor(@HalfFloat short h) {
-        int bits = h & 0xffff;
-        int e = bits & 0x7fff;
-        int result = bits;
-
-        if (e < 0x3c00) {
-            result &= FP16_SIGN_MASK;
-            result |= 0x3c00 & (bits > 0x8000 ? 0xffff : 0x0);
-        } else if (e < 0x6400) {
-            e = 25 - (e >> 10);
-            int mask = (1 << e) - 1;
-            result += mask & -(bits >> 15);
-            result &= ~mask;
-        }
-
-        return (short) result;
+        return FP16.floor(h);
     }
 
     /**
@@ -644,19 +582,7 @@
      *         half-precision float value
      */
     public static @HalfFloat short trunc(@HalfFloat short h) {
-        int bits = h & 0xffff;
-        int e = bits & 0x7fff;
-        int result = bits;
-
-        if (e < 0x3c00) {
-            result &= FP16_SIGN_MASK;
-        } else if (e < 0x6400) {
-            e = 25 - (e >> 10);
-            int mask = (1 << e) - 1;
-            result &= ~mask;
-        }
-
-        return (short) result;
+        return FP16.trunc(h);
     }
 
     /**
@@ -672,15 +598,7 @@
      * @return The smaller of the two specified half-precision values
      */
     public static @HalfFloat short min(@HalfFloat short x, @HalfFloat short y) {
-        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return NaN;
-        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return NaN;
-
-        if ((x & FP16_COMBINED) == 0 && (y & FP16_COMBINED) == 0) {
-            return (x & FP16_SIGN_MASK) != 0 ? x : y;
-        }
-
-        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <
-               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff) ? x : y;
+        return FP16.min(x, y);
     }
 
     /**
@@ -697,15 +615,7 @@
      * @return The larger of the two specified half-precision values
      */
     public static @HalfFloat short max(@HalfFloat short x, @HalfFloat short y) {
-        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return NaN;
-        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return NaN;
-
-        if ((x & FP16_COMBINED) == 0 && (y & FP16_COMBINED) == 0) {
-            return (x & FP16_SIGN_MASK) != 0 ? y : x;
-        }
-
-        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >
-               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff) ? x : y;
+        return FP16.max(x, y);
     }
 
     /**
@@ -719,11 +629,7 @@
      * @return True if x is less than y, false otherwise
      */
     public static boolean less(@HalfFloat short x, @HalfFloat short y) {
-        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
-        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
-
-        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <
-               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+        return FP16.less(x, y);
     }
 
     /**
@@ -737,11 +643,7 @@
      * @return True if x is less than or equal to y, false otherwise
      */
     public static boolean lessEquals(@HalfFloat short x, @HalfFloat short y) {
-        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
-        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
-
-        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <=
-               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+        return FP16.lessEquals(x, y);
     }
 
     /**
@@ -755,11 +657,7 @@
      * @return True if x is greater than y, false otherwise
      */
     public static boolean greater(@HalfFloat short x, @HalfFloat short y) {
-        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
-        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
-
-        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >
-               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+        return FP16.greater(x, y);
     }
 
     /**
@@ -773,11 +671,7 @@
      * @return True if x is greater than y, false otherwise
      */
     public static boolean greaterEquals(@HalfFloat short x, @HalfFloat short y) {
-        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
-        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
-
-        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >=
-               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+        return FP16.greaterEquals(x, y);
     }
 
     /**
@@ -791,10 +685,7 @@
      * @return True if x is equal to y, false otherwise
      */
     public static boolean equals(@HalfFloat short x, @HalfFloat short y) {
-        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
-        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
-
-        return x == y || ((x | y) & FP16_COMBINED) == 0;
+        return FP16.equals(x, y);
     }
 
     /**
@@ -804,7 +695,7 @@
      * @return 1 if the value is positive, -1 if the value is negative
      */
     public static int getSign(@HalfFloat short h) {
-        return (h & FP16_SIGN_MASK) == 0 ? 1 : -1;
+        return (h & FP16.SIGN_MASK) == 0 ? 1 : -1;
     }
 
     /**
@@ -818,7 +709,7 @@
      * @return The unbiased exponent of the specified value
      */
     public static int getExponent(@HalfFloat short h) {
-        return ((h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK) - FP16_EXPONENT_BIAS;
+        return ((h >>> FP16.EXPONENT_SHIFT) & FP16.SHIFTED_EXPONENT_MASK) - FP16.EXPONENT_BIAS;
     }
 
     /**
@@ -829,7 +720,7 @@
      * @return The significand, or significand, of the specified vlaue
      */
     public static int getSignificand(@HalfFloat short h) {
-        return h & FP16_SIGNIFICAND_MASK;
+        return h & FP16.SIGNIFICAND_MASK;
     }
 
     /**
@@ -841,7 +732,7 @@
      *         false otherwise
      */
     public static boolean isInfinite(@HalfFloat short h) {
-        return (h & FP16_COMBINED) == FP16_EXPONENT_MAX;
+        return FP16.isInfinite(h);
     }
 
     /**
@@ -852,7 +743,7 @@
      * @return True if the value is a NaN, false otherwise
      */
     public static boolean isNaN(@HalfFloat short h) {
-        return (h & FP16_COMBINED) > FP16_EXPONENT_MAX;
+        return FP16.isNaN(h);
     }
 
     /**
@@ -866,7 +757,7 @@
      * @return True if the value is normalized, false otherwise
      */
     public static boolean isNormalized(@HalfFloat short h) {
-        return (h & FP16_EXPONENT_MAX) != 0 && (h & FP16_EXPONENT_MAX) != FP16_EXPONENT_MAX;
+        return FP16.isNormalized(h);
     }
 
     /**
@@ -885,35 +776,7 @@
      * @return A normalized single-precision float value
      */
     public static float toFloat(@HalfFloat short h) {
-        int bits = h & 0xffff;
-        int s = bits & FP16_SIGN_MASK;
-        int e = (bits >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
-        int m = (bits                        ) & FP16_SIGNIFICAND_MASK;
-
-        int outE = 0;
-        int outM = 0;
-
-        if (e == 0) { // Denormal or 0
-            if (m != 0) {
-                // Convert denorm fp16 into normalized fp32
-                float o = Float.intBitsToFloat(FP32_DENORMAL_MAGIC + m);
-                o -= FP32_DENORMAL_FLOAT;
-                return s == 0 ? o : -o;
-            }
-        } else {
-            outM = m << 13;
-            if (e == 0x1f) { // Infinite or NaN
-                outE = 0xff;
-                if (outM != 0) { // SNaNs are quieted
-                    outM |= FP32_QNAN_MASK;
-                }
-            } else {
-                outE = e - FP16_EXPONENT_BIAS + FP32_EXPONENT_BIAS;
-            }
-        }
-
-        int out = (s << 16) | (outE << FP32_EXPONENT_SHIFT) | outM;
-        return Float.intBitsToFloat(out);
+        return FP16.toFloat(h);
     }
 
     /**
@@ -940,44 +803,7 @@
      */
     @SuppressWarnings("StatementWithEmptyBody")
     public static @HalfFloat short toHalf(float f) {
-        int bits = Float.floatToRawIntBits(f);
-        int s = (bits >>> FP32_SIGN_SHIFT    );
-        int e = (bits >>> FP32_EXPONENT_SHIFT) & FP32_EXPONENT_MASK;
-        int m = (bits                        ) & FP32_SIGNIFICAND_MASK;
-
-        int outE = 0;
-        int outM = 0;
-
-        if (e == 0xff) { // Infinite or NaN
-            outE = 0x1f;
-            outM = m != 0 ? 0x200 : 0;
-        } else {
-            e = e - FP32_EXPONENT_BIAS + FP16_EXPONENT_BIAS;
-            if (e >= 0x1f) { // Overflow
-                outE = 0x31;
-            } else if (e <= 0) { // Underflow
-                if (e < -10) {
-                    // The absolute fp32 value is less than MIN_VALUE, flush to +/-0
-                } else {
-                    // The fp32 value is a normalized float less than MIN_NORMAL,
-                    // we convert to a denorm fp16
-                    m = (m | 0x800000) >> (1 - e);
-                    if ((m & 0x1000) != 0) m += 0x2000;
-                    outM = m >> 13;
-                }
-            } else {
-                outE = e;
-                outM = m >> 13;
-                if ((m & 0x1000) != 0) {
-                    // Round to nearest "0.5" up
-                    int out = (outE << FP16_EXPONENT_SHIFT) | outM;
-                    out++;
-                    return (short) (out | (s << FP16_SIGN_SHIFT));
-                }
-            }
-        }
-
-        return (short) ((s << FP16_SIGN_SHIFT) | (outE << FP16_EXPONENT_SHIFT) | outM);
+        return FP16.toHalf(f);
     }
 
     /**
@@ -1073,40 +899,6 @@
      */
     @NonNull
     public static String toHexString(@HalfFloat short h) {
-        StringBuilder o = new StringBuilder();
-
-        int bits = h & 0xffff;
-        int s = (bits >>> FP16_SIGN_SHIFT    );
-        int e = (bits >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
-        int m = (bits                        ) & FP16_SIGNIFICAND_MASK;
-
-        if (e == 0x1f) { // Infinite or NaN
-            if (m == 0) {
-                if (s != 0) o.append('-');
-                o.append("Infinity");
-            } else {
-                o.append("NaN");
-            }
-        } else {
-            if (s == 1) o.append('-');
-            if (e == 0) {
-                if (m == 0) {
-                    o.append("0x0.0p0");
-                } else {
-                    o.append("0x0.");
-                    String significand = Integer.toHexString(m);
-                    o.append(significand.replaceFirst("0{2,}$", ""));
-                    o.append("p-14");
-                }
-            } else {
-                o.append("0x1.");
-                String significand = Integer.toHexString(m);
-                o.append(significand.replaceFirst("0{2,}$", ""));
-                o.append('p');
-                o.append(Integer.toString(e - FP16_EXPONENT_BIAS));
-            }
-        }
-
-        return o.toString();
+        return FP16.toHexString(h);
     }
 }
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 78331a1..b33b4a0 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1893,12 +1893,13 @@
     public static final boolean isWakeKey(int keyCode) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_BACK:
+            case KeyEvent.KEYCODE_CAMERA:
             case KeyEvent.KEYCODE_MENU:
-            case KeyEvent.KEYCODE_WAKEUP:
             case KeyEvent.KEYCODE_PAIRING:
             case KeyEvent.KEYCODE_STEM_1:
             case KeyEvent.KEYCODE_STEM_2:
             case KeyEvent.KEYCODE_STEM_3:
+            case KeyEvent.KEYCODE_WAKEUP:
                 return true;
         }
         return false;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 66f16d5..1ef2629 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -75,7 +75,8 @@
             boolean allLayers, boolean useIdentityTransform, int rotation);
     private static native GraphicBuffer nativeScreenshotToBuffer(IBinder displayToken,
             Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
-            boolean allLayers, boolean useIdentityTransform, int rotation);
+            boolean allLayers, boolean useIdentityTransform, int rotation,
+            boolean captureSecureLayers);
     private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
             Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
             boolean allLayers, boolean useIdentityTransform);
@@ -1299,7 +1300,28 @@
         IBinder displayToken = SurfaceControl.getBuiltInDisplay(
                 SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
         return nativeScreenshotToBuffer(displayToken, sourceCrop, width, height,
-                minLayer, maxLayer, false, useIdentityTransform, rotation);
+                minLayer, maxLayer, false, useIdentityTransform, rotation,
+                false /* captureSecureLayers */);
+    }
+
+    /**
+     * Like screenshotToBuffer, but if the caller is AID_SYSTEM, allows
+     * for the capture of secure layers. This is used for the screen rotation
+     * animation where the system server takes screenshots but does
+     * not persist them or allow them to leave the server. However in other
+     * cases in the system server, we mostly want to omit secure layers
+     * like when we take a screenshot on behalf of the assistant.
+     *
+     * @hide
+     */
+    public static GraphicBuffer screenshotToBufferWithSecureLayersUnsafe(Rect sourceCrop,
+            int width, int height, int minLayer, int maxLayer, boolean useIdentityTransform,
+            int rotation) {
+        IBinder displayToken = SurfaceControl.getBuiltInDisplay(
+                SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
+        return nativeScreenshotToBuffer(displayToken, sourceCrop, width, height,
+                minLayer, maxLayer, false, useIdentityTransform, rotation,
+                true /* captureSecureLayers */);
     }
 
     /**
diff --git a/core/java/android/view/textclassifier/OWNERS b/core/java/android/view/textclassifier/OWNERS
new file mode 100644
index 0000000..893a083
--- /dev/null
+++ b/core/java/android/view/textclassifier/OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 709498
+
+toki@google.com
+tonymak@google.com
+zilka@google.com
+jalt@google.com
+joannechung@google.com
+svetoslavganov@google.com
+eugeniom@google.com
+samsellem@google.com
\ No newline at end of file
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index a29d449..26c21753 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -102,9 +102,11 @@
 
     /**
      * Notify the host application that a page has finished loading. This method
-     * is called only for main frame. When onPageFinished() is called, the
-     * rendering picture may not be updated yet. To get the notification for the
-     * new Picture, use {@link WebView.PictureListener#onNewPicture}.
+     * is called only for main frame. Receiving an {@code onPageFinished()} callback does not
+     * guarantee that the next frame drawn by WebView will reflect the state of the DOM at this
+     * point. In order to be notified that the current DOM state is ready to be rendered, request a
+     * visual state callback with {@link WebView#postVisualStateCallback} and wait for the supplied
+     * callback to be triggered.
      *
      * @param view The WebView that is initiating the callback.
      * @param url The url of the page.
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index a85c585..b4a3661 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -88,6 +88,7 @@
     private float mTouchDownX;
     @UnsupportedAppUsage
     private boolean mIsDragging;
+    private float mTouchThumbOffset = 0.0f;
 
     public AbsSeekBar(Context context) {
         super(context);
@@ -775,6 +776,14 @@
 
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
+                if (mThumb != null) {
+                    final int availableWidth = getWidth() - mPaddingLeft - mPaddingRight;
+                    mTouchThumbOffset = (getProgress() - getMin()) / (float) (getMax()
+                        - getMin()) - (event.getX() - mPaddingLeft) / availableWidth;
+                    if (Math.abs(mTouchThumbOffset * availableWidth) > getThumbOffset()) {
+                        mTouchThumbOffset = 0;
+                    }
+                }
                 if (isInScrollingContainer()) {
                     mTouchDownX = event.getX();
                 } else {
@@ -857,7 +866,8 @@
             } else if (x < mPaddingLeft) {
                 scale = 1.0f;
             } else {
-                scale = (availableWidth - x + mPaddingLeft) / (float) availableWidth;
+                scale = (availableWidth - x + mPaddingLeft) / (float) availableWidth
+                    + mTouchThumbOffset;
                 progress = mTouchProgressOffset;
             }
         } else {
@@ -866,7 +876,7 @@
             } else if (x > width - mPaddingRight) {
                 scale = 1.0f;
             } else {
-                scale = (x - mPaddingLeft) / (float) availableWidth;
+                scale = (x - mPaddingLeft) / (float) availableWidth + mTouchThumbOffset;
                 progress = mTouchProgressOffset;
             }
         }
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 795b034..8716471 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -26,12 +26,14 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.text.format.DateUtils;
-import android.text.format.Time;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.RemoteViews.RemoteView;
 
-import java.util.TimeZone;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 
 /**
  * This widget display an analogic clock with two hands for hours and
@@ -45,7 +47,7 @@
 @RemoteView
 @Deprecated
 public class AnalogClock extends View {
-    private Time mCalendar;
+    private Clock mClock;
 
     @UnsupportedAppUsage
     private Drawable mHourHand;
@@ -97,7 +99,7 @@
             mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
         }
 
-        mCalendar = new Time();
+        mClock = Clock.systemDefaultZone();
 
         mDialWidth = mDial.getIntrinsicWidth();
         mDialHeight = mDial.getIntrinsicHeight();
@@ -130,7 +132,7 @@
         // in the main thread, therefore the receiver can't run before this method returns.
 
         // The time zone may have changed while the receiver wasn't registered, so update the Time
-        mCalendar = new Time();
+        mClock = Clock.systemDefaultZone();
 
         // Make sure we update to the current time
         onTimeChanged();
@@ -239,17 +241,18 @@
     }
 
     private void onTimeChanged() {
-        mCalendar.setToNow();
+        long nowMillis = mClock.millis();
+        LocalDateTime localDateTime = toLocalDateTime(nowMillis, mClock.getZone());
 
-        int hour = mCalendar.hour;
-        int minute = mCalendar.minute;
-        int second = mCalendar.second;
+        int hour = localDateTime.getHour();
+        int minute = localDateTime.getMinute();
+        int second = localDateTime.getSecond();
 
         mMinutes = minute + second / 60.0f;
         mHour = hour + mMinutes / 60.0f;
         mChanged = true;
 
-        updateContentDescription(mCalendar);
+        updateContentDescription(nowMillis);
     }
 
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -257,7 +260,7 @@
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
                 String tz = intent.getStringExtra("time-zone");
-                mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
+                mClock = Clock.system(ZoneId.of(tz));
             }
 
             onTimeChanged();
@@ -266,10 +269,17 @@
         }
     };
 
-    private void updateContentDescription(Time time) {
+    private void updateContentDescription(long timeMillis) {
         final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
-        String contentDescription = DateUtils.formatDateTime(mContext,
-                time.toMillis(false), flags);
+        String contentDescription = DateUtils.formatDateTime(mContext, timeMillis, flags);
         setContentDescription(contentDescription);
     }
+
+    private static LocalDateTime toLocalDateTime(long timeMillis, ZoneId zoneId) {
+        // java.time types like LocalDateTime / Instant can support the full range of "long millis"
+        // with room to spare so we do not need to worry about overflow / underflow and the
+        // resulting exceptions while the input to this class is a long.
+        Instant instant = Instant.ofEpochMilli(timeMillis);
+        return LocalDateTime.ofInstant(instant, zoneId);
+    }
 }
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index bf2762a..fedd1d2 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -20,7 +20,6 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.YEAR_IN_MILLIS;
-import static android.text.format.Time.getJulianDay;
 
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
@@ -32,7 +31,6 @@
 import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.os.Handler;
-import android.text.format.Time;
 import android.util.AttributeSet;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.RemoteViews.RemoteView;
@@ -40,10 +38,14 @@
 import com.android.internal.R;
 
 import java.text.DateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.temporal.JulianFields;
 import java.util.ArrayList;
-import java.util.Calendar;
 import java.util.Date;
-import java.util.TimeZone;
 
 //
 // TODO
@@ -62,8 +64,9 @@
     private static final int SHOW_TIME = 0;
     private static final int SHOW_MONTH_DAY_YEAR = 1;
 
-    Date mTime;
-    long mTimeMillis;
+    private long mTimeMillis;
+    // The LocalDateTime equivalent of mTimeMillis but truncated to minute, i.e. no seconds / nanos.
+    private LocalDateTime mLocalTime;
 
     int mLastDisplay = -1;
     DateFormat mLastFormat;
@@ -127,11 +130,10 @@
 
     @android.view.RemotableViewMethod
     @UnsupportedAppUsage
-    public void setTime(long time) {
-        Time t = new Time();
-        t.set(time);
-        mTimeMillis = t.toMillis(false);
-        mTime = new Date(t.year-1900, t.month, t.monthDay, t.hour, t.minute, 0);
+    public void setTime(long timeMillis) {
+        mTimeMillis = timeMillis;
+        LocalDateTime dateTime = toLocalDateTime(timeMillis, ZoneId.systemDefault());
+        mLocalTime = dateTime.withSecond(0);
         update();
     }
 
@@ -154,7 +156,7 @@
 
     @UnsupportedAppUsage
     void update() {
-        if (mTime == null || getVisibility() == GONE) {
+        if (mLocalTime == null || getVisibility() == GONE) {
             return;
         }
         if (mShowRelativeTime) {
@@ -163,31 +165,27 @@
         }
 
         int display;
-        Date time = mTime;
+        ZoneId zoneId = ZoneId.systemDefault();
 
-        Time t = new Time();
-        t.set(mTimeMillis);
-        t.second = 0;
+        // localTime is the local time for mTimeMillis but at zero seconds past the minute.
+        LocalDateTime localTime = mLocalTime;
+        LocalDateTime localStartOfDay =
+                LocalDateTime.of(localTime.toLocalDate(), LocalTime.MIDNIGHT);
+        LocalDateTime localTomorrowStartOfDay = localStartOfDay.plusDays(1);
+        // now is current local time but at zero seconds past the minute.
+        LocalDateTime localNow = LocalDateTime.now(zoneId).withSecond(0);
 
-        t.hour -= 12;
-        long twelveHoursBefore = t.toMillis(false);
-        t.hour += 12;
-        long twelveHoursAfter = t.toMillis(false);
-        t.hour = 0;
-        t.minute = 0;
-        long midnightBefore = t.toMillis(false);
-        t.monthDay++;
-        long midnightAfter = t.toMillis(false);
-
-        long nowMillis = System.currentTimeMillis();
-        t.set(nowMillis);
-        t.second = 0;
-        nowMillis = t.normalize(false);
+        long twelveHoursBefore = toEpochMillis(localTime.minusHours(12), zoneId);
+        long twelveHoursAfter = toEpochMillis(localTime.plusHours(12), zoneId);
+        long midnightBefore = toEpochMillis(localStartOfDay, zoneId);
+        long midnightAfter = toEpochMillis(localTomorrowStartOfDay, zoneId);
+        long time = toEpochMillis(localTime, zoneId);
+        long now = toEpochMillis(localNow, zoneId);
 
         // Choose the display mode
         choose_display: {
-            if ((nowMillis >= midnightBefore && nowMillis < midnightAfter)
-                    || (nowMillis >= twelveHoursBefore && nowMillis < twelveHoursAfter)) {
+            if ((now >= midnightBefore && now < midnightAfter)
+                    || (now >= twelveHoursBefore && now < twelveHoursAfter)) {
                 display = SHOW_TIME;
                 break choose_display;
             }
@@ -216,7 +214,7 @@
         }
 
         // Set the text
-        String text = format.format(mTime);
+        String text = format.format(new Date(time));
         setText(text);
 
         // Schedule the next update
@@ -225,7 +223,7 @@
             mUpdateTimeMillis = twelveHoursAfter > midnightAfter ? twelveHoursAfter : midnightAfter;
         } else {
             // Currently showing the date
-            if (mTimeMillis < nowMillis) {
+            if (mTimeMillis < now) {
                 // If the time is in the past, don't schedule an update
                 mUpdateTimeMillis = 0;
             } else {
@@ -266,15 +264,18 @@
             millisIncrease = HOUR_IN_MILLIS;
         } else if (duration < YEAR_IN_MILLIS) {
             // In weird cases it can become 0 because of daylight savings
-            TimeZone timeZone = TimeZone.getDefault();
-            count = Math.max(Math.abs(dayDistance(timeZone, mTimeMillis, now)), 1);
+            LocalDateTime localDateTime = mLocalTime;
+            ZoneId zoneId = ZoneId.systemDefault();
+            LocalDateTime localNow = toLocalDateTime(now, zoneId);
+
+            count = Math.max(Math.abs(dayDistance(localDateTime, localNow)), 1);
             result = String.format(getContext().getResources().getQuantityString(past
                             ? com.android.internal.R.plurals.duration_days_shortest
                             : com.android.internal.R.plurals.duration_days_shortest_future,
                             count),
                     count);
             if (past || count != 1) {
-                mUpdateTimeMillis = computeNextMidnight(timeZone);
+                mUpdateTimeMillis = computeNextMidnight(localNow, zoneId);
                 millisIncrease = -1;
             } else {
                 millisIncrease = DAY_IN_MILLIS;
@@ -300,18 +301,13 @@
     }
 
     /**
-     * @param timeZone the timezone we are in
-     * @return the timepoint in millis at UTC at midnight in the current timezone
+     * Returns the epoch millis for the next midnight in the specified timezone.
      */
-    private long computeNextMidnight(TimeZone timeZone) {
-        Calendar c = Calendar.getInstance();
-        c.setTimeZone(timeZone);
-        c.add(Calendar.DAY_OF_MONTH, 1);
-        c.set(Calendar.HOUR_OF_DAY, 0);
-        c.set(Calendar.MINUTE, 0);
-        c.set(Calendar.SECOND, 0);
-        c.set(Calendar.MILLISECOND, 0);
-        return c.getTimeInMillis();
+    private static long computeNextMidnight(LocalDateTime time, ZoneId zoneId) {
+        // This ignores the chance of overflow: it should never happen.
+        LocalDate tomorrow = time.toLocalDate().plusDays(1);
+        LocalDateTime nextMidnight = LocalDateTime.of(tomorrow, LocalTime.MIDNIGHT);
+        return toEpochMillis(nextMidnight, zoneId);
     }
 
     @Override
@@ -329,11 +325,10 @@
                 com.android.internal.R.string.now_string_shortest);
     }
 
-    // Return the date difference for the two times in a given timezone.
-    private static int dayDistance(TimeZone timeZone, long startTime,
-            long endTime) {
-        return getJulianDay(endTime, timeZone.getOffset(endTime) / 1000)
-                - getJulianDay(startTime, timeZone.getOffset(startTime) / 1000);
+    // Return the number of days between the two dates.
+    private static int dayDistance(LocalDateTime start, LocalDateTime end) {
+        return (int) (end.getLong(JulianFields.JULIAN_DAY)
+                - start.getLong(JulianFields.JULIAN_DAY));
     }
 
     private DateFormat getTimeFormat() {
@@ -378,8 +373,11 @@
                         count);
             } else if (duration < YEAR_IN_MILLIS) {
                 // In weird cases it can become 0 because of daylight savings
-                TimeZone timeZone = TimeZone.getDefault();
-                count = Math.max(Math.abs(dayDistance(timeZone, mTimeMillis, now)), 1);
+                LocalDateTime localDateTime = mLocalTime;
+                ZoneId zoneId = ZoneId.systemDefault();
+                LocalDateTime localNow = toLocalDateTime(now, zoneId);
+
+                count = Math.max(Math.abs(dayDistance(localDateTime, localNow)), 1);
                 result = String.format(getContext().getResources().getQuantityString(past
                                 ? com.android.internal.
                                         R.plurals.duration_days_relative
@@ -515,4 +513,17 @@
             }
         }
     }
+
+    private static LocalDateTime toLocalDateTime(long timeMillis, ZoneId zoneId) {
+        // java.time types like LocalDateTime / Instant can support the full range of "long millis"
+        // with room to spare so we do not need to worry about overflow / underflow and the rsulting
+        // exceptions while the input to this class is a long.
+        Instant instant = Instant.ofEpochMilli(timeMillis);
+        return LocalDateTime.ofInstant(instant, zoneId);
+    }
+
+    private static long toEpochMillis(LocalDateTime time, ZoneId zoneId) {
+        Instant instant = time.toInstant(zoneId.getRules().getOffset(time));
+        return instant.toEpochMilli();
+    }
 }
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 929496f..350094e 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -106,6 +106,8 @@
     // Lock to synchronize between the UI thread and the thread that handles pixel copy results.
     // Only sync mWindow writes from UI thread with mWindow reads from sPixelCopyHandlerThread.
     private final Object mLock = new Object();
+    // The lock used to synchronize the UI and render threads when a #dismiss is performed.
+    private final Object mDestroyLock = new Object();
 
     /**
      * Initializes a magnifier.
@@ -173,7 +175,7 @@
                             mParentSurface.mSurface,
                             mWindowWidth, mWindowHeight, mWindowElevation, mWindowCornerRadius,
                             Handler.getMain() /* draw the magnifier on the UI thread */, mLock,
-                            mCallback);
+                            mDestroyLock, mCallback);
                 }
             }
             performPixelCopy(startX, startY, true /* update window position */);
@@ -187,9 +189,11 @@
      */
     public void dismiss() {
         if (mWindow != null) {
-            synchronized (mLock) {
-                mWindow.destroy();
-                mWindow = null;
+            synchronized (mDestroyLock) {
+                synchronized (mLock) {
+                    mWindow.destroy();
+                    mWindow = null;
+                }
             }
             mPrevPosInView.x = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
             mPrevPosInView.y = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
@@ -478,14 +482,16 @@
         // is performed on the UI thread and a frame callback on the render thread.
         // When both mLock and mDestroyLock need to be held at the same time,
         // mDestroyLock should be acquired before mLock in order to avoid deadlocks.
-        private final Object mDestroyLock = new Object();
+        private final Object mDestroyLock;
 
         InternalPopupWindow(final Context context, final Display display,
                 final Surface parentSurface,
                 final int width, final int height, final float elevation, final float cornerRadius,
-                final Handler handler, final Object lock, final Callback callback) {
+                final Handler handler, final Object lock, final Object destroyLock,
+                final Callback callback) {
             mDisplay = display;
             mLock = lock;
+            mDestroyLock = destroyLock;
             mCallback = callback;
 
             mContentWidth = width;
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index b6ed22c..6f7d456 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -905,10 +905,12 @@
                 if (!mFlingScroller.isFinished()) {
                     mFlingScroller.forceFinished(true);
                     mAdjustScroller.forceFinished(true);
+                    onScrollerFinished(mFlingScroller);
                     onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                 } else if (!mAdjustScroller.isFinished()) {
                     mFlingScroller.forceFinished(true);
                     mAdjustScroller.forceFinished(true);
+                    onScrollerFinished(mAdjustScroller);
                 } else if (mLastDownEventY < mTopSelectionDividerTop) {
                     postChangeCurrentByOneFromLongPress(
                             false, ViewConfiguration.getLongPressTimeout());
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index fde01dd..750d698 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -195,7 +195,7 @@
         return applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp();
     }
 
-    private static boolean isDisclosureEnabled(Context context) {
+    public static boolean isDisclosureEnabled(Context context) {
         return Settings.Secure.getInt(context.getContentResolver(),
                 Settings.Secure.ASSIST_DISCLOSURE_ENABLED, 0) != 0;
     }
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
new file mode 100644
index 0000000..9049c3a
--- /dev/null
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+import android.content.pm.ApplicationInfo;
+
+/**
+ * Platform private API for talking with the PlatformCompat service.
+ *
+ * <p> Should be used for gating and logging from non-app processes.
+ * For app processes please use android.compat.Compatibility API.
+ *
+ * {@hide}
+ */
+interface IPlatformCompat
+{
+
+    /**
+     * Reports that a compatibility change is affecting an app process now.
+     *
+     * <p>Note: for changes that are gated using {@link #isChangeEnabled(long, ApplicationInfo)},
+     * you do not need to call this API directly. The change will be reported for you in the case
+     * that {@link #isChangeEnabled(long, ApplicationInfo)} returns {@code true}.
+     *
+     * @param changeId The ID of the compatibility change taking effect.
+     * @param appInfo Representing the affected app.
+     */
+    void reportChange(long changeId, in ApplicationInfo appInfo);
+
+    /**
+     * Query if a given compatibility change is enabled for an app process. This method should
+     * be called when implementing functionality on behalf of the affected app.
+     *
+     * <p>If this method returns {@code true}, the calling code should implement the compatibility
+     * change, resulting in differing behaviour compared to earlier releases. If this method returns
+     * {@code false}, the calling code should behave as it did in earlier releases.
+     *
+     * <p>When this method returns {@code true}, it will also report the change as
+     * {@link #reportChange(long, ApplicationInfo)} would, so there is no need to call that method
+     * directly.
+     *
+     * @param changeId The ID of the compatibility change in question.
+     * @param appInfo Representing the app in question.
+     * @return {@code true} if the change is enabled for the current app.
+     */
+    boolean isChangeEnabled(long changeId, in ApplicationInfo appInfo);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/http/HttpDateTime.java b/core/java/com/android/internal/http/HttpDateTime.java
deleted file mode 100644
index f7706e3..0000000
--- a/core/java/com/android/internal/http/HttpDateTime.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.http;
-
-import android.annotation.UnsupportedAppUsage;
-import android.text.format.Time;
-
-import java.util.Calendar;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Helper for parsing an HTTP date.
- */
-public final class HttpDateTime {
-
-    /*
-     * Regular expression for parsing HTTP-date.
-     *
-     * Wdy, DD Mon YYYY HH:MM:SS GMT
-     * RFC 822, updated by RFC 1123
-     *
-     * Weekday, DD-Mon-YY HH:MM:SS GMT
-     * RFC 850, obsoleted by RFC 1036
-     *
-     * Wdy Mon DD HH:MM:SS YYYY
-     * ANSI C's asctime() format
-     *
-     * with following variations
-     *
-     * Wdy, DD-Mon-YYYY HH:MM:SS GMT
-     * Wdy, (SP)D Mon YYYY HH:MM:SS GMT
-     * Wdy,DD Mon YYYY HH:MM:SS GMT
-     * Wdy, DD-Mon-YY HH:MM:SS GMT
-     * Wdy, DD Mon YYYY HH:MM:SS -HHMM
-     * Wdy, DD Mon YYYY HH:MM:SS
-     * Wdy Mon (SP)D HH:MM:SS YYYY
-     * Wdy Mon DD HH:MM:SS YYYY GMT
-     * 
-     * HH can be H if the first digit is zero.
-     * 
-     * Mon can be the full name of the month.
-     */
-    private static final String HTTP_DATE_RFC_REGEXP =
-            "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
-            + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
-
-    private static final String HTTP_DATE_ANSIC_REGEXP =
-            "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
-            + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
-
-    /**
-     * The compiled version of the HTTP-date regular expressions.
-     */
-    private static final Pattern HTTP_DATE_RFC_PATTERN =
-            Pattern.compile(HTTP_DATE_RFC_REGEXP);
-    private static final Pattern HTTP_DATE_ANSIC_PATTERN =
-            Pattern.compile(HTTP_DATE_ANSIC_REGEXP);
-
-    private static class TimeOfDay {
-        TimeOfDay(int h, int m, int s) {
-            this.hour = h;
-            this.minute = m;
-            this.second = s;
-        }
-        
-        int hour;
-        int minute;
-        int second;
-    }
-
-    @UnsupportedAppUsage
-    public static long parse(String timeString)
-            throws IllegalArgumentException {
-
-        int date = 1;
-        int month = Calendar.JANUARY;
-        int year = 1970;
-        TimeOfDay timeOfDay;
-
-        Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString);
-        if (rfcMatcher.find()) {
-            date = getDate(rfcMatcher.group(1));
-            month = getMonth(rfcMatcher.group(2));
-            year = getYear(rfcMatcher.group(3));
-            timeOfDay = getTime(rfcMatcher.group(4));
-        } else {
-            Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString);
-            if (ansicMatcher.find()) {
-                month = getMonth(ansicMatcher.group(1));
-                date = getDate(ansicMatcher.group(2));
-                timeOfDay = getTime(ansicMatcher.group(3));
-                year = getYear(ansicMatcher.group(4));
-            } else {
-                throw new IllegalArgumentException();
-            }
-        }
-
-        // FIXME: Y2038 BUG!
-        if (year >= 2038) {
-            year = 2038;
-            month = Calendar.JANUARY;
-            date = 1;
-        }
-
-        Time time = new Time(Time.TIMEZONE_UTC);
-        time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date,
-                month, year);
-        return time.toMillis(false /* use isDst */);
-    }
-
-    private static int getDate(String dateString) {
-        if (dateString.length() == 2) {
-            return (dateString.charAt(0) - '0') * 10
-                    + (dateString.charAt(1) - '0');
-        } else {
-            return (dateString.charAt(0) - '0');
-        }
-    }
-
-    /*
-     * jan = 9 + 0 + 13 = 22
-     * feb = 5 + 4 + 1 = 10
-     * mar = 12 + 0 + 17 = 29
-     * apr = 0 + 15 + 17 = 32
-     * may = 12 + 0 + 24 = 36
-     * jun = 9 + 20 + 13 = 42
-     * jul = 9 + 20 + 11 = 40
-     * aug = 0 + 20 + 6 = 26
-     * sep = 18 + 4 + 15 = 37
-     * oct = 14 + 2 + 19 = 35
-     * nov = 13 + 14 + 21 = 48
-     * dec = 3 + 4 + 2 = 9
-     */
-    private static int getMonth(String monthString) {
-        int hash = Character.toLowerCase(monthString.charAt(0)) +
-                Character.toLowerCase(monthString.charAt(1)) +
-                Character.toLowerCase(monthString.charAt(2)) - 3 * 'a';
-        switch (hash) {
-            case 22:
-                return Calendar.JANUARY;
-            case 10:
-                return Calendar.FEBRUARY;
-            case 29:
-                return Calendar.MARCH;
-            case 32:
-                return Calendar.APRIL;
-            case 36:
-                return Calendar.MAY;
-            case 42:
-                return Calendar.JUNE;
-            case 40:
-                return Calendar.JULY;
-            case 26:
-                return Calendar.AUGUST;
-            case 37:
-                return Calendar.SEPTEMBER;
-            case 35:
-                return Calendar.OCTOBER;
-            case 48:
-                return Calendar.NOVEMBER;
-            case 9:
-                return Calendar.DECEMBER;
-            default:
-                throw new IllegalArgumentException();
-        }
-    }
-
-    private static int getYear(String yearString) {
-        if (yearString.length() == 2) {
-            int year = (yearString.charAt(0) - '0') * 10
-                    + (yearString.charAt(1) - '0');
-            if (year >= 70) {
-                return year + 1900;
-            } else {
-                return year + 2000;
-            }
-        } else if (yearString.length() == 3) {
-            // According to RFC 2822, three digit years should be added to 1900.
-            int year = (yearString.charAt(0) - '0') * 100
-                    + (yearString.charAt(1) - '0') * 10
-                    + (yearString.charAt(2) - '0');
-            return year + 1900;
-        } else if (yearString.length() == 4) {
-             return (yearString.charAt(0) - '0') * 1000
-                    + (yearString.charAt(1) - '0') * 100
-                    + (yearString.charAt(2) - '0') * 10
-                    + (yearString.charAt(3) - '0');
-        } else {
-             return 1970;
-        }
-    }
-
-    private static TimeOfDay getTime(String timeString) {
-        // HH might be H
-        int i = 0;
-        int hour = timeString.charAt(i++) - '0';
-        if (timeString.charAt(i) != ':')
-            hour = hour * 10 + (timeString.charAt(i++) - '0');
-        // Skip ':'
-        i++;
-        
-        int minute = (timeString.charAt(i++) - '0') * 10
-                    + (timeString.charAt(i++) - '0');
-        // Skip ':'
-        i++;
-        
-        int second = (timeString.charAt(i++) - '0') * 10
-                  + (timeString.charAt(i++) - '0');
-
-        return new TimeOfDay(hour, minute, second);        
-    }
-}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 274c444..a47beb5 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -4938,11 +4938,11 @@
             final long uptime = mClocks.uptimeMillis();
 
             boolean updateHistory = false;
-            if (isScreenDoze(state)) {
+            if (isScreenDoze(state) && !isScreenDoze(oldState)) {
                 mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
                 mScreenDozeTimer.startRunningLocked(elapsedRealtime);
                 updateHistory = true;
-            } else if (isScreenDoze(oldState)) {
+            } else if (isScreenDoze(oldState) && !isScreenDoze(state)) {
                 mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG;
                 mScreenDozeTimer.stopRunningLocked(elapsedRealtime);
                 updateHistory = true;
diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java
index 86b2a90..b3b0ba1 100644
--- a/core/java/com/android/internal/os/KernelWakelockReader.java
+++ b/core/java/com/android/internal/os/KernelWakelockReader.java
@@ -16,12 +16,18 @@
 package com.android.internal.os;
 
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.StrictMode;
 import android.os.SystemClock;
+import android.system.suspend.ISuspendControlService;
+import android.system.suspend.WakeLockInfo;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.util.Iterator;
 
@@ -33,6 +39,7 @@
     private static int sKernelWakelockUpdateVersion = 0;
     private static final String sWakelockFile = "/proc/wakelocks";
     private static final String sWakeupSourceFile = "/d/wakeup_sources";
+    private static final String sSysClassWakeupDir = "/sys/class/wakeup";
 
     private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
         Process.PROC_TAB_TERM|Process.PROC_OUT_STRING|                // 0: name
@@ -58,6 +65,7 @@
 
     private final String[] mProcWakelocksName = new String[3];
     private final long[] mProcWakelocksData = new long[3];
+    private ISuspendControlService mSuspendControlService = null;
 
     /**
      * Reads kernel wakelock stats and updates the staleStats with the new information.
@@ -65,59 +73,130 @@
      * @return the updated data.
      */
     public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) {
-        byte[] buffer = new byte[32*1024];
-        int len = 0;
-        boolean wakeup_sources;
-        final long startTime = SystemClock.uptimeMillis();
+        boolean useSystemSuspend = (new File(sSysClassWakeupDir)).exists();
 
-        final int oldMask = StrictMode.allowThreadDiskReadsMask();
-        try {
-            FileInputStream is;
+        if (useSystemSuspend) {
+            // Get both kernel and native wakelock stats from SystemSuspend
+            updateVersion(staleStats);
+            if (getWakelockStatsFromSystemSuspend(staleStats) == null) {
+                Slog.w(TAG, "Failed to get wakelock stats from SystemSuspend");
+                return null;
+            }
+            return removeOldStats(staleStats);
+        } else {
+            byte[] buffer = new byte[32*1024];
+            int len = 0;
+            boolean wakeup_sources;
+            final long startTime = SystemClock.uptimeMillis();
+
+            final int oldMask = StrictMode.allowThreadDiskReadsMask();
             try {
-                is = new FileInputStream(sWakelockFile);
-                wakeup_sources = false;
-            } catch (java.io.FileNotFoundException e) {
+                FileInputStream is;
                 try {
-                    is = new FileInputStream(sWakeupSourceFile);
-                    wakeup_sources = true;
-                } catch (java.io.FileNotFoundException e2) {
-                    Slog.wtf(TAG, "neither " + sWakelockFile + " nor " +
-                            sWakeupSourceFile + " exists");
-                    return null;
+                    is = new FileInputStream(sWakelockFile);
+                    wakeup_sources = false;
+                } catch (java.io.FileNotFoundException e) {
+                    try {
+                        is = new FileInputStream(sWakeupSourceFile);
+                        wakeup_sources = true;
+                    } catch (java.io.FileNotFoundException e2) {
+                        Slog.wtf(TAG, "neither " + sWakelockFile + " nor " +
+                                sWakeupSourceFile + " exists");
+                        return null;
+                    }
+                }
+
+                int cnt;
+                while ((cnt = is.read(buffer, len, buffer.length - len)) > 0) {
+                    len += cnt;
+                }
+
+                is.close();
+            } catch (java.io.IOException e) {
+                Slog.wtf(TAG, "failed to read kernel wakelocks", e);
+                return null;
+            } finally {
+                StrictMode.setThreadPolicyMask(oldMask);
+            }
+
+            final long readTime = SystemClock.uptimeMillis() - startTime;
+            if (readTime > 100) {
+                Slog.w(TAG, "Reading wakelock stats took " + readTime + "ms");
+            }
+
+            if (len > 0) {
+                if (len >= buffer.length) {
+                    Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length);
+                }
+                int i;
+                for (i=0; i<len; i++) {
+                    if (buffer[i] == '\0') {
+                        len = i;
+                        break;
+                    }
                 }
             }
 
-            int cnt;
-            while ((cnt = is.read(buffer, len, buffer.length - len)) > 0) {
-                len += cnt;
+            updateVersion(staleStats);
+            // Get native wakelock stats from SystemSuspend
+            if (getWakelockStatsFromSystemSuspend(staleStats) == null) {
+                Slog.w(TAG, "Failed to get Native wakelock stats from SystemSuspend");
             }
+            // Get kernel wakelock stats
+            parseProcWakelocks(buffer, len, wakeup_sources, staleStats);
+            return removeOldStats(staleStats);
+        }
+    }
 
-            is.close();
-        } catch (java.io.IOException e) {
-            Slog.wtf(TAG, "failed to read kernel wakelocks", e);
+    /**
+     * On success, returns the updated stats from SystemSupend, else returns null.
+     */
+    private KernelWakelockStats getWakelockStatsFromSystemSuspend(
+            final KernelWakelockStats staleStats) {
+        WakeLockInfo[] wlStats = null;
+        if (mSuspendControlService == null) {
+            try {
+                mSuspendControlService = ISuspendControlService.Stub.asInterface(
+                    ServiceManager.getServiceOrThrow("suspend_control"));
+            } catch (ServiceNotFoundException e) {
+                Slog.wtf(TAG, "Required service suspend_control not available", e);
+                return null;
+            }
+        }
+
+        try {
+            wlStats = mSuspendControlService.getWakeLockStats();
+            updateWakelockStats(wlStats, staleStats);
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to obtain wakelock stats from ISuspendControlService", e);
             return null;
-        } finally {
-            StrictMode.setThreadPolicyMask(oldMask);
         }
 
-        final long readTime = SystemClock.uptimeMillis() - startTime;
-        if (readTime > 100) {
-            Slog.w(TAG, "Reading wakelock stats took " + readTime + "ms");
+        return staleStats;
+    }
+
+    /**
+     * Updates statleStats with stats from  SystemSuspend.
+     * @param staleStats Existing object to update.
+     * @return the updated stats.
+     */
+    @VisibleForTesting
+    public KernelWakelockStats updateWakelockStats(WakeLockInfo[] wlStats,
+                                                      final KernelWakelockStats staleStats) {
+        for (WakeLockInfo info : wlStats) {
+            if (!staleStats.containsKey(info.name)) {
+                staleStats.put(info.name, new KernelWakelockStats.Entry((int) info.activeCount,
+                        info.totalTime * 1000 /* ms to us */, sKernelWakelockUpdateVersion));
+            } else {
+                KernelWakelockStats.Entry kwlStats = staleStats.get(info.name);
+                kwlStats.mCount = (int) info.activeCount;
+                // Convert milliseconds to microseconds
+                kwlStats.mTotalTime = info.totalTime * 1000;
+                kwlStats.mVersion = sKernelWakelockUpdateVersion;
+            }
         }
 
-        if (len > 0) {
-            if (len >= buffer.length) {
-                Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length);
-            }
-            int i;
-            for (i=0; i<len; i++) {
-                if (buffer[i] == '\0') {
-                    len = i;
-                    break;
-                }
-            }
-        }
-        return parseProcWakelocks(buffer, len, wakeup_sources, staleStats);
+        return staleStats;
     }
 
     /**
@@ -138,7 +217,6 @@
         startIndex = endIndex = i + 1;
 
         synchronized(this) {
-            sKernelWakelockUpdateVersion++;
             while (endIndex < len) {
                 for (endIndex=startIndex;
                         endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
@@ -199,16 +277,35 @@
                 startIndex = endIndex + 1;
             }
 
-            // Don't report old data.
-            Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
-            while (itr.hasNext()) {
-                if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
-                    itr.remove();
-                }
-            }
-
-            staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion;
             return staleStats;
         }
     }
+
+    /**
+     * Increments sKernelWakelockUpdateVersion and updates the version in staleStats.
+     * @param staleStats Existing object to update.
+     * @return the updated stats.
+     */
+    @VisibleForTesting
+    public KernelWakelockStats updateVersion(KernelWakelockStats staleStats) {
+        sKernelWakelockUpdateVersion++;
+        staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion;
+        return staleStats;
+    }
+
+    /**
+     * Removes old stats from staleStats.
+     * @param staleStats Existing object to update.
+     * @return the updated stats.
+     */
+    @VisibleForTesting
+    public KernelWakelockStats removeOldStats(final KernelWakelockStats staleStats) {
+        Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
+        while (itr.hasNext()) {
+            if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
+                itr.remove();
+            }
+        }
+        return staleStats;
+    }
 }
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index eac150d..1de2e72 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -64,6 +64,19 @@
         return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
     }
 
+    public static void logUncaught(String threadName, String processName, int pid, Throwable e) {
+        StringBuilder message = new StringBuilder();
+        // The "FATAL EXCEPTION" string is still used on Android even though
+        // apps can set a custom UncaughtExceptionHandler that renders uncaught
+        // exceptions non-fatal.
+        message.append("FATAL EXCEPTION: ").append(threadName).append("\n");
+        if (processName != null) {
+            message.append("Process: ").append(processName).append(", ");
+        }
+        message.append("PID: ").append(pid);
+        Clog_e(TAG, message.toString(), e);
+    }
+
     /**
      * Logs a message when a thread encounters an uncaught exception. By
      * default, {@link KillApplicationHandler} will terminate this process later,
@@ -85,17 +98,7 @@
             if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
                 Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
             } else {
-                StringBuilder message = new StringBuilder();
-                // The "FATAL EXCEPTION" string is still used on Android even though
-                // apps can set a custom UncaughtExceptionHandler that renders uncaught
-                // exceptions non-fatal.
-                message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
-                final String processName = ActivityThread.currentProcessName();
-                if (processName != null) {
-                    message.append("Process: ").append(processName).append(", ");
-                }
-                message.append("PID: ").append(Process.myPid());
-                Clog_e(TAG, message.toString(), e);
+                logUncaught(t.getName(), ActivityThread.currentProcessName(), Process.myPid(), e);
             }
         }
     }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 14b511d..cfe05c9 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -99,6 +99,14 @@
      */
     public static final int USE_APP_IMAGE_STARTUP_CACHE = 1 << 16;
 
+    /**
+     * When set, application specified signal handlers are not chained (i.e, ignored)
+     * by the runtime.
+     *
+     * Used for debugging only. Usage: set debug.ignoreappsignalhandler to 1.
+     */
+    public static final int DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17;
+
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
     /** Default external storage should be mounted. */
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index af90b15..c64103f 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -169,6 +169,11 @@
     boolean mPidQuery;
 
     /**
+     * Whether the current arguments constitute a notification that boot completed.
+     */
+    boolean mBootCompleted;
+
+    /**
      * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, or
      * when they change, via --set-api-blacklist-exemptions.
      */
@@ -330,6 +335,8 @@
                 mAbiListQuery = true;
             } else if (arg.equals("--get-pid")) {
                 mPidQuery = true;
+            } else if (arg.equals("--boot-completed")) {
+                mBootCompleted = true;
             } else if (arg.startsWith("--instruction-set=")) {
                 mInstructionSet = arg.substring(arg.indexOf('=') + 1);
             } else if (arg.startsWith("--app-data-dir=")) {
@@ -364,7 +371,11 @@
             }
         }
 
-        if (mAbiListQuery || mPidQuery) {
+        if (mBootCompleted) {
+            if (args.length - curArg > 0) {
+                throw new IllegalArgumentException("Unexpected arguments after --boot-completed");
+            }
+        } else if (mAbiListQuery || mPidQuery) {
             if (args.length - curArg > 0) {
                 throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
             }
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ad9f64c..fc25a47 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -149,6 +149,11 @@
 
         parsedArgs = new ZygoteArguments(args);
 
+        if (parsedArgs.mBootCompleted) {
+            handleBootCompleted();
+            return null;
+        }
+
         if (parsedArgs.mAbiListQuery) {
             handleAbiListQuery();
             return null;
@@ -291,6 +296,10 @@
         }
     }
 
+    private void handleBootCompleted() {
+        VMRuntime.bootCompleted();
+    }
+
     /**
      * Preloads resources if the zygote is in lazily preload mode. Writes the result of the
      * preload operation; {@code 0} when a preload was initiated due to this request and {@code 1}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2abc8c0..b03e76b 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -94,11 +94,6 @@
     private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
     private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
 
-    /**
-     * when preloading, GC after allocating this many bytes
-     */
-    private static final int PRELOAD_GC_THRESHOLD = 50000;
-
     private static final String ABI_LIST_ARG = "--abi-list=";
 
     // TODO (chriswailes): Re-name this --zygote-socket-name= and then add a
@@ -278,11 +273,6 @@
             droppedPriviliges = true;
         }
 
-        // Alter the target heap utilization.  With explicit GCs this
-        // is not likely to have any effect.
-        float defaultUtilization = runtime.getTargetHeapUtilization();
-        runtime.setTargetHeapUtilization(0.8f);
-
         try {
             BufferedReader br =
                     new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
@@ -298,9 +288,6 @@
 
                 Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
                 try {
-                    if (false) {
-                        Log.v(TAG, "Preloading " + line + "...");
-                    }
                     // Load and explicitly initialize the given class. Use
                     // Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups
                     // (to derive the caller's class-loader). Use true to force initialization, and
@@ -331,8 +318,6 @@
             Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
         } finally {
             IoUtils.closeQuietly(is);
-            // Restore default.
-            runtime.setTargetHeapUtilization(defaultUtilization);
 
             // Fill in dex caches with classes, fields, and methods brought in by preloading.
             Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadDexCaches");
diff --git a/core/java/com/android/internal/util/TrafficStatsConstants.java b/core/java/com/android/internal/util/TrafficStatsConstants.java
index 2806ae2..413be48 100644
--- a/core/java/com/android/internal/util/TrafficStatsConstants.java
+++ b/core/java/com/android/internal/util/TrafficStatsConstants.java
@@ -40,4 +40,5 @@
     // {@link android.net.TrafficStats#TAG_NETWORK_STACK_IMPERSONATION_RANGE_START} and
     // {@link android.net.TrafficStats#TAG_NETWORK_STACK_IMPERSONATION_RANGE_END}.
     public static final int TAG_SYSTEM_PROBE = 0xFFFFFF81;
+    public static final int TAG_SYSTEM_DNS = 0xFFFFFF82;
 }
diff --git a/core/java/com/package.html b/core/java/com/package.html
new file mode 100644
index 0000000..8f35da9
--- /dev/null
+++ b/core/java/com/package.html
@@ -0,0 +1,8 @@
+<!--
+  This file is to hide classes in com.* packages from SDK
+-->
+<html>
+<body>
+    {@hide}
+</body>
+</html>
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 1854ea9..6c351fe 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -232,11 +232,19 @@
 // Copying (CC) garbage collector.
 static const char* kNoGenerationalCCRuntimeOption = "-Xgc:nogenerational_cc";
 
+// Phenotype property name for enabling profiling the boot class path.
+static const char* PROFILE_BOOT_CLASS_PATH = "profilebootclasspath";
+
 // Feature flag name for running the JIT in Zygote experiment, b/119800099.
 static const char* ENABLE_APEX_IMAGE = "enable_apex_image";
 // Flag to pass to the runtime when using the apex image.
 static const char* kApexImageOption = "-Ximage:/system/framework/apex.art";
 
+// Feature flag name for disabling lock profiling.
+static const char* DISABLE_LOCK_PROFILING = "disable_lock_profiling";
+// Runtime option disabling lock profiling.
+static const char* kLockProfThresholdRuntimeOption = "-Xlockprofthreshold:0";
+
 static AndroidRuntime* gCurRuntime = NULL;
 
 /*
@@ -613,7 +621,7 @@
  *
  * Returns 0 on success.
  */
-int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
+int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote)
 {
     JavaVMInitArgs initArgs;
     char propBuf[PROPERTY_VALUE_MAX];
@@ -672,6 +680,24 @@
     char jdwpProviderBuf[sizeof("-XjdwpProvider:") - 1 + PROPERTY_VALUE_MAX];
     char bootImageBuf[sizeof("-Ximage:") - 1 + PROPERTY_VALUE_MAX];
 
+    // Read if we are using the profile configuration, do this at the start since the last ART args
+    // take precedence.
+    property_get("dalvik.vm.profilebootclasspath", propBuf, "");
+    std::string profile_boot_class_path = propBuf;
+    // Empty means the property is unset and we should default to the phenotype property.
+    // The possible values are {"true", "false", ""}
+    if (profile_boot_class_path.empty()) {
+        profile_boot_class_path = server_configurable_flags::GetServerConfigurableFlag(
+                RUNTIME_NATIVE_BOOT_NAMESPACE,
+                PROFILE_BOOT_CLASS_PATH,
+                /*default_value=*/ "");
+    }
+    if (profile_boot_class_path == "true") {
+        addOption("-Xps-profile-boot-class-path");
+        addOption("-Xps-profile-aot-code");
+        addOption("-Xjitsaveprofilinginfo");
+    }
+
     std::string use_apex_image =
         server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE,
                                                              ENABLE_APEX_IMAGE,
@@ -685,6 +711,17 @@
         ALOGI("Using default boot image");
     }
 
+    std::string disable_lock_profiling =
+        server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE,
+                                                             DISABLE_LOCK_PROFILING,
+                                                             /*default_value=*/ "");
+    if (disable_lock_profiling == "true") {
+        addOption(kLockProfThresholdRuntimeOption);
+        ALOGI("Disabling lock profiling: '%s'\n", kLockProfThresholdRuntimeOption);
+    } else {
+        ALOGI("Leaving lock profiling enabled");
+    }
+
     bool checkJni = false;
     property_get("dalvik.vm.checkjni", propBuf, "");
     if (strcmp(propBuf, "true") == 0) {
@@ -733,6 +770,10 @@
     addOption("-verbose:gc");
     //addOption("-verbose:class");
 
+    if (primary_zygote) {
+        addOption("-Xprimaryzygote");
+    }
+
     /*
      * The default starting and maximum size of the heap.  Larger
      * values should be specified in a product property override.
@@ -774,13 +815,6 @@
     parseRuntimeOption("dalvik.vm.jittransitionweight",
                        jittransitionweightOptBuf,
                        "-Xjittransitionweight:");
-
-    property_get("dalvik.vm.profilebootimage", propBuf, "");
-    if (strcmp(propBuf, "true") == 0) {
-        addOption("-Xps-profile-boot-class-path");
-        addOption("-Xps-profile-aot-code");
-    }
-
     /*
      * Madvise related options.
      */
@@ -866,20 +900,16 @@
         addOption("-Ximage-compiler-option");
         addOption("--compiler-filter=speed-profile");
     } else {
-        // Make sure there is a preloaded-classes file.
-        if (!hasFile("/system/etc/preloaded-classes")) {
-            ALOGE("Missing preloaded-classes file, /system/etc/preloaded-classes not found: %s\n",
-                  strerror(errno));
-            return -1;
-        }
-        addOption("-Ximage-compiler-option");
-        addOption("--image-classes=/system/etc/preloaded-classes");
+        ALOGE("Missing boot-image.prof file, /system/etc/boot-image.prof not found: %s\n",
+              strerror(errno));
+        return -1;
+    }
 
-        // If there is a dirty-image-objects file, push it.
-        if (hasFile("/system/etc/dirty-image-objects")) {
-            addOption("-Ximage-compiler-option");
-            addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
-        }
+
+    // If there is a dirty-image-objects file, push it.
+    if (hasFile("/system/etc/dirty-image-objects")) {
+        addOption("-Ximage-compiler-option");
+        addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
     }
 
     property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, "");
@@ -1094,6 +1124,8 @@
             className != NULL ? className : "(unknown)", getuid());
 
     static const String8 startSystemServer("start-system-server");
+    // Whether this is the primary zygote, meaning the zygote which will fork system server.
+    bool primary_zygote = false;
 
     /*
      * 'startSystemServer == true' means runtime is obsolete and not run from
@@ -1101,6 +1133,7 @@
      */
     for (size_t i = 0; i < options.size(); ++i) {
         if (options[i] == startSystemServer) {
+            primary_zygote = true;
            /* track our progress through the boot sequence */
            const int LOG_BOOT_PROGRESS_START = 3000;
            LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,  ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
@@ -1136,7 +1169,7 @@
     JniInvocation jni_invocation;
     jni_invocation.Init(NULL);
     JNIEnv* env;
-    if (startVm(&mJavaVM, &env, zygote) != 0) {
+    if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
         return;
     }
     onVmCreated(env);
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index c5fc9b3..08aa1d9 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -18,26 +18,27 @@
 
 #include <vector>
 
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-#include "NetdClient.h"
-#include <utils/misc.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <utils/Log.h>
 #include <arpa/inet.h>
-#include <net/if.h>
 #include <linux/filter.h>
 #include <linux/if_arp.h>
 #include <linux/tcp.h>
+#include <net/if.h>
 #include <netinet/ether.h>
 #include <netinet/icmp6.h>
 #include <netinet/ip.h>
 #include <netinet/ip6.h>
 #include <netinet/udp.h>
-#include <cutils/properties.h>
 
+#include <android_runtime/AndroidRuntime.h>
+#include <cutils/properties.h>
+#include <utils/misc.h>
+#include <utils/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include "NetdClient.h"
 #include "core_jni_helpers.h"
+#include "jni.h"
 
 extern "C" {
 int ifc_enable(const char *ifname);
@@ -303,6 +304,21 @@
     jniSetFileDescriptorOfFD(env, javaFd, -1);
 }
 
+static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
+    unsigned dnsNetId = 0;
+    if (int res = getNetworkForDns(&dnsNetId) < 0) {
+        throwErrnoException(env, "getDnsNetId", -res);
+        return nullptr;
+    }
+    bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS;
+
+    static jclass class_Network = MakeGlobalRefOrDie(
+            env, FindClassOrDie(env, "android/net/Network"));
+    static jmethodID ctor = env->GetMethodID(class_Network, "<init>", "(IZ)V");
+    return env->NewObject(
+            class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass);
+}
+
 static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
     if (javaFd == NULL) {
         jniThrowNullPointerException(env, NULL);
@@ -359,6 +375,7 @@
     { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
     { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
     { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
+    { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork },
 };
 
 int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 62c4d76..bbd8ffe 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -306,6 +306,8 @@
                 whichHeap = HEAP_NATIVE;
             } else if (strncmp(name, "[stack", 6) == 0) {
                 whichHeap = HEAP_STACK;
+            } else if (strncmp(name, "[anon:stack_and_tls:", 20) == 0) {
+                whichHeap = HEAP_STACK;
             } else if (nameLen > 3 && strcmp(name+nameLen-3, ".so") == 0) {
                 whichHeap = HEAP_SO;
                 is_swappable = true;
diff --git a/core/jni/android_os_HwBlob.cpp b/core/jni/android_os_HwBlob.cpp
index cb55618..e5b72ca 100644
--- a/core/jni/android_os_HwBlob.cpp
+++ b/core/jni/android_os_HwBlob.cpp
@@ -88,7 +88,7 @@
       mOwnsBuffer(true),
       mHandle(0) {
     if (size > 0) {
-        mBuffer = malloc(size);
+        mBuffer = calloc(size, 1);
     }
 }
 
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 9556333..fc977f1 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -99,7 +99,9 @@
 
 static struct error_offsets_t
 {
-    jclass mClass;
+    jclass mError;
+    jclass mOutOfMemory;
+    jclass mStackOverflow;
 } gErrorOffsets;
 
 // ----------------------------------------------------------------------------
@@ -208,6 +210,16 @@
     return vm->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL;
 }
 
+static const char* GetErrorTypeName(JNIEnv* env, jthrowable error) {
+  if (env->IsInstanceOf(error, gErrorOffsets.mOutOfMemory)) {
+    return "OutOfMemoryError";
+  }
+  if (env->IsInstanceOf(error, gErrorOffsets.mStackOverflow)) {
+    return "StackOverflowError";
+  }
+  return nullptr;
+}
+
 // Report a java.lang.Error (or subclass). This will terminate the runtime by
 // calling FatalError with a message derived from the given error.
 static void report_java_lang_error_fatal_error(JNIEnv* env, jthrowable error,
@@ -217,7 +229,7 @@
 
     // Try to get the exception string. Sometimes logcat isn't available,
     // so try to add it to the abort message.
-    std::string exc_msg = "(Unknown exception message)";
+    std::string exc_msg;
     {
         ScopedLocalRef<jclass> exc_class(env, env->GetObjectClass(error));
         jmethodID method_id = env->GetMethodID(exc_class.get(), "toString",
@@ -226,15 +238,36 @@
                 env,
                 reinterpret_cast<jstring>(
                         env->CallObjectMethod(error, method_id)));
-        env->ExceptionClear();  // Just for good measure.
+        ScopedLocalRef<jthrowable> new_error(env, nullptr);
+        bool got_jstr = false;
+        if (env->ExceptionCheck()) {
+            new_error = ScopedLocalRef<jthrowable>(env, env->ExceptionOccurred());
+            env->ExceptionClear();
+        }
         if (jstr.get() != nullptr) {
             ScopedUtfChars jstr_utf(env, jstr.get());
             if (jstr_utf.c_str() != nullptr) {
                 exc_msg = jstr_utf.c_str();
+                got_jstr = true;
             } else {
+                new_error = ScopedLocalRef<jthrowable>(env, env->ExceptionOccurred());
                 env->ExceptionClear();
             }
         }
+        if (!got_jstr) {
+            exc_msg = "(Unknown exception message)";
+            const char* orig_type = GetErrorTypeName(env, error);
+            if (orig_type != nullptr) {
+                exc_msg = base::StringPrintf("%s (Error was %s)", exc_msg.c_str(), orig_type);
+            }
+            const char* new_type =
+                new_error == nullptr ? nullptr : GetErrorTypeName(env, new_error.get());
+            if (new_type != nullptr) {
+                exc_msg = base::StringPrintf("%s (toString() error was %s)",
+                                             exc_msg.c_str(),
+                                             new_type);
+            }
+        }
     }
 
     env->Throw(error);
@@ -292,7 +325,7 @@
         ALOGE("%s", msg);
     }
 
-    if (env->IsInstanceOf(excep, gErrorOffsets.mClass)) {
+    if (env->IsInstanceOf(excep, gErrorOffsets.mError)) {
         report_java_lang_error(env, excep, msg);
     }
 }
@@ -1417,10 +1450,13 @@
 
 static int int_register_android_os_BinderProxy(JNIEnv* env)
 {
-    jclass clazz = FindClassOrDie(env, "java/lang/Error");
-    gErrorOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
+    gErrorOffsets.mError = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/lang/Error"));
+    gErrorOffsets.mOutOfMemory =
+        MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/lang/OutOfMemoryError"));
+    gErrorOffsets.mStackOverflow =
+        MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/lang/StackOverflowError"));
 
-    clazz = FindClassOrDie(env, kBinderProxyPathName);
+    jclass clazz = FindClassOrDie(env, kBinderProxyPathName);
     gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
     gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance",
             "(JJ)Landroid/os/BinderProxy;");
diff --git a/core/jni/android_util_jar_StrictJarFile.cpp b/core/jni/android_util_jar_StrictJarFile.cpp
index e33da91..57688c4 100644
--- a/core/jni/android_util_jar_StrictJarFile.cpp
+++ b/core/jni/android_util_jar_StrictJarFile.cpp
@@ -112,7 +112,7 @@
 
 jobject StrictJarFile_nativeNextEntry(JNIEnv* env, jobject, jlong iterationHandle) {
   ZipEntry data;
-  ZipString entryName;
+  std::string entryName;
 
   IterationHandle* handle = reinterpret_cast<IterationHandle*>(iterationHandle);
   const int32_t error = Next(*handle->CookieAddress(), &data, &entryName);
@@ -121,10 +121,7 @@
     return NULL;
   }
 
-  std::unique_ptr<char[]> entryNameCString(new char[entryName.name_length + 1]);
-  memcpy(entryNameCString.get(), entryName.name, entryName.name_length);
-  entryNameCString[entryName.name_length] = '\0';
-  ScopedLocalRef<jstring> entryNameString(env, env->NewStringUTF(entryNameCString.get()));
+  ScopedLocalRef<jstring> entryNameString(env, env->NewStringUTF(entryName.c_str()));
 
   return newZipEntry(env, data, entryNameString.get());
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 5b4b5f2..04b0609 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -160,7 +160,7 @@
 static jobject nativeScreenshotToBuffer(JNIEnv* env, jclass clazz,
         jobject displayTokenObj, jobject sourceCropObj, jint width, jint height,
         jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform,
-        int rotation) {
+        int rotation, bool captureSecureLayers) {
     sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
     if (displayToken == NULL) {
         return NULL;
@@ -171,9 +171,10 @@
         maxLayer = INT32_MAX;
     }
     sp<GraphicBuffer> buffer;
+    bool capturedSecureLayers = false;
     status_t res = ScreenshotClient::capture(displayToken,
             sourceCrop, width, height, minLayer, maxLayer, useIdentityTransform,
-            rotation, &buffer);
+            rotation, captureSecureLayers, &buffer, capturedSecureLayers);
     if (res != NO_ERROR) {
         return NULL;
     }
@@ -184,7 +185,8 @@
             buffer->getHeight(),
             buffer->getPixelFormat(),
             (jint)buffer->getUsage(),
-            (jlong)buffer.get());
+            (jlong)buffer.get(),
+            capturedSecureLayers);
 }
 
 static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz,
@@ -306,7 +308,8 @@
                                        buffer->getHeight(),
                                        buffer->getPixelFormat(),
                                        (jint)buffer->getUsage(),
-                                       (jlong)buffer.get());
+                                       (jlong)buffer.get(),
+                                       false /* capturedSecureLayers */);
 }
 
 static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) {
@@ -1026,7 +1029,7 @@
     {"nativeGetHandle", "(J)Landroid/os/IBinder;",
             (void*)nativeGetHandle },
     {"nativeScreenshotToBuffer",
-     "(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZI)Landroid/graphics/GraphicBuffer;",
+     "(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZIZ)Landroid/graphics/GraphicBuffer;",
      (void*)nativeScreenshotToBuffer },
     {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/graphics/Rect;F)Landroid/graphics/GraphicBuffer;",
             (void*)nativeCaptureLayers },
@@ -1082,7 +1085,7 @@
     jclass graphicsBufferClazz = FindClassOrDie(env, "android/graphics/GraphicBuffer");
     gGraphicBufferClassInfo.clazz = MakeGlobalRefOrDie(env, graphicsBufferClazz);
     gGraphicBufferClassInfo.builder = GetStaticMethodIDOrDie(env, graphicsBufferClazz,
-            "createFromExisting", "(IIIIJ)Landroid/graphics/GraphicBuffer;");
+            "createFromExisting", "(IIIIJZ)Landroid/graphics/GraphicBuffer;");
 
     return err;
 }
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 04d2706..7e3b343 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1099,6 +1099,7 @@
   /*
    *  Grant the following capabilities to the Bluetooth user:
    *    - CAP_WAKE_ALARM
+   *    - CAP_NET_ADMIN
    *    - CAP_NET_RAW
    *    - CAP_NET_BIND_SERVICE (for DHCP client functionality)
    *    - CAP_SYS_NICE (for setting RT priority for audio-related threads)
@@ -1106,6 +1107,7 @@
 
   if (multiuser_get_app_id(uid) == AID_BLUETOOTH) {
     capabilities |= (1LL << CAP_WAKE_ALARM);
+    capabilities |= (1LL << CAP_NET_ADMIN);
     capabilities |= (1LL << CAP_NET_RAW);
     capabilities |= (1LL << CAP_NET_BIND_SERVICE);
     capabilities |= (1LL << CAP_SYS_NICE);
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 2071b98..7a8bc93 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -79,9 +79,9 @@
     return true;
   }
 
-  // Jars from the runtime apex are allowed.
-  static const char* kRuntimeApexPrefix = "/apex/com.android.runtime/javalib/";
-  if (android::base::StartsWith(path, kRuntimeApexPrefix)
+  // Jars from the ART APEX are allowed.
+  static const char* kArtApexPrefix = "/apex/com.android.art/javalib/";
+  if (android::base::StartsWith(path, kArtApexPrefix)
       && android::base::EndsWith(path, kJarSuffix)) {
     return true;
   }
@@ -238,7 +238,7 @@
   }
 
   if (!whitelist->IsAllowed(file_path)) {
-    fail_fn(std::string("Not whitelisted : ").append(file_path));
+    fail_fn(android::base::StringPrintf("Not whitelisted (%d): %s", fd, file_path.c_str()));
   }
 
   // File descriptor flags : currently on FD_CLOEXEC. We can set these
diff --git a/core/jni/include/android_runtime/AndroidRuntime.h b/core/jni/include/android_runtime/AndroidRuntime.h
index 3ec8b1f..9d803f6 100644
--- a/core/jni/include/android_runtime/AndroidRuntime.h
+++ b/core/jni/include/android_runtime/AndroidRuntime.h
@@ -131,7 +131,7 @@
                                     const char* runtimeArg,
                                     const char* quotingArg);
     void parseExtraOpts(char* extraOptsBuf, const char* quotingArg);
-    int startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote);
+    int startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote);
 
     Vector<JavaVMOption> mOptions;
     bool mExitWithoutCleanup;
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 480b1ea..d31df38 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -12,7 +12,7 @@
 yro@google.com
 
 # Settings UI
-per-file settings_enums.proto=zhfan@google.com
+per-file settings_enums.proto=tmfang@google.com
 
 # Frameworks
 ogunwale@google.com
@@ -20,3 +20,6 @@
 
 # Launcher
 hyunyoungs@google.com
+
+# Graphics stats
+jreck@google.com
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 973fa5a..c9fef43 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -446,8 +446,7 @@
         }
         optional Telephony telephony = 38;
 
-        optional string url_legal = 39;
-        optional string url_legal_android_privacy = 40;
+        reserved 39, 40; // Removed url_legal* props
 
         message Vendor {
             optional string build_date = 1;
diff --git a/core/proto/android/server/connectivity/Android.bp b/core/proto/android/server/connectivity/Android.bp
index c0ac2cb..4136239 100644
--- a/core/proto/android/server/connectivity/Android.bp
+++ b/core/proto/android/server/connectivity/Android.bp
@@ -21,5 +21,4 @@
         "data_stall_event.proto",
     ],
     sdk_version: "system_current",
-    no_framework_libs: true,
-}
\ No newline at end of file
+}
diff --git a/core/proto/android/stats/connectivity/Android.bp b/core/proto/android/stats/connectivity/Android.bp
index 5aa4ddb..5d642d38 100644
--- a/core/proto/android/stats/connectivity/Android.bp
+++ b/core/proto/android/stats/connectivity/Android.bp
@@ -21,5 +21,4 @@
         "network_stack.proto",
     ],
     sdk_version: "system_current",
-    no_framework_libs: true,
-}
\ No newline at end of file
+}
diff --git a/core/proto/android/stats/dnsresolver/Android.bp b/core/proto/android/stats/dnsresolver/Android.bp
index 0b5aa86..1e8c763 100644
--- a/core/proto/android/stats/dnsresolver/Android.bp
+++ b/core/proto/android/stats/dnsresolver/Android.bp
@@ -21,5 +21,4 @@
         "dns_resolver.proto",
     ],
     sdk_version: "system_current",
-    no_framework_libs: true,
 }
diff --git a/core/proto/android/stats/dnsresolver/dns_resolver.proto b/core/proto/android/stats/dnsresolver/dns_resolver.proto
index af6fea0..9eaabfb 100644
--- a/core/proto/android/stats/dnsresolver/dns_resolver.proto
+++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto
@@ -1,214 +1,217 @@
-/*

- * Copyright (C) 2019 The Android Open Source Project

- *

- * Licensed under the Apache License, Version 2.0 (the "License");

- * you may not use this file except in compliance with the License.

- * You may obtain a copy of the License at

- *

- *      http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing, software

- * distributed under the License is distributed on an "AS IS" BASIS,

- * WITHOUT 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.stats.dnsresolver;

-

-enum EventType {

-    EVENT_UNKNOWN = 0;

-    EVENT_GETADDRINFO = 1;

-    EVENT_GETHOSTBYNAME = 2;

-    EVENT_GETHOSTBYADDR = 3;

-    EVENT_RES_NSEND = 4;

-}

-

-// The return value of the DNS resolver for each DNS lookups.

-// bionic/libc/include/netdb.h

-// system/netd/resolv/include/netd_resolv/resolv.h

-enum ReturnCode {

-    RC_EAI_NO_ERROR = 0;

-    RC_EAI_ADDRFAMILY = 1;

-    RC_EAI_AGAIN = 2;

-    RC_EAI_BADFLAGS = 3;

-    RC_EAI_FAIL = 4;

-    RC_EAI_FAMILY = 5;

-    RC_EAI_MEMORY = 6;

-    RC_EAI_NODATA = 7;

-    RC_EAI_NONAME = 8;

-    RC_EAI_SERVICE = 9;

-    RC_EAI_SOCKTYPE = 10;

-    RC_EAI_SYSTEM = 11;

-    RC_EAI_BADHINTS = 12;

-    RC_EAI_PROTOCOL = 13;

-    RC_EAI_OVERFLOW = 14;

-    RC_RESOLV_TIMEOUT = 255;

-    RC_EAI_MAX = 256;

-}

-

-enum NsRcode {

-    NS_R_NO_ERROR = 0;  // No error occurred.

-    NS_R_FORMERR = 1;   // Format error.

-    NS_R_SERVFAIL = 2;  // Server failure.

-    NS_R_NXDOMAIN = 3;  // Name error.

-    NS_R_NOTIMPL = 4;   // Unimplemented.

-    NS_R_REFUSED = 5;   // Operation refused.

-    // these are for BIND_UPDATE

-    NS_R_YXDOMAIN = 6;  // Name exists

-    NS_R_YXRRSET = 7;   // RRset exists

-    NS_R_NXRRSET = 8;   // RRset does not exist

-    NS_R_NOTAUTH = 9;   // Not authoritative for zone

-    NS_R_NOTZONE = 10;  // Zone of record different from zone section

-    NS_R_MAX = 11;

-    // The following are EDNS extended rcodes

-    NS_R_BADVERS = 16;

-    // The following are TSIG errors

-    // NS_R_BADSIG  = 16,

-    NS_R_BADKEY = 17;

-    NS_R_BADTIME = 18;

-}

-

-// Currently defined type values for resources and queries.

-enum NsType {

-    NS_T_INVALID = 0;      // Cookie.

-    NS_T_A = 1;            // Host address.

-    NS_T_NS = 2;           // Authoritative server.

-    NS_T_MD = 3;           // Mail destination.

-    NS_T_MF = 4;           // Mail forwarder.

-    NS_T_CNAME = 5;        // Canonical name.

-    NS_T_SOA = 6;          // Start of authority zone.

-    NS_T_MB = 7;           // Mailbox domain name.

-    NS_T_MG = 8;           // Mail group member.

-    NS_T_MR = 9;           // Mail rename name.

-    NS_T_NULL = 10;        // Null resource record.

-    NS_T_WKS = 11;         // Well known service.

-    NS_T_PTR = 12;         // Domain name pointer.

-    NS_T_HINFO = 13;       // Host information.

-    NS_T_MINFO = 14;       // Mailbox information.

-    NS_T_MX = 15;          // Mail routing information.

-    NS_T_TXT = 16;         // Text strings.

-    NS_T_RP = 17;          // Responsible person.

-    NS_T_AFSDB = 18;       // AFS cell database.

-    NS_T_X25 = 19;         // X_25 calling address.

-    NS_T_ISDN = 20;        // ISDN calling address.

-    NS_T_RT = 21;          // Router.

-    NS_T_NSAP = 22;        // NSAP address.

-    NS_T_NSAP_PTR = 23;    // Reverse NSAP lookup (deprecated).

-    NS_T_SIG = 24;         // Security signature.

-    NS_T_KEY = 25;         // Security key.

-    NS_T_PX = 26;          // X.400 mail mapping.

-    NS_T_GPOS = 27;        // Geographical position (withdrawn).

-    NS_T_AAAA = 28;        // IPv6 Address.

-    NS_T_LOC = 29;         // Location Information.

-    NS_T_NXT = 30;         // Next domain (security).

-    NS_T_EID = 31;         // Endpoint identifier.

-    NS_T_NIMLOC = 32;      // Nimrod Locator.

-    NS_T_SRV = 33;         // Server Selection.

-    NS_T_ATMA = 34;        // ATM Address

-    NS_T_NAPTR = 35;       // Naming Authority PoinTeR

-    NS_T_KX = 36;          // Key Exchange

-    NS_T_CERT = 37;        // Certification record

-    NS_T_A6 = 38;          // IPv6 address (experimental)

-    NS_T_DNAME = 39;       // Non-terminal DNAME

-    NS_T_SINK = 40;        // Kitchen sink (experimentatl)

-    NS_T_OPT = 41;         // EDNS0 option (meta-RR)

-    NS_T_APL = 42;         // Address prefix list (RFC 3123)

-    NS_T_DS = 43;          // Delegation Signer

-    NS_T_SSHFP = 44;       // SSH Fingerprint

-    NS_T_IPSECKEY = 45;    // IPSEC Key

-    NS_T_RRSIG = 46;       // RRset Signature

-    NS_T_NSEC = 47;        // Negative security

-    NS_T_DNSKEY = 48;      // DNS Key

-    NS_T_DHCID = 49;       // Dynamic host configuratin identifier

-    NS_T_NSEC3 = 50;       // Negative security type 3

-    NS_T_NSEC3PARAM = 51;  // Negative security type 3 parameters

-    NS_T_HIP = 55;         // Host Identity Protocol

-    NS_T_SPF = 99;         // Sender Policy Framework

-    NS_T_TKEY = 249;       // Transaction key

-    NS_T_TSIG = 250;       // Transaction signature.

-    NS_T_IXFR = 251;       // Incremental zone transfer.

-    NS_T_AXFR = 252;       // Transfer zone of authority.

-    NS_T_MAILB = 253;      // Transfer mailbox records.

-    NS_T_MAILA = 254;      // Transfer mail agent records.

-    NS_T_ANY = 255;        // Wildcard match.

-    NS_T_ZXFR = 256;       // BIND-specific, nonstandard.

-    NS_T_DLV = 32769;      // DNSSEC look-aside validatation.

-    NS_T_MAX = 65536;

-}

-

-enum IpVersion {

-    IV_UNKNOWN = 0;

-    IV_IPV4 = 1;

-    IV_IPV6 = 2;

-}

-

-enum TransportType {

-    TT_UNKNOWN = 0;

-    TT_UDP = 1;

-    TT_TCP = 2;

-    TT_DOT = 3;

-}

-

-enum PrivateDnsModes {

-    PDM_UNKNOWN = 0;

-    PDM_OFF = 1;

-    PDM_OPPORTUNISTIC = 2;

-    PDM_STRICT = 3;

-}

-

-enum Transport {

-    // Indicates this network uses a Cellular transport.

-    TRANSPORT_DEFAULT = 0;  // TRANSPORT_CELLULAR

-    // 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;

-}

-

-enum CacheStatus{

-    // the cache can't handle that kind of queries.

-    // or the answer buffer is too small.

-    CS_UNSUPPORTED = 0;

-    // the cache doesn't know about this query.

-    CS_NOTFOUND = 1;

-    // the cache found the answer.

-    CS_FOUND = 2;

-    // Don't do anything on cache.

-    CS_SKIP = 3;

-}

-

-message DnsQueryEvent {

-    optional android.stats.dnsresolver.NsRcode rcode = 1;

-

-    optional android.stats.dnsresolver.NsType type = 2;

-

-    optional android.stats.dnsresolver.CacheStatus cache_hit = 3;

-

-    optional android.stats.dnsresolver.IpVersion ip_version = 4;

-

-    optional android.stats.dnsresolver.TransportType transport = 5;

-

-    // Number of DNS query retry times

-    optional int32 retry_times = 6;

-

-    // Ordinal number of name server.

-    optional int32 dns_server_count = 7;

-

-    // Used only by TCP and DOT. True for new connections.

-    optional bool connected = 8;

-

-    optional int32 latency_micros = 9;

-}

-

-message DnsQueryEvents {

-    repeated DnsQueryEvent dns_query_event = 1;

-}

+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.stats.dnsresolver;
+
+enum EventType {
+    EVENT_UNKNOWN = 0;
+    EVENT_GETADDRINFO = 1;
+    EVENT_GETHOSTBYNAME = 2;
+    EVENT_GETHOSTBYADDR = 3;
+    EVENT_RES_NSEND = 4;
+}
+
+// The return value of the DNS resolver for each DNS lookups.
+// bionic/libc/include/netdb.h
+// system/netd/resolv/include/netd_resolv/resolv.h
+enum ReturnCode {
+    RC_EAI_NO_ERROR = 0;
+    RC_EAI_ADDRFAMILY = 1;
+    RC_EAI_AGAIN = 2;
+    RC_EAI_BADFLAGS = 3;
+    RC_EAI_FAIL = 4;
+    RC_EAI_FAMILY = 5;
+    RC_EAI_MEMORY = 6;
+    RC_EAI_NODATA = 7;
+    RC_EAI_NONAME = 8;
+    RC_EAI_SERVICE = 9;
+    RC_EAI_SOCKTYPE = 10;
+    RC_EAI_SYSTEM = 11;
+    RC_EAI_BADHINTS = 12;
+    RC_EAI_PROTOCOL = 13;
+    RC_EAI_OVERFLOW = 14;
+    RC_RESOLV_TIMEOUT = 255;
+    RC_EAI_MAX = 256;
+}
+
+enum NsRcode {
+    NS_R_NO_ERROR = 0;  // No error occurred.
+    NS_R_FORMERR = 1;   // Format error.
+    NS_R_SERVFAIL = 2;  // Server failure.
+    NS_R_NXDOMAIN = 3;  // Name error.
+    NS_R_NOTIMPL = 4;   // Unimplemented.
+    NS_R_REFUSED = 5;   // Operation refused.
+    // these are for BIND_UPDATE
+    NS_R_YXDOMAIN = 6;  // Name exists
+    NS_R_YXRRSET = 7;   // RRset exists
+    NS_R_NXRRSET = 8;   // RRset does not exist
+    NS_R_NOTAUTH = 9;   // Not authoritative for zone
+    NS_R_NOTZONE = 10;  // Zone of record different from zone section
+    NS_R_MAX = 11;
+    // The following are EDNS extended rcodes
+    NS_R_BADVERS = 16;
+    // The following are TSIG errors
+    // NS_R_BADSIG  = 16,
+    NS_R_BADKEY = 17;
+    NS_R_BADTIME = 18;
+    NS_R_INTERNAL_ERROR = 254;
+    NS_R_TIMEOUT = 255;
+}
+
+// Currently defined type values for resources and queries.
+enum NsType {
+    NS_T_INVALID = 0;      // Cookie.
+    NS_T_A = 1;            // Host address.
+    NS_T_NS = 2;           // Authoritative server.
+    NS_T_MD = 3;           // Mail destination.
+    NS_T_MF = 4;           // Mail forwarder.
+    NS_T_CNAME = 5;        // Canonical name.
+    NS_T_SOA = 6;          // Start of authority zone.
+    NS_T_MB = 7;           // Mailbox domain name.
+    NS_T_MG = 8;           // Mail group member.
+    NS_T_MR = 9;           // Mail rename name.
+    NS_T_NULL = 10;        // Null resource record.
+    NS_T_WKS = 11;         // Well known service.
+    NS_T_PTR = 12;         // Domain name pointer.
+    NS_T_HINFO = 13;       // Host information.
+    NS_T_MINFO = 14;       // Mailbox information.
+    NS_T_MX = 15;          // Mail routing information.
+    NS_T_TXT = 16;         // Text strings.
+    NS_T_RP = 17;          // Responsible person.
+    NS_T_AFSDB = 18;       // AFS cell database.
+    NS_T_X25 = 19;         // X_25 calling address.
+    NS_T_ISDN = 20;        // ISDN calling address.
+    NS_T_RT = 21;          // Router.
+    NS_T_NSAP = 22;        // NSAP address.
+    NS_T_NSAP_PTR = 23;    // Reverse NSAP lookup (deprecated).
+    NS_T_SIG = 24;         // Security signature.
+    NS_T_KEY = 25;         // Security key.
+    NS_T_PX = 26;          // X.400 mail mapping.
+    NS_T_GPOS = 27;        // Geographical position (withdrawn).
+    NS_T_AAAA = 28;        // IPv6 Address.
+    NS_T_LOC = 29;         // Location Information.
+    NS_T_NXT = 30;         // Next domain (security).
+    NS_T_EID = 31;         // Endpoint identifier.
+    NS_T_NIMLOC = 32;      // Nimrod Locator.
+    NS_T_SRV = 33;         // Server Selection.
+    NS_T_ATMA = 34;        // ATM Address
+    NS_T_NAPTR = 35;       // Naming Authority PoinTeR
+    NS_T_KX = 36;          // Key Exchange
+    NS_T_CERT = 37;        // Certification record
+    NS_T_A6 = 38;          // IPv6 address (experimental)
+    NS_T_DNAME = 39;       // Non-terminal DNAME
+    NS_T_SINK = 40;        // Kitchen sink (experimentatl)
+    NS_T_OPT = 41;         // EDNS0 option (meta-RR)
+    NS_T_APL = 42;         // Address prefix list (RFC 3123)
+    NS_T_DS = 43;          // Delegation Signer
+    NS_T_SSHFP = 44;       // SSH Fingerprint
+    NS_T_IPSECKEY = 45;    // IPSEC Key
+    NS_T_RRSIG = 46;       // RRset Signature
+    NS_T_NSEC = 47;        // Negative security
+    NS_T_DNSKEY = 48;      // DNS Key
+    NS_T_DHCID = 49;       // Dynamic host configuratin identifier
+    NS_T_NSEC3 = 50;       // Negative security type 3
+    NS_T_NSEC3PARAM = 51;  // Negative security type 3 parameters
+    NS_T_HIP = 55;         // Host Identity Protocol
+    NS_T_SPF = 99;         // Sender Policy Framework
+    NS_T_TKEY = 249;       // Transaction key
+    NS_T_TSIG = 250;       // Transaction signature.
+    NS_T_IXFR = 251;       // Incremental zone transfer.
+    NS_T_AXFR = 252;       // Transfer zone of authority.
+    NS_T_MAILB = 253;      // Transfer mailbox records.
+    NS_T_MAILA = 254;      // Transfer mail agent records.
+    NS_T_ANY = 255;        // Wildcard match.
+    NS_T_ZXFR = 256;       // BIND-specific, nonstandard.
+    NS_T_DLV = 32769;      // DNSSEC look-aside validatation.
+    NS_T_MAX = 65536;
+}
+
+enum IpVersion {
+    IV_UNKNOWN = 0;
+    IV_IPV4 = 1;
+    IV_IPV6 = 2;
+}
+
+enum Protocol {
+    PROTO_UNKNOWN = 0;
+    PROTO_UDP = 1;
+    PROTO_TCP = 2;
+    PROTO_DOT = 3;
+}
+
+enum PrivateDnsModes {
+    PDM_UNKNOWN = 0;
+    PDM_OFF = 1;
+    PDM_OPPORTUNISTIC = 2;
+    PDM_STRICT = 3;
+}
+
+enum NetworkType {
+    NT_UNKNOWN = 0;
+    // Indicates this network uses a Cellular transport.
+    NT_CELLULAR = 1;
+    // Indicates this network uses a Wi-Fi transport.
+    NT_WIFI = 2;
+    // Indicates this network uses a Bluetooth transport.
+    NT_BLUETOOTH = 3;
+    // Indicates this network uses an Ethernet transport.
+    NT_ETHERNET = 4;
+    // Indicates this network uses a VPN transport.
+    NT_VPN = 5;
+    // Indicates this network uses a Wi-Fi Aware transport.
+    NT_WIFI_AWARE = 6;
+    // Indicates this network uses a LoWPAN transport.
+    NT_LOWPAN = 7;
+}
+
+enum CacheStatus{
+    // the cache can't handle that kind of queries.
+    // or the answer buffer is too small.
+    CS_UNSUPPORTED = 0;
+    // the cache doesn't know about this query.
+    CS_NOTFOUND = 1;
+    // the cache found the answer.
+    CS_FOUND = 2;
+    // Don't do anything on cache.
+    CS_SKIP = 3;
+}
+
+message DnsQueryEvent {
+    optional android.stats.dnsresolver.NsRcode rcode = 1;
+
+    optional android.stats.dnsresolver.NsType type = 2;
+
+    optional android.stats.dnsresolver.CacheStatus cache_hit = 3;
+
+    optional android.stats.dnsresolver.IpVersion ip_version = 4;
+
+    optional android.stats.dnsresolver.Protocol protocol = 5;
+
+    // Number of DNS query retry times
+    optional int32 retry_times = 6;
+
+    // Ordinal number of name server.
+    optional int32 dns_server_index = 7;
+
+    // Used only by TCP and DOT. True for new connections.
+    optional bool connected = 8;
+
+    optional int32 latency_micros = 9;
+}
+
+message DnsQueryEvents {
+    repeated DnsQueryEvent dns_query_event = 1;
+}
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 4e60f8c..3402033 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -16,7 +16,7 @@
 
 android_app {
     name: "framework-res",
-    no_framework_libs: true,
+    sdk_version: "core_platform",
     certificate: "platform",
 
     // Soong special-cases framework-res to install this alongside
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ccf5199..990deaa 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1579,6 +1579,7 @@
          @hide -->
     <permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"
         android:protectionLevel="signature|privileged" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
 
     <!-- @SystemApi Allows an internal user to set signal strength in NetworkRequest. This kind of
          request will wake up device when signal strength meets the given value.
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 1720e8b..b203fa3 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1779,7 +1779,7 @@
     <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>
     <string name="toolbar_collapse_description" msgid="2821479483960330739">"Suzi"</string>
     <string name="zen_mode_feature_name" msgid="5254089399895895004">"Ne ometaj"</string>
-    <string name="zen_mode_downtime_feature_name" msgid="2626974636779860146">"Prestanak rada"</string>
+    <string name="zen_mode_downtime_feature_name" msgid="2626974636779860146">"Neaktivnost"</string>
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Radni dan uvečer"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Vikend"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Događaj"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index e5f208d..6ca2691 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -255,7 +255,7 @@
     <string name="notification_channel_vpn" msgid="8330103431055860618">"Estat de la VPN"</string>
     <string name="notification_channel_device_admin" msgid="1568154104368069249">"Administració del dispositiu"</string>
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alertes"</string>
-    <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demostració comercial"</string>
+    <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demostració per a botigues"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Connexió USB"</string>
     <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"S\'està executant una aplicació"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplicacions que consumeixen bateria"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index d9e15b2..bb85487 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -375,13 +375,13 @@
     <string name="permdesc_broadcastSticky" product="tv" msgid="6839285697565389467">"Umožňuje aplikaci odesílat trvalá vysílání, která přetrvávají i po skončení vysílání. Nadměrné používání může televizi zpomalit či způsobit její nestabilitu, protože bude používat příliš mnoho paměti."</string>
     <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"Umožňuje aplikaci odesílat trvalá vysílání, která přetrvávají i po skončení vysílání. Nadměrné používání může telefon zpomalit či způsobit jeho nestabilitu, protože bude používat příliš mnoho paměti."</string>
     <string name="permlab_readContacts" msgid="8348481131899886131">"čtení kontaktů"</string>
-    <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"Umožňuje aplikaci číst údaje o kontaktech uložených v tabletu, včetně toho, jak často voláte, posíláte e-maily nebo jinak komunikujete s konkrétními osobami. Toto oprávnění umožňuje aplikacím ukládat údaje o kontaktech. Škodlivé aplikace mohou tyto údaje bez vašeho vědomí sdílet."</string>
-    <string name="permdesc_readContacts" product="tv" msgid="1839238344654834087">"Umožňuje aplikaci číst údaje o kontaktech uložených v televizi včetně toho, jak často voláte, posíláte e-maily nebo jinými způsoby komunikujete s konkrétními kontakty. Toto oprávnění umožňuje aplikacím ukládat údaje o vašich kontaktech a škodlivé aplikace mohou sdílet údaje o kontaktech bez vašeho vědomí."</string>
-    <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"Umožňuje aplikaci číst údaje o kontaktech uložených v telefonu, včetně toho, jak často voláte, posíláte e-maily nebo komunikujete jinými způsoby s konkrétními osobami. Toto oprávnění umožňuje aplikacím ukládat údaje o kontaktech. Škodlivé aplikace mohou tyto údaje bez vašeho vědomí sdílet."</string>
+    <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"Umožňuje aplikaci číst údaje o kontaktech uložených v tabletu, včetně toho, jak často voláte, posíláte e‑maily nebo jinak komunikujete s konkrétními osobami. Toto oprávnění umožňuje aplikacím ukládat údaje o kontaktech. Škodlivé aplikace mohou tyto údaje bez vašeho vědomí sdílet."</string>
+    <string name="permdesc_readContacts" product="tv" msgid="1839238344654834087">"Umožňuje aplikaci číst údaje o kontaktech uložených v televizi včetně toho, jak často voláte, posíláte e‑maily nebo jinými způsoby komunikujete s konkrétními kontakty. Toto oprávnění umožňuje aplikacím ukládat údaje o vašich kontaktech a škodlivé aplikace mohou sdílet údaje o kontaktech bez vašeho vědomí."</string>
+    <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"Umožňuje aplikaci číst údaje o kontaktech uložených v telefonu, včetně toho, jak často voláte, posíláte e‑maily nebo komunikujete jinými způsoby s konkrétními osobami. Toto oprávnění umožňuje aplikacím ukládat údaje o kontaktech. Škodlivé aplikace mohou tyto údaje bez vašeho vědomí sdílet."</string>
     <string name="permlab_writeContacts" msgid="5107492086416793544">"úprava kontaktů"</string>
-    <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"Umožňuje aplikaci upravit údaje o kontaktech uložených v tabletu včetně toho, jak často voláte, posíláte e-maily nebo komunikujete jinými způsoby s konkrétními kontakty. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
-    <string name="permdesc_writeContacts" product="tv" msgid="5438230957000018959">"Umožňuje aplikaci upravit údaje o kontaktech uložených v televizi včetně toho, jak často voláte, posíláte e-maily nebo jinými způsoby komunikujete s konkrétními kontakty. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
-    <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"Umožňuje aplikaci upravit údaje o kontaktech uložených v telefonu včetně toho, jak často voláte, posíláte e-maily nebo komunikujete jinými způsoby s konkrétními kontakty. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
+    <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"Umožňuje aplikaci upravit údaje o kontaktech uložených v tabletu včetně toho, jak často voláte, posíláte e‑maily nebo komunikujete jinými způsoby s konkrétními kontakty. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
+    <string name="permdesc_writeContacts" product="tv" msgid="5438230957000018959">"Umožňuje aplikaci upravit údaje o kontaktech uložených v televizi včetně toho, jak často voláte, posíláte e‑maily nebo jinými způsoby komunikujete s konkrétními kontakty. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
+    <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"Umožňuje aplikaci upravit údaje o kontaktech uložených v telefonu včetně toho, jak často voláte, posíláte e‑maily nebo komunikujete jinými způsoby s konkrétními kontakty. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"čtení seznamu hovorů"</string>
     <string name="permdesc_readCallLog" msgid="3204122446463552146">"Tato aplikace může číst historii volání."</string>
     <string name="permlab_writeCallLog" msgid="8552045664743499354">"zápis do seznamu hovorů"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index ab95af6f5..6541655 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -63,7 +63,7 @@
     <string name="CfMmi" msgid="5123218989141573515">"Rufweiterleitung"</string>
     <string name="CwMmi" msgid="9129678056795016867">"Anklopfen"</string>
     <string name="BaMmi" msgid="455193067926770581">"Anrufsperre"</string>
-    <string name="PwdMmi" msgid="7043715687905254199">"Passwort-Änderung"</string>
+    <string name="PwdMmi" msgid="7043715687905254199">"Passwortänderung"</string>
     <string name="PinMmi" msgid="3113117780361190304">"PIN-Änderung"</string>
     <string name="CnipMmi" msgid="3110534680557857162">"Rufnummer vorhanden"</string>
     <string name="CnirMmi" msgid="3062102121430548731">"Rufnummer begrenzt"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 0893201..6219bb9 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -134,7 +134,7 @@
     <string name="wifi_calling_off_summary" msgid="8720659586041656098">"Desactivado"</string>
     <string name="wfc_mode_wifi_preferred_summary" msgid="1994113411286935263">"Preferir Wi-Fi"</string>
     <string name="wfc_mode_cellular_preferred_summary" msgid="1988279625335345908">"Preferir datos móviles"</string>
-    <string name="wfc_mode_wifi_only_summary" msgid="2379919155237869320">"Solo conexión Wi-Fi"</string>
+    <string name="wfc_mode_wifi_only_summary" msgid="2379919155237869320">"Solo Wi-Fi"</string>
     <string name="cfTemplateNotForwarded" msgid="1683685883841272560">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string>
     <string name="cfTemplateForwarded" msgid="1302922117498590521">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="9206251736527085256">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> transcurridos <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 0bf4802..d1f6cb5 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1195,7 +1195,7 @@
     <string name="sms_short_code_confirm_deny" msgid="2927389840209170706">"Utzi"</string>
     <string name="sms_short_code_remember_choice" msgid="5289538592272218136">"Gogoratu aukera"</string>
     <string name="sms_short_code_remember_undo_instruction" msgid="4960944133052287484">"Hori geroago alda dezakezu Ezarpenak &gt; Aplikazioak atalean"</string>
-    <string name="sms_short_code_confirm_always_allow" msgid="3241181154869493368">"Onartu beti"</string>
+    <string name="sms_short_code_confirm_always_allow" msgid="3241181154869493368">"Eman baimena beti"</string>
     <string name="sms_short_code_confirm_never_allow" msgid="446992765774269673">"Ez onartu inoiz"</string>
     <string name="sim_removed_title" msgid="6227712319223226185">"SIM txartela kendu da"</string>
     <string name="sim_removed_message" msgid="2333164559970958645">"Sare mugikorra ez da erabilgarri egongo baliozko SIM txartel bat sartuta berrabiarazten ez duzun arte."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index f840922..223d8b5 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -266,7 +266,7 @@
     <string name="safeMode" msgid="2788228061547930246">"Mode sécurisé"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Système Android"</string>
     <string name="user_owner_label" msgid="8836124313744349203">"Passer au profil personnel"</string>
-    <string name="managed_profile_label" msgid="8947929265267690522">"Passer au profil professionnel"</string>
+    <string name="managed_profile_label" msgid="8947929265267690522">"Passer au profil pro"</string>
     <string name="permgrouplab_contacts" msgid="3657758145679177612">"Contacts"</string>
     <string name="permgroupdesc_contacts" msgid="6951499528303668046">"accéder à vos contacts"</string>
     <string name="permgrouprequest_contacts" msgid="1601591667800538208">"Permettre à &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; d\'accéder à vos contacts"</string>
diff --git a/core/res/res/values-mcc450-mnc08/config.xml b/core/res/res/values-mcc450-mnc08/config.xml
index ca26ec1..5edbaed 100644
--- a/core/res/res/values-mcc450-mnc08/config.xml
+++ b/core/res/res/values-mcc450-mnc08/config.xml
@@ -28,4 +28,14 @@
     <!-- Do not set the system language as value of EF LI/EF PL -->
     <bool name="config_use_sim_language_file">false</bool>
 
+    <!-- Configures encoding type to parse the User Data of an SMS for reserved TP-DCS value.
+         Refer to SmsConstants.java
+         ENCODING_UNKNOWN = 0;
+         ENCODING_7BIT = 1;
+         ENCODING_8BIT = 2;
+         ENCODING_16BIT = 3;
+         ENCODING_KSC5601 = 4;
+         -->
+    <integer name="default_reserved_data_coding_scheme">4</integer>
+
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8a2648c..3c62bda 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -353,7 +353,7 @@
          overridden by the device to present the capability of creating socket keepalives. -->
     <!-- An Array of "[NetworkCapabilities.TRANSPORT_*],[supported keepalives] -->
     <string-array translatable="false" name="config_networkSupportedKeepaliveCount">
-        <item>0,3</item>
+        <item>0,1</item>
         <item>1,3</item>
     </string-array>
 
@@ -1172,6 +1172,9 @@
 
     <bool name="config_use_strict_phone_number_comparation">false</bool>
 
+    <!-- The character count of the minimum match for comparison phone numbers -->
+    <integer name="config_phonenumber_compare_min_match">7</integer>
+
     <!-- Display low battery warning when battery level dips to this value.
          Also, the battery stats are flushed to disk when we hit this level.  -->
     <integer name="config_criticalBatteryWarningLevel">5</integer>
@@ -2806,6 +2809,14 @@
          empty string is passed in -->
     <string name="config_wlan_data_service_package" translatable="false"></string>
 
+    <!-- Cellular data service class name to bind to by default. If none is specified in an overlay, an
+         empty string is passed in -->
+    <string name="config_wwan_data_service_class" translatable="false"></string>
+
+    <!-- IWLAN data service class name to bind to by default. If none is specified in an overlay, an
+         empty string is passed in -->
+    <string name="config_wlan_data_service_class" translatable="false"></string>
+
     <bool name="config_networkSamplingWakesDevice">true</bool>
 
     <!--From SmsMessage-->
@@ -2813,6 +2824,16 @@
         string that's stored in 8-bit unpacked format) characters.-->
     <bool translatable="false" name="config_sms_decode_gsm_8bit_data">false</bool>
 
+    <!-- Configures encoding type to parse the User Data of an SMS for reserved TP-DCS value.
+         Refer to SmsConstants.java
+         ENCODING_UNKNOWN = 0;
+         ENCODING_7BIT = 1;
+         ENCODING_8BIT = 2;
+         ENCODING_16BIT = 3;
+         ENCODING_KSC5601 = 4;
+         -->
+    <integer name="default_reserved_data_coding_scheme">2</integer>
+
     <!-- If EMS is not supported, framework breaks down EMS into single segment SMS
          and adds page info " x/y". This config is used to set which carrier doesn't
          support EMS and whether page info should be added at the beginning or the end.
@@ -3339,8 +3360,10 @@
     <!-- Flag indicates that whether non-system apps can be installed on internal storage. -->
     <bool name="config_allow3rdPartyAppOnInternal">true</bool>
 
-    <!-- Package name of the default cell broadcast receiver -->
-    <string name="config_defaultCellBroadcastReceiverPkg" translatable="false">com.android.cellbroadcastreceiver</string>
+    <!-- Package names of the default cell broadcast receivers -->
+    <string-array name="config_defaultCellBroadcastReceiverPkgs" translatable="false">
+        <item>com.android.cellbroadcastreceiver</item>
+    </string-array>
 
     <!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. -->
     <string name="config_icon_mask" translatable="false">"M50,0L92,0C96.42,0 100,4.58 100 8L100,92C100, 96.42 96.42 100 92 100L8 100C4.58, 100 0 96.42 0 92L0 8 C 0 4.42 4.42 0 8 0L50 0Z"</string>
@@ -3543,13 +3566,21 @@
     <!-- Cellular network service package name to bind to by default. -->
     <string name="config_wwan_network_service_package" translatable="false">com.android.phone</string>
 
+    <!-- Cellular network service class name to bind to by default.-->
+    <string name="config_wwan_network_service_class" translatable="false"></string>
+
     <!-- IWLAN network service package name to bind to by default. If none is specified in an overlay, an
          empty string is passed in -->
     <string name="config_wlan_network_service_package" translatable="false"></string>
 
+    <!-- IWLAN network service class name to bind to by default. If none is specified in an overlay, an
+         empty string is passed in -->
+    <string name="config_wlan_network_service_class" translatable="false"></string>
     <!-- Telephony qualified networks service package name to bind to by default. -->
     <string name="config_qualified_networks_service_package" translatable="false"></string>
 
+    <!-- Telephony qualified networks service class name to bind to by default. -->
+    <string name="config_qualified_networks_service_class" translatable="false"></string>
     <!-- Wear devices: Controls the radios affected by Activity Mode. -->
     <string-array name="config_wearActivityModeRadios">
         <item>"wifi"</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f8fc953..02154dd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -271,9 +271,14 @@
   <java-symbol type="bool" name="config_dynamic_bind_ims" />
   <java-symbol type="string" name="config_wwan_network_service_package" />
   <java-symbol type="string" name="config_wlan_network_service_package" />
+  <java-symbol type="string" name="config_wwan_network_service_class" />
+  <java-symbol type="string" name="config_wlan_network_service_class" />
   <java-symbol type="string" name="config_wwan_data_service_package" />
   <java-symbol type="string" name="config_wlan_data_service_package" />
+  <java-symbol type="string" name="config_wwan_data_service_class" />
+  <java-symbol type="string" name="config_wlan_data_service_class" />
   <java-symbol type="string" name="config_qualified_networks_service_package" />
+  <java-symbol type="string" name="config_qualified_networks_service_class" />
   <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
   <java-symbol type="bool" name="config_sip_wifi_only" />
@@ -285,6 +290,7 @@
   <java-symbol type="bool" name="config_ui_enableFadingMarquee" />
   <java-symbol type="bool" name="config_enableHapticTextHandle" />
   <java-symbol type="bool" name="config_use_strict_phone_number_comparation" />
+  <java-symbol type="integer" name="config_phonenumber_compare_min_match" />
   <java-symbol type="bool" name="config_single_volume" />
   <java-symbol type="bool" name="config_voice_capable" />
   <java-symbol type="bool" name="config_requireCallCapableAccountForHandle" />
@@ -2492,6 +2498,7 @@
   <java-symbol type="attr" name="ambientShadowAlpha" />
   <java-symbol type="attr" name="spotShadowAlpha" />
   <java-symbol type="bool" name="config_sms_decode_gsm_8bit_data" />
+  <java-symbol type="integer" name="default_reserved_data_coding_scheme" />
   <java-symbol type="dimen" name="text_size_small_material" />
   <java-symbol type="attr" name="checkMarkGravity" />
   <java-symbol type="layout" name="select_dialog_singlechoice_material" />
@@ -3030,7 +3037,7 @@
   <java-symbol type="drawable" name="lockscreen_selected" />
 
   <java-symbol type="string" name="notification_header_divider_symbol_with_spaces" />
-  <java-symbol type="string" name="config_defaultCellBroadcastReceiverPkg" />
+  <java-symbol type="array" name="config_defaultCellBroadcastReceiverPkgs" />
 
   <java-symbol type="color" name="notification_primary_text_color_light" />
   <java-symbol type="color" name="notification_primary_text_color_dark" />
diff --git a/core/tests/benchmarks/src/android/text/format/AndroidTimeVsOthersBenchmark.java b/core/tests/benchmarks/src/android/text/format/AndroidTimeVsOthersBenchmark.java
new file mode 100644
index 0000000..ea24400
--- /dev/null
+++ b/core/tests/benchmarks/src/android/text/format/AndroidTimeVsOthersBenchmark.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.format;
+
+import com.google.caliper.Benchmark;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+
+public class AndroidTimeVsOthersBenchmark {
+
+    private static final String[] TIMEZONE_IDS = {
+            "Europe/London",
+            "America/Los_Angeles",
+            "Asia/Shanghai",
+    };
+
+    @Benchmark
+    public void toMillis_androidTime(int reps) {
+        long answer = 0;
+        for (int i = 0; i < reps; i++) {
+            String timezoneId = TIMEZONE_IDS[i % TIMEZONE_IDS.length];
+            Time time = new Time(timezoneId);
+            time.set(1, 2, 3, 4, 5, 2010);
+            answer = time.toMillis(false);
+        }
+        // System.out.println(answer);
+    }
+
+    @Benchmark
+    public void toMillis_javaTime(int reps) {
+        long answer = 0;
+        for (int i = 0; i < reps; i++) {
+            String timezoneId = TIMEZONE_IDS[i % TIMEZONE_IDS.length];
+            LocalDateTime time = LocalDateTime.of(2010, 5 + 1, 4, 3, 2, 1);
+            ZoneOffset offset = ZoneId.of(timezoneId).getRules().getOffset(time);
+            answer = time.toInstant(offset).toEpochMilli();
+        }
+        // System.out.println(answer);
+    }
+
+    @Benchmark
+    public void toMillis_javaUtil(int reps) {
+        long answer = 0;
+        for (int i = 0; i < reps; i++) {
+            String timezoneId = TIMEZONE_IDS[i % TIMEZONE_IDS.length];
+            java.util.TimeZone timeZone = java.util.TimeZone.getTimeZone(timezoneId);
+            java.util.Calendar calendar = new java.util.GregorianCalendar(timeZone);
+            calendar.set(2010, 5, 4, 3, 2, 1);
+            calendar.set(java.util.Calendar.MILLISECOND, 0);
+            answer = calendar.getTimeInMillis();
+        }
+        // System.out.println(answer);
+    }
+
+    @Benchmark
+    public void toMillis_androidIucUtil(int reps) {
+        long answer = 0;
+        for (int i = 0; i < reps; i++) {
+            String timezoneId = TIMEZONE_IDS[i % TIMEZONE_IDS.length];
+            android.icu.util.TimeZone timeZone =
+                    android.icu.util.TimeZone.getTimeZone(timezoneId);
+            android.icu.util.Calendar calendar = new android.icu.util.GregorianCalendar(timeZone);
+            calendar.set(2010, 5, 4, 3, 2, 1);
+            calendar.set(android.icu.util.Calendar.MILLISECOND, 0);
+            answer = calendar.getTimeInMillis();
+        }
+        // System.out.println(answer);
+    }
+}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index a909487..2fa4487 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -55,6 +55,8 @@
 
     resource_dirs: ["res"],
     resource_zips: [":FrameworksCoreTests_apks_as_resources"],
+
+    data: [":BstatsTestApp"],
 }
 
 // Rules to copy all the test apks to the intermediate raw resource directory
diff --git a/core/tests/coretests/BstatsTestApp/Android.bp b/core/tests/coretests/BstatsTestApp/Android.bp
index 424c71a..a89d728 100644
--- a/core/tests/coretests/BstatsTestApp/Android.bp
+++ b/core/tests/coretests/BstatsTestApp/Android.bp
@@ -15,10 +15,6 @@
 android_test_helper_app {
     name: "BstatsTestApp",
 
-    test_suites: [
-        "device-tests",
-    ],
-
     static_libs: ["coretests-aidl"],
 
     srcs: ["**/*.java"],
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index d922c16..b9f5ef9 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -412,7 +412,8 @@
                 IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1,
                 boolean b2, boolean b3, Configuration configuration,
                 CompatibilityInfo compatibilityInfo, Map map, Bundle bundle1, String s1,
-                boolean autofillCompatEnabled) throws RemoteException {
+                boolean autofillCompatEnabled, long[] disableCompatChanges)
+                throws RemoteException {
         }
 
         @Override
diff --git a/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java b/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java
index e248a77..586b472 100644
--- a/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java
+++ b/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java
@@ -45,12 +45,12 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
-import libcore.testing.io.TestIoUtils;
-
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 
 @SmallTest
@@ -59,21 +59,14 @@
     private static final String APK_FILE_EXTENSION = ".apk";
     private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
 
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
     private File mTmpDir = null;
 
     @Before
-    public void setUp() {
-        mTmpDir = TestIoUtils.createTemporaryDirectory("DexMetadataHelperTest");
-    }
-
-    @After
-    public void tearDown() {
-        if (mTmpDir != null) {
-            File[] files = mTmpDir.listFiles();
-            for (File f : files) {
-                f.delete();
-            }
-        }
+    public void setUp() throws IOException {
+        mTmpDir = mTemporaryFolder.newFolder("DexMetadataHelperTest");
     }
 
     private File createDexMetadataFile(String apkFileName) throws IOException {
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelWakelockReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelWakelockReaderTest.java
index 4e4bb3507..dc9208d 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelWakelockReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelWakelockReaderTest.java
@@ -13,14 +13,17 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package com.android.internal.os;
 
-import android.support.test.filters.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import junit.framework.TestCase;
 
 import java.nio.charset.Charset;
 
+import android.system.suspend.WakeLockInfo;
+
 public class KernelWakelockReaderTest extends TestCase {
     /**
      * Helper class that builds the mock Kernel module file /d/wakeup_sources.
@@ -57,6 +60,34 @@
         }
     }
 
+    /**
+     * Helper method to create WakeLockInfo object.
+     * @param totalTime is time in microseconds.
+     * @return the created WakeLockInfo object.
+     */
+    private WakeLockInfo createWakeLockInfo(String name, int activeCount, long totalTime) {
+        WakeLockInfo info = new WakeLockInfo();
+        info.name = name;
+        info.activeCount = activeCount;
+        info.totalTime = totalTime;
+        return info;
+    }
+
+    /**
+     * Helper method for KernelWakeLockReader::readKernelWakelockStats(...)
+     * @param staleStats existing stats to update.
+     * @param buffer representation of mock kernel module file /d/wakeup_sources.
+     * @param wlStats mock WakeLockInfo list returned from ISuspendControlService.
+     * @return the updated stats.
+     */
+    private KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats,
+                                                        byte[] buffer, WakeLockInfo[] wlStats) {
+        mReader.updateVersion(staleStats);
+        mReader.parseProcWakelocks(buffer, buffer.length, true, staleStats);
+        mReader.updateWakelockStats(wlStats, staleStats);
+        return mReader.removeOldStats(staleStats);
+    }
+
     private KernelWakelockReader mReader;
 
     @Override
@@ -65,18 +96,22 @@
         mReader = new KernelWakelockReader();
     }
 
+// ------------------------- Legacy Wakelock Stats Test ------------------------
     @SmallTest
     public void testParseEmptyFile() throws Exception {
         KernelWakelockStats staleStats = mReader.parseProcWakelocks(new byte[0], 0, true,
                 new KernelWakelockStats());
+
         assertTrue(staleStats.isEmpty());
     }
 
     @SmallTest
     public void testOnlyHeader() throws Exception {
         byte[] buffer = new ProcFileBuilder().getBytes();
+
         KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true,
                 new KernelWakelockStats());
+
         assertTrue(staleStats.isEmpty());
     }
 
@@ -85,9 +120,12 @@
         byte[] buffer = new ProcFileBuilder()
                 .addLine("Wakelock", 34, 123) // Milliseconds
                 .getBytes();
+
         KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true,
                 new KernelWakelockStats());
+
         assertEquals(1, staleStats.size());
+
         assertTrue(staleStats.containsKey("Wakelock"));
 
         KernelWakelockStats.Entry entry = staleStats.get("Wakelock");
@@ -101,9 +139,12 @@
                 .addLine("Wakelock", 1, 10)
                 .addLine("Fakelock", 2, 20)
                 .getBytes();
+
         KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true,
                 new KernelWakelockStats());
+
         assertEquals(2, staleStats.size());
+
         assertTrue(staleStats.containsKey("Wakelock"));
         assertTrue(staleStats.containsKey("Fakelock"));
     }
@@ -114,8 +155,10 @@
                 .addLine("Wakelock", 1, 10) // Milliseconds
                 .addLine("Wakelock", 1, 10) // Milliseconds
                 .getBytes();
+
         KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true,
                 new KernelWakelockStats());
+
         assertEquals(1, staleStats.size());
         assertTrue(staleStats.containsKey("Wakelock"));
 
@@ -126,12 +169,14 @@
 
     @SmallTest
     public void testWakelocksBecomeStale() throws Exception {
+        KernelWakelockStats staleStats = new KernelWakelockStats();
+
         byte[] buffer = new ProcFileBuilder()
                 .addLine("Fakelock", 3, 30)
                 .getBytes();
-        KernelWakelockStats staleStats = new KernelWakelockStats();
 
-        staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true, staleStats);
+        readKernelWakelockStats(staleStats, buffer, new WakeLockInfo[0]);
+
         assertEquals(1, staleStats.size());
         assertTrue(staleStats.containsKey("Fakelock"));
 
@@ -139,9 +184,228 @@
                 .addLine("Wakelock", 1, 10)
                 .getBytes();
 
-        staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true, staleStats);
+        readKernelWakelockStats(staleStats, buffer, new WakeLockInfo[0]);
+
         assertEquals(1, staleStats.size());
         assertTrue(staleStats.containsKey("Wakelock"));
         assertFalse(staleStats.containsKey("Fakelock"));
     }
+
+// -------------------- SystemSuspend Wakelock Stats Test -------------------
+    @SmallTest
+    public void testEmptyWakeLockInfoList() {
+        KernelWakelockStats staleStats = mReader.updateWakelockStats(new WakeLockInfo[0],
+                new KernelWakelockStats());
+
+        assertTrue(staleStats.isEmpty());
+    }
+
+    @SmallTest
+    public void testOneWakeLockInfo() {
+        WakeLockInfo[] wlStats = new WakeLockInfo[1];
+        wlStats[0] = createWakeLockInfo("WakeLock", 20, 1000);   // Milliseconds
+
+        KernelWakelockStats staleStats = mReader.updateWakelockStats(wlStats,
+                new KernelWakelockStats());
+
+        assertEquals(1, staleStats.size());
+
+        assertTrue(staleStats.containsKey("WakeLock"));
+
+        KernelWakelockStats.Entry entry = staleStats.get("WakeLock");
+        assertEquals(20, entry.mCount);
+        assertEquals(1000 * 1000, entry.mTotalTime);   // Microseconds
+    }
+
+    @SmallTest
+    public void testTwoWakeLockInfos() {
+        WakeLockInfo[] wlStats = new WakeLockInfo[2];
+        wlStats[0] = createWakeLockInfo("WakeLock1", 10, 1000); // Milliseconds
+        wlStats[1] = createWakeLockInfo("WakeLock2", 20, 2000); // Milliseconds
+
+        KernelWakelockStats staleStats = mReader.updateWakelockStats(wlStats,
+                new KernelWakelockStats());
+
+        assertEquals(2, staleStats.size());
+
+        assertTrue(staleStats.containsKey("WakeLock1"));
+        assertTrue(staleStats.containsKey("WakeLock2"));
+
+        KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1");
+        assertEquals(10, entry1.mCount);
+        assertEquals(1000 * 1000, entry1.mTotalTime); // Microseconds
+
+        KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2");
+        assertEquals(20, entry2.mCount);
+        assertEquals(2000 * 1000, entry2.mTotalTime); // Microseconds
+    }
+
+    @SmallTest
+    public void testWakeLockInfosBecomeStale() {
+        WakeLockInfo[] wlStats = new WakeLockInfo[1];
+        wlStats[0] = createWakeLockInfo("WakeLock1", 10, 1000); // Milliseconds
+
+        KernelWakelockStats staleStats = new KernelWakelockStats();
+
+        readKernelWakelockStats(staleStats, new byte[0], wlStats);
+
+        assertEquals(1, staleStats.size());
+
+        assertTrue(staleStats.containsKey("WakeLock1"));
+        KernelWakelockStats.Entry entry = staleStats.get("WakeLock1");
+        assertEquals(10, entry.mCount);
+        assertEquals(1000 * 1000, entry.mTotalTime);  // Microseconds
+
+        wlStats[0] = createWakeLockInfo("WakeLock2", 20, 2000); // Milliseconds
+
+        readKernelWakelockStats(staleStats, new byte[0], wlStats);
+
+        assertEquals(1, staleStats.size());
+
+        assertFalse(staleStats.containsKey("WakeLock1"));
+        assertTrue(staleStats.containsKey("WakeLock2"));
+        entry = staleStats.get("WakeLock2");
+        assertEquals(20, entry.mCount);
+        assertEquals(2000 * 1000, entry.mTotalTime); // Micro seconds
+    }
+
+// -------------------- Aggregate  Wakelock Stats Tests --------------------
+    @SmallTest
+    public void testAggregateStatsEmpty() throws Exception {
+        KernelWakelockStats staleStats = new KernelWakelockStats();
+
+        byte[] buffer = new byte[0];
+        WakeLockInfo[] wlStats = new WakeLockInfo[0];
+
+        readKernelWakelockStats(staleStats, buffer, wlStats);
+
+        assertTrue(staleStats.isEmpty());
+    }
+
+    @SmallTest
+    public void testAggregateStatsNoNativeWakelocks() throws Exception {
+        KernelWakelockStats staleStats = new KernelWakelockStats();
+
+        byte[] buffer = new ProcFileBuilder()
+                .addLine("Wakelock", 34, 123) // Milliseconds
+                .getBytes();
+        WakeLockInfo[] wlStats = new WakeLockInfo[0];
+
+        readKernelWakelockStats(staleStats, buffer, wlStats);
+
+        assertEquals(1, staleStats.size());
+
+        assertTrue(staleStats.containsKey("Wakelock"));
+
+        KernelWakelockStats.Entry entry = staleStats.get("Wakelock");
+        assertEquals(34, entry.mCount);
+        assertEquals(1000 * 123, entry.mTotalTime);  // Microseconds
+    }
+
+    @SmallTest
+    public void testAggregateStatsNoKernelWakelocks() throws Exception {
+        KernelWakelockStats staleStats = new KernelWakelockStats();
+
+        byte[] buffer = new byte[0];
+        WakeLockInfo[] wlStats = new WakeLockInfo[1];
+        wlStats[0] = createWakeLockInfo("WakeLock", 10, 1000);  // Milliseconds
+
+        readKernelWakelockStats(staleStats, buffer, wlStats);
+
+        assertEquals(1, staleStats.size());
+
+        assertTrue(staleStats.containsKey("WakeLock"));
+
+        KernelWakelockStats.Entry entry = staleStats.get("WakeLock");
+        assertEquals(10, entry.mCount);
+        assertEquals(1000 * 1000, entry.mTotalTime);  // Microseconds
+    }
+
+    @SmallTest
+    public void testAggregateStatsBothKernelAndNativeWakelocks() throws Exception {
+        KernelWakelockStats staleStats = new KernelWakelockStats();
+
+        byte[] buffer = new ProcFileBuilder()
+                .addLine("WakeLock1", 34, 123)  // Milliseconds
+                .getBytes();
+        WakeLockInfo[] wlStats = new WakeLockInfo[1];
+        wlStats[0] = createWakeLockInfo("WakeLock2", 10, 1000); // Milliseconds
+
+        readKernelWakelockStats(staleStats, buffer, wlStats);
+
+        assertEquals(2, staleStats.size());
+
+        assertTrue(staleStats.containsKey("WakeLock1"));
+        KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1");
+        assertEquals(34, entry1.mCount);
+        assertEquals(123 * 1000, entry1.mTotalTime);  // Microseconds
+
+        assertTrue(staleStats.containsKey("WakeLock2"));
+        KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2");
+        assertEquals(10, entry2.mCount);
+        assertEquals(1000 * 1000, entry2.mTotalTime);  // Microseconds
+    }
+
+    @SmallTest
+    public void testAggregateStatsUpdate() throws Exception {
+        KernelWakelockStats staleStats = new KernelWakelockStats();
+
+        byte[] buffer = new ProcFileBuilder()
+                .addLine("WakeLock1", 34, 123)  // Milliseconds
+                .addLine("WakeLock2", 46, 345)  // Milliseconds
+                .getBytes();
+        WakeLockInfo[] wlStats = new WakeLockInfo[2];
+        wlStats[0] = createWakeLockInfo("WakeLock3", 10, 1000); // Milliseconds
+        wlStats[1] = createWakeLockInfo("WakeLock4", 20, 2000); // Milliseconds
+
+        readKernelWakelockStats(staleStats, buffer, wlStats);
+
+        assertEquals(4, staleStats.size());
+
+        assertTrue(staleStats.containsKey("WakeLock1"));
+        assertTrue(staleStats.containsKey("WakeLock2"));
+        assertTrue(staleStats.containsKey("WakeLock3"));
+        assertTrue(staleStats.containsKey("WakeLock4"));
+
+        KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1");
+        assertEquals(34, entry1.mCount);
+        assertEquals(123 * 1000, entry1.mTotalTime); // Microseconds
+
+        KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2");
+        assertEquals(46, entry2.mCount);
+        assertEquals(345 * 1000, entry2.mTotalTime); // Microseconds
+
+        KernelWakelockStats.Entry entry3 = staleStats.get("WakeLock3");
+        assertEquals(10, entry3.mCount);
+        assertEquals(1000 * 1000, entry3.mTotalTime); // Microseconds
+
+        KernelWakelockStats.Entry entry4 = staleStats.get("WakeLock4");
+        assertEquals(20, entry4.mCount);
+        assertEquals(2000 * 1000, entry4.mTotalTime); // Microseconds
+
+        buffer = new ProcFileBuilder()
+                .addLine("WakeLock1", 45, 789)  // Milliseconds
+                .addLine("WakeLock1", 56, 123)  // Milliseconds
+                .getBytes();
+        wlStats = new WakeLockInfo[1];
+        wlStats[0] = createWakeLockInfo("WakeLock4", 40, 4000); // Milliseconds
+
+        readKernelWakelockStats(staleStats, buffer, wlStats);
+
+        assertEquals(2, staleStats.size());
+
+        assertTrue(staleStats.containsKey("WakeLock1"));
+        assertTrue(staleStats.containsKey("WakeLock4"));
+
+        assertFalse(staleStats.containsKey("WakeLock2"));
+        assertFalse(staleStats.containsKey("WakeLock3"));
+
+        entry1 = staleStats.get("WakeLock1");
+        assertEquals(45 + 56, entry1.mCount);
+        assertEquals((789 + 123) * 1000, entry1.mTotalTime);  // Microseconds
+
+        entry2 = staleStats.get("WakeLock4");
+        assertEquals(40, entry2.mCount);
+        assertEquals(4000 * 1000, entry4.mTotalTime); // Microseconds
+    }
 }
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index afe7913..d6ffa8a 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -147,6 +147,9 @@
     <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="media" />
     <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
     <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" />
+    <assign-permission name="android.permission.INTERNET" uid="media" />
+
+    <assign-permission name="android.permission.INTERNET" uid="shell" />
 
     <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="audioserver" />
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="audioserver" />
diff --git a/data/keyboards/Vendor_045e_Product_02d1.kl b/data/keyboards/Vendor_045e_Product_02d1.kl
index 5d1637b..0867670 100644
--- a/data/keyboards/Vendor_045e_Product_02d1.kl
+++ b/data/keyboards/Vendor_045e_Product_02d1.kl
@@ -13,20 +13,22 @@
 # limitations under the License.
 
 #
-# XBox One USB Controller
+# XBox One Controller - Model 1537 - USB
 #
 
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
 key 304   BUTTON_A
 key 305   BUTTON_B
 key 307   BUTTON_X
 key 308   BUTTON_Y
+
 key 310   BUTTON_L1
 key 311   BUTTON_R1
-key 314   BACK
-key 315   BUTTON_START
-key 316   HOME
-key 317   BUTTON_THUMBL
-key 318   BUTTON_THUMBR
+
+# Triggers.
+axis 0x02 LTRIGGER
+axis 0x05 RTRIGGER
 
 # Left and right stick.
 # The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd.
@@ -37,10 +39,19 @@
 axis 0x03 Z flat 4096
 axis 0x04 RZ flat 4096
 
-# Triggers.
-axis 0x02 LTRIGGER
-axis 0x05 RTRIGGER
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
 
 # Hat.
 axis 0x10 HAT_X
 axis 0x11 HAT_Y
+
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Two overlapping rectangles
+key 314   BUTTON_SELECT
+# Hamburger - 3 parallel lines
+key 315   BUTTON_START
+
+# Xbox key
+key 316   BUTTON_MODE
diff --git a/data/keyboards/Vendor_045e_Product_02e3.kl b/data/keyboards/Vendor_045e_Product_02e3.kl
new file mode 100644
index 0000000..0a6e7d7
--- /dev/null
+++ b/data/keyboards/Vendor_045e_Product_02e3.kl
@@ -0,0 +1,56 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Microsoft X-Box One Elite Pad - Model 1698 - USB
+#
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+key 0x130   BUTTON_A
+key 0x131   BUTTON_B
+key 0x133   BUTTON_X
+key 0x134   BUTTON_Y
+
+key 0x136   BUTTON_L1
+key 0x137   BUTTON_R1
+
+# Triggers.
+axis 0x02 LTRIGGER
+axis 0x05 RTRIGGER
+
+# Left stick
+axis 0x00 X
+axis 0x01 Y
+# Right stick
+axis 0x03 Z
+axis 0x04 RZ
+
+key 0x13d   BUTTON_THUMBL
+key 0x13e   BUTTON_THUMBR
+
+# Hat.
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+
+# Two overlapping rectangles
+key 0x13a   BUTTON_SELECT
+# Hamburger - 3 parallel lines
+key 0x13b   BUTTON_START
+
+# Xbox key
+key 0x13c   BUTTON_MODE
diff --git a/data/keyboards/Vendor_045e_Product_02ea.kl b/data/keyboards/Vendor_045e_Product_02ea.kl
new file mode 100644
index 0000000..3b46db2
--- /dev/null
+++ b/data/keyboards/Vendor_045e_Product_02ea.kl
@@ -0,0 +1,57 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# XBox One Controller - Model 1708 - USB
+#
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+key 304   BUTTON_A
+key 305   BUTTON_B
+key 307   BUTTON_X
+key 308   BUTTON_Y
+
+key 310   BUTTON_L1
+key 311   BUTTON_R1
+
+# Triggers.
+axis 0x02 LTRIGGER
+axis 0x05 RTRIGGER
+
+# Left and right stick.
+# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd.
+# This confuses applications that rely on the flat value because the joystick actually
+# settles in a flat range of +/- 4096 or so.
+axis 0x00 X flat 4096
+axis 0x01 Y flat 4096
+axis 0x03 Z flat 4096
+axis 0x04 RZ flat 4096
+
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
+
+# Hat.
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Two overlapping rectangles
+key 314   BUTTON_SELECT
+# Hamburger - 3 parallel lines
+key 315   BUTTON_START
+
+# Xbox key
+key 316   BUTTON_MODE
diff --git a/data/keyboards/Vendor_045e_Product_02fd.kl b/data/keyboards/Vendor_045e_Product_02fd.kl
new file mode 100644
index 0000000..1b03497
--- /dev/null
+++ b/data/keyboards/Vendor_045e_Product_02fd.kl
@@ -0,0 +1,62 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# XBox One Controller - Model 1708 - Bluetooth
+#
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+key 304   BUTTON_A
+key 305   BUTTON_B
+key 307   BUTTON_X
+key 308   BUTTON_Y
+
+key 310   BUTTON_L1
+key 311   BUTTON_R1
+
+# Triggers.
+axis 0x0a LTRIGGER
+axis 0x09 RTRIGGER
+
+# Left and right stick.
+# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd.
+# This confuses applications that rely on the flat value because the joystick actually
+# settles in a flat range of +/- 4096 or so.
+axis 0x00 X flat 4096
+axis 0x01 Y flat 4096
+axis 0x02 Z flat 4096
+axis 0x05 RZ flat 4096
+
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
+
+# Hat.
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Two overlapping rectangles
+key 158   BUTTON_SELECT
+# Hamburger - 3 parallel lines
+key 315   BUTTON_START
+
+# There are at least two versions of firmware out for this controller.
+# They send different linux keys for the "Xbox" button.
+# Xbox key (original firmware)
+key 172   BUTTON_MODE
+
+# Xbox key (newer firmware)
+key 316   BUTTON_MODE
diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java
index 7408683..644f7bc 100644
--- a/graphics/java/android/graphics/GraphicBuffer.java
+++ b/graphics/java/android/graphics/GraphicBuffer.java
@@ -53,6 +53,7 @@
     private final int mHeight;
     private final int mFormat;
     private final int mUsage;
+    private final boolean mCapturedSecureLayers;
     // Note: do not rename, this field is used by native code
     @UnsupportedAppUsage
     private final long mNativeObject;
@@ -87,12 +88,22 @@
      * Private use only. See {@link #create(int, int, int, int)}.
      */
     @UnsupportedAppUsage
-    private GraphicBuffer(int width, int height, int format, int usage, long nativeObject) {
+    private GraphicBuffer(int width, int height, int format, int usage, long nativeObject,
+            boolean capturedSecureLayers) {
         mWidth = width;
         mHeight = height;
         mFormat = format;
         mUsage = usage;
         mNativeObject = nativeObject;
+        mCapturedSecureLayers = capturedSecureLayers;
+    }
+
+    /**
+     * Private use only. See {@link #create(int, int, int, int)}.
+     */
+    @UnsupportedAppUsage
+    private GraphicBuffer(int width, int height, int format, int usage, long nativeObject) {
+        this(width, height, format, usage, nativeObject, false);
     }
 
     /**
@@ -101,15 +112,34 @@
      */
     @UnsupportedAppUsage
     public static GraphicBuffer createFromExisting(int width, int height,
-            int format, int usage, long unwrappedNativeObject) {
+            int format, int usage, long unwrappedNativeObject,
+            boolean capturedSecureLayers) {
         long nativeObject = nWrapGraphicBuffer(unwrappedNativeObject);
         if (nativeObject != 0) {
-            return new GraphicBuffer(width, height, format, usage, nativeObject);
+            return new GraphicBuffer(width, height, format, usage, nativeObject,
+                                     capturedSecureLayers);
         }
         return null;
     }
 
     /**
+     * For SurfaceControl JNI. Provides and ignored value for capturedSecureLayers for backwards
+     * compatibility
+     * @hide
+     */
+    public static GraphicBuffer createFromExisting(int width, int height,
+            int format, int usage, long unwrappedNativeObject) {
+        return createFromExisting(width, height, format, usage, unwrappedNativeObject, false);
+    }
+
+    /**
+      * Returns true if the buffer contains visible secure layers.
+      */
+    public boolean doesContainSecureLayers() {
+        return mCapturedSecureLayers;
+    }
+
+    /**
      * Returns the width of this buffer in pixels.
      */
     public int getWidth() {
diff --git a/graphics/proto/Android.bp b/graphics/proto/Android.bp
index 1d06348..ddced59 100644
--- a/graphics/proto/Android.bp
+++ b/graphics/proto/Android.bp
@@ -5,7 +5,6 @@
         type: "lite",
     },
     srcs: ["game_driver.proto"],
-    no_framework_libs: true,
     jarjar_rules: "jarjar-rules.txt",
     sdk_version: "28",
 }
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 7ba7828..5a8579a 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -211,7 +211,7 @@
     return false;
   }
 
-  ::ZipString name;
+  std::string name;
   ::ZipEntry entry;
 
   // We need to hold back directories because many paths will contain them and we want to only
@@ -220,7 +220,7 @@
 
   int32_t result;
   while ((result = ::Next(cookie, &entry, &name)) == 0) {
-    StringPiece full_file_path(reinterpret_cast<const char*>(name.name), name.name_length);
+    StringPiece full_file_path(name);
     StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
 
     if (!leaf_file_path.empty()) {
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 861dc0f..b79ffa5 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -7344,12 +7344,12 @@
         print_complex(value.data, true);
         printf("\n");
     } else if (value.dataType >= Res_value::TYPE_FIRST_COLOR_INT
-            || value.dataType <= Res_value::TYPE_LAST_COLOR_INT) {
+            && value.dataType <= Res_value::TYPE_LAST_COLOR_INT) {
         printf("(color) #%08x\n", value.data);
     } else if (value.dataType == Res_value::TYPE_INT_BOOLEAN) {
         printf("(boolean) %s\n", value.data ? "true" : "false");
     } else if (value.dataType >= Res_value::TYPE_FIRST_INT
-            || value.dataType <= Res_value::TYPE_LAST_INT) {
+            && value.dataType <= Res_value::TYPE_LAST_INT) {
         printf("(int) 0x%08x or %d\n", value.data, value.data);
     } else {
         printf("(unknown type) t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)\n",
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index ee5f778..e77ac3d 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -39,7 +39,7 @@
 class _ZipEntryRO {
 public:
     ZipEntry entry;
-    ZipString name;
+    std::string_view name;
     void *cookie;
 
     _ZipEntryRO() : cookie(NULL) {}
@@ -96,7 +96,7 @@
 {
     _ZipEntryRO* data = new _ZipEntryRO;
 
-    data->name = ZipString(entryName);
+    data->name = entryName;
 
     const int32_t error = FindEntry(mHandle, entryName, &(data->entry));
     if (error) {
@@ -194,14 +194,14 @@
     const
 {
     const _ZipEntryRO* zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
-    const uint16_t requiredSize = zipEntry->name.name_length + 1;
+    const uint16_t requiredSize = zipEntry->name.length() + 1;
 
     if (bufLen < requiredSize) {
         ALOGW("Buffer too short, requires %d bytes for entry name", requiredSize);
         return requiredSize;
     }
 
-    memcpy(buffer, zipEntry->name.name, requiredSize - 1);
+    memcpy(buffer, zipEntry->name.data(), requiredSize - 1);
     buffer[requiredSize - 1] = '\0';
 
     return 0;
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 39ed9a0..2016b5e 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -138,7 +138,7 @@
         (*mGlobalData)->reportJank();
     }
 
-    bool isTripleBuffered = mSwapDeadline > frame[FrameInfoIndex::IntendedVsync];
+    bool isTripleBuffered = (mSwapDeadline - frame[FrameInfoIndex::IntendedVsync]) > (mFrameInterval * 0.1);
 
     mSwapDeadline = std::max(mSwapDeadline + mFrameInterval,
                              frame[FrameInfoIndex::IntendedVsync] + mFrameInterval);
diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include/android/os/IncidentReportArgs.h
index ee1e33c..52b555e 100644
--- a/libs/incident/include/android/os/IncidentReportArgs.h
+++ b/libs/incident/include/android/os/IncidentReportArgs.h
@@ -36,6 +36,12 @@
 const uint8_t DEST_EXPLICIT = 100;
 const uint8_t DEST_AUTOMATIC = 200;
 
+// Aliases for the above.
+const uint8_t PRIVACY_POLICY_LOCAL = 0;
+const uint8_t PRIVACY_POLICY_EXPLICIT = 100;
+const uint8_t PRIVACY_POLICY_AUTOMATIC = 200;
+const uint8_t PRIVACY_POLICY_UNSET = 255;
+
 
 class IncidentReportArgs : public Parcelable {
 public:
@@ -48,7 +54,10 @@
 
     void setAll(bool all);
     void setDest(int dest);
+    void setPrivacyPolicy(int);
     void addSection(int section);
+    void setReceiverPkg(const string&);
+    void setReceiverCls(const string&);
     void addHeader(const IncidentHeaderProto& headerProto);
 
     inline bool all() const { return mAll; }
diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp
index fbc21e5..fd0d5cc 100644
--- a/libs/incident/src/IncidentReportArgs.cpp
+++ b/libs/incident/src/IncidentReportArgs.cpp
@@ -194,5 +194,23 @@
     }
 }
 
+// stub
+void
+IncidentReportArgs::setPrivacyPolicy(int)
+{
+}
+
+// stub
+void
+IncidentReportArgs::setReceiverPkg(const string&)
+{
+}
+
+// stub
+void
+IncidentReportArgs::setReceiverCls(const string&)
+{
+}
+
 }
 }
diff --git a/location/java/android/location/GpsClock.java b/location/java/android/location/GpsClock.java
index 4135a1c..2e66b41 100644
--- a/location/java/android/location/GpsClock.java
+++ b/location/java/android/location/GpsClock.java
@@ -16,6 +16,7 @@
 
 package android.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -437,6 +438,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         final String format = "   %-15s = %s\n";
diff --git a/location/java/android/location/GpsMeasurement.java b/location/java/android/location/GpsMeasurement.java
index f13a440..a8cd756 100644
--- a/location/java/android/location/GpsMeasurement.java
+++ b/location/java/android/location/GpsMeasurement.java
@@ -16,6 +16,7 @@
 
 package android.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1244,6 +1245,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         final String format = "   %-29s = %s\n";
diff --git a/location/java/android/location/GpsMeasurementsEvent.java b/location/java/android/location/GpsMeasurementsEvent.java
index 1366873..2442d8c 100644
--- a/location/java/android/location/GpsMeasurementsEvent.java
+++ b/location/java/android/location/GpsMeasurementsEvent.java
@@ -140,6 +140,7 @@
         parcel.writeTypedArray(measurementsArray, flags);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder("[ GpsMeasurementsEvent:\n\n");
diff --git a/location/java/android/location/GpsNavigationMessage.java b/location/java/android/location/GpsNavigationMessage.java
index 5c3c710..7823597 100644
--- a/location/java/android/location/GpsNavigationMessage.java
+++ b/location/java/android/location/GpsNavigationMessage.java
@@ -290,6 +290,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         final String format = "   %-15s = %s\n";
diff --git a/location/java/android/location/GpsNavigationMessageEvent.java b/location/java/android/location/GpsNavigationMessageEvent.java
index bd6921c..8faa366 100644
--- a/location/java/android/location/GpsNavigationMessageEvent.java
+++ b/location/java/android/location/GpsNavigationMessageEvent.java
@@ -109,6 +109,7 @@
         parcel.writeParcelable(mNavigationMessage, flags);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder("[ GpsNavigationMessageEvent:\n\n");
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 154bd56..9c9c715 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -16,6 +16,7 @@
 
 package android.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -705,6 +706,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index 35f2877..349b9e0 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -16,9 +16,9 @@
 
 java_sdk_library {
     name: "com.android.location.provider",
-    srcs: ["java/**/*.java"],
+    srcs: [
+        "java/**/*.java",
+        ":framework-srcs",
+    ],
     api_packages: ["com.android.location.provider"],
-    srcs_lib: "framework",
-    srcs_lib_whitelist_dirs: ["location/java"],
-    srcs_lib_whitelist_pkgs: ["com.android.internal.location"],
 }
diff --git a/media/OWNERS b/media/OWNERS
index 72c8952..0a12adc 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -9,6 +9,7 @@
 jaewan@google.com
 jmtrivi@google.com
 jsharkey@android.com
+klhyun@google.com
 lajos@google.com
 marcone@google.com
 sungsoo@google.com
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index 0a9ca02..8875a15 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -144,7 +145,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj)
             return true;
         if (obj == null)
diff --git a/media/java/android/media/AudioPortConfig.java b/media/java/android/media/AudioPortConfig.java
index 45e49a7..ac19bb1 100644
--- a/media/java/android/media/AudioPortConfig.java
+++ b/media/java/android/media/AudioPortConfig.java
@@ -95,7 +95,6 @@
 
     /**
      * The gain configuration if this port supports gain control, null otherwise
-     * @see AudioGainConfig.
      */
     public AudioGainConfig gain() {
         return mGain;
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
index f9a4b1e..6d9d626 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -19,10 +19,12 @@
 import android.annotation.UnsupportedAppUsage;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.Message;
-import java.util.ArrayList;
+
+import com.android.internal.annotations.GuardedBy;
+
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 
 /**
  * The AudioPortEventHandler handles AudioManager.OnAudioPortUpdateListener callbacks
@@ -33,6 +35,9 @@
 class AudioPortEventHandler {
     private Handler mHandler;
     private HandlerThread mHandlerThread;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     private final ArrayList<AudioManager.OnAudioPortUpdateListener> mListeners =
             new ArrayList<AudioManager.OnAudioPortUpdateListener>();
 
@@ -53,7 +58,7 @@
     private long mJniCallback;
 
     void init() {
-        synchronized (this) {
+        synchronized (mLock) {
             if (mHandler != null) {
                 return;
             }
@@ -66,7 +71,7 @@
                     @Override
                     public void handleMessage(Message msg) {
                         ArrayList<AudioManager.OnAudioPortUpdateListener> listeners;
-                        synchronized (this) {
+                        synchronized (mLock) {
                             if (msg.what == AUDIOPORT_EVENT_NEW_LISTENER) {
                                 listeners = new ArrayList<AudioManager.OnAudioPortUpdateListener>();
                                 if (mListeners.contains(msg.obj)) {
@@ -152,7 +157,7 @@
     private native void native_finalize();
 
     void registerListener(AudioManager.OnAudioPortUpdateListener l) {
-        synchronized (this) {
+        synchronized (mLock) {
             mListeners.add(l);
         }
         if (mHandler != null) {
@@ -162,7 +167,7 @@
     }
 
     void unregisterListener(AudioManager.OnAudioPortUpdateListener l) {
-        synchronized (this) {
+        synchronized (mLock) {
             mListeners.remove(l);
         }
     }
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index fb10e6e..6b5ecd1 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -41,7 +41,6 @@
 import android.os.UserHandle;
 import android.service.media.MediaBrowserService;
 import android.service.notification.NotificationListenerService;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -882,7 +881,7 @@
          * @return {@code true} if equals, {@code false} otherwise
          */
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (!(obj instanceof RemoteUserInfo)) {
                 return false;
             }
diff --git a/media/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java
index 762f0c0..ad8c949 100644
--- a/media/java/android/media/tv/TvInputHardwareInfo.java
+++ b/media/java/android/media/tv/TvInputHardwareInfo.java
@@ -19,12 +19,14 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.hardware.tv.input.V1_0.Constants;
 import android.media.AudioManager;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
+
 import java.lang.annotation.Retention;
 
 /**
@@ -141,6 +143,7 @@
         return mCableConnectionStatus;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder b = new StringBuilder(128);
diff --git a/media/java/android/media/tv/TvStreamConfig.java b/media/java/android/media/tv/TvStreamConfig.java
index 0c2f3fe..75fe11a 100644
--- a/media/java/android/media/tv/TvStreamConfig.java
+++ b/media/java/android/media/tv/TvStreamConfig.java
@@ -16,6 +16,8 @@
 
 package android.media.tv;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -87,6 +89,7 @@
         return mGeneration;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TvStreamConfig {mStreamId=" + mStreamId + ";" + "mType=" + mType + ";mGeneration="
@@ -163,7 +166,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null) return false;
         if (!(obj instanceof TvStreamConfig)) return false;
 
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index 2bc9a2b..68c2a84 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -277,28 +277,36 @@
     @Override
     public boolean equals(Object o) {
         if (this == o) {
-          return true;
+            return true;
         }
 
         if (!(o instanceof TvTrackInfo)) {
-          return false;
+            return false;
         }
 
         TvTrackInfo obj = (TvTrackInfo) o;
-        return TextUtils.equals(mId, obj.mId)
-                && mType == obj.mType
-                && TextUtils.equals(mLanguage, obj.mLanguage)
-                && TextUtils.equals(mDescription, obj.mDescription)
-                && mEncrypted == obj.mEncrypted
-                && Objects.equals(mExtra, obj.mExtra)
-                && (mType == TYPE_AUDIO
-                        ? mAudioChannelCount == obj.mAudioChannelCount
-                        && mAudioSampleRate == obj.mAudioSampleRate
-                        : (mType == TYPE_VIDEO
-                                ? mVideoWidth == obj.mVideoWidth
-                                && mVideoHeight == obj.mVideoHeight
-                                && mVideoFrameRate == obj.mVideoFrameRate
-                                && mVideoPixelAspectRatio == obj.mVideoPixelAspectRatio : true));
+
+        if (!TextUtils.equals(mId, obj.mId) || mType != obj.mType
+                || !TextUtils.equals(mLanguage, obj.mLanguage)
+                || !TextUtils.equals(mDescription, obj.mDescription)
+                || !Objects.equals(mExtra, obj.mExtra)) {
+            return false;
+        }
+
+        switch (mType) {
+            case TYPE_AUDIO:
+                return mAudioChannelCount == obj.mAudioChannelCount
+                        && mAudioSampleRate == obj.mAudioSampleRate;
+
+            case TYPE_VIDEO:
+                return mVideoWidth == obj.mVideoWidth
+                        && mVideoHeight == obj.mVideoHeight
+                        && mVideoFrameRate == obj.mVideoFrameRate
+                        && mVideoPixelAspectRatio == obj.mVideoPixelAspectRatio
+                        && mVideoActiveFormatDescription == obj.mVideoActiveFormatDescription;
+        }
+
+        return true;
     }
 
     @Override
@@ -386,6 +394,7 @@
          *
          * @param encrypted The encryption status of the track.
          */
+        @NonNull
         public Builder setEncrypted(boolean encrypted) {
             mEncrypted = encrypted;
             return this;
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index e568ef7..2bd401f 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -25,6 +25,7 @@
 import android.content.SharedPreferences;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.media.ExifInterface;
 import android.media.MediaScanner;
 import android.net.Uri;
 import android.os.BatteryManager;
@@ -47,6 +48,7 @@
 import com.google.android.collect.Sets;
 
 import java.io.File;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -843,6 +845,52 @@
         return obj.getFormat();
     }
 
+    private boolean getThumbnailInfo(int handle, long[] outLongs) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null) {
+            return false;
+        }
+
+        String path = obj.getPath().toString();
+        switch (obj.getFormat()) {
+            case MtpConstants.FORMAT_HEIF:
+            case MtpConstants.FORMAT_EXIF_JPEG:
+            case MtpConstants.FORMAT_JFIF:
+                try {
+                    ExifInterface exif = new ExifInterface(path);
+                    long[] thumbOffsetAndSize = exif.getThumbnailRange();
+                    outLongs[0] = thumbOffsetAndSize != null ? thumbOffsetAndSize[1] : 0;
+                    outLongs[1] = exif.getAttributeInt(ExifInterface.TAG_PIXEL_X_DIMENSION, 0);
+                    outLongs[2] = exif.getAttributeInt(ExifInterface.TAG_PIXEL_Y_DIMENSION, 0);
+                    return true;
+                } catch (IOException e) {
+                    // ignore and fall through
+                }
+        }
+        return false;
+    }
+
+    private byte[] getThumbnailData(int handle) {
+        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+        if (obj == null) {
+            return null;
+        }
+
+        String path = obj.getPath().toString();
+        switch (obj.getFormat()) {
+            case MtpConstants.FORMAT_HEIF:
+            case MtpConstants.FORMAT_EXIF_JPEG:
+            case MtpConstants.FORMAT_JFIF:
+                try {
+                    ExifInterface exif = new ExifInterface(path);
+                    return exif.getThumbnail();
+                } catch (IOException e) {
+                    // ignore and fall through
+                }
+        }
+        return null;
+    }
+
     private int beginDeleteObject(int handle) {
         MtpStorageManager.MtpObject obj = mManager.getObject(handle);
         if (obj == null) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 7a41c77..557eb9f 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -50,7 +50,6 @@
         "libstagefright_foundation",
         "libcamera_client",
         "libmtp",
-        "libexif",
         "libpiex",
         "libprocessgroup",
         "libandroidfw",
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index a6c5fc8..c5389d1 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -30,13 +30,6 @@
 #include "src/piex_types.h"
 #include "src/piex.h"
 
-extern "C" {
-#include "libexif/exif-content.h"
-#include "libexif/exif-data.h"
-#include "libexif/exif-tag.h"
-#include "libexif/exif-utils.h"
-}
-
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <jni.h>
@@ -70,6 +63,8 @@
 static jmethodID method_getObjectPropertyList;
 static jmethodID method_getObjectInfo;
 static jmethodID method_getObjectFilePath;
+static jmethodID method_getThumbnailInfo;
+static jmethodID method_getThumbnailData;
 static jmethodID method_beginDeleteObject;
 static jmethodID method_endDeleteObject;
 static jmethodID method_beginMoveObject;
@@ -219,7 +214,7 @@
         return; // Already threw.
     }
     mIntBuffer = (jintArray)env->NewGlobalRef(intArray);
-    jlongArray longArray = env->NewLongArray(2);
+    jlongArray longArray = env->NewLongArray(3);
     if (!longArray) {
         return; // Already threw.
     }
@@ -780,57 +775,6 @@
     return result;
 }
 
-static void foreachentry(ExifEntry *entry, void* /* user */) {
-    char buf[1024];
-    ALOGI("entry %x, format %d, size %d: %s",
-            entry->tag, entry->format, entry->size, exif_entry_get_value(entry, buf, sizeof(buf)));
-}
-
-static void foreachcontent(ExifContent *content, void *user) {
-    ALOGI("content %d", exif_content_get_ifd(content));
-    exif_content_foreach_entry(content, foreachentry, user);
-}
-
-static long getLongFromExifEntry(ExifEntry *e) {
-    ExifByteOrder o = exif_data_get_byte_order(e->parent->parent);
-    return exif_get_long(e->data, o);
-}
-
-static ExifData *getExifFromExtractor(const char *path) {
-    std::unique_ptr<uint8_t[]> exifBuf;
-    ExifData *exifdata = NULL;
-
-    FILE *fp = fopen (path, "rb");
-    if (!fp) {
-        ALOGE("failed to open file");
-        return NULL;
-    }
-
-    sp<NuMediaExtractor> extractor = new NuMediaExtractor();
-    fseek(fp, 0L, SEEK_END);
-    if (extractor->setDataSource(fileno(fp), 0, ftell(fp)) != OK) {
-        ALOGE("failed to setDataSource");
-        fclose(fp);
-        return NULL;
-    }
-
-    off64_t offset;
-    size_t size;
-    if (extractor->getExifOffsetSize(&offset, &size) != OK) {
-        fclose(fp);
-        return NULL;
-    }
-
-    exifBuf.reset(new uint8_t[size]);
-    fseek(fp, offset, SEEK_SET);
-    if (fread(exifBuf.get(), 1, size, fp) == size) {
-        exifdata = exif_data_new_from_data(exifBuf.get(), size);
-    }
-
-    fclose(fp);
-    return exifdata;
-}
-
 MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle,
                                              MtpObjectInfo& info) {
     MtpStringBuffer path;
@@ -877,26 +821,23 @@
         case MTP_FORMAT_EXIF_JPEG:
         case MTP_FORMAT_HEIF:
         case MTP_FORMAT_JFIF: {
-            ExifData *exifdata;
-            if (info.mFormat == MTP_FORMAT_HEIF) {
-                exifdata = getExifFromExtractor(path);
-            } else {
-                exifdata = exif_data_new_from_file(path);
-            }
-            if (exifdata) {
-                if ((false)) {
-                    exif_data_foreach_content(exifdata, foreachcontent, NULL);
-                }
+            env = AndroidRuntime::getJNIEnv();
+            if (env->CallBooleanMethod(
+                    mDatabase, method_getThumbnailInfo, (jint)handle, mLongBuffer)) {
 
-                ExifEntry *w = exif_content_get_entry(
-                        exifdata->ifd[EXIF_IFD_EXIF], EXIF_TAG_PIXEL_X_DIMENSION);
-                ExifEntry *h = exif_content_get_entry(
-                        exifdata->ifd[EXIF_IFD_EXIF], EXIF_TAG_PIXEL_Y_DIMENSION);
-                info.mThumbCompressedSize = exifdata->data ? exifdata->size : 0;
-                info.mThumbFormat = MTP_FORMAT_EXIF_JPEG;
-                info.mImagePixWidth = w ? getLongFromExifEntry(w) : 0;
-                info.mImagePixHeight = h ? getLongFromExifEntry(h) : 0;
-                exif_data_unref(exifdata);
+                jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
+                jlong size = longValues[0];
+                jlong w = longValues[1];
+                jlong h = longValues[2];
+                if (size > 0 && size <= UINT32_MAX &&
+                        w > 0 && w <= UINT32_MAX &&
+                        h > 0 && h <= UINT32_MAX) {
+                    info.mThumbCompressedSize = size;
+                    info.mThumbFormat = MTP_FORMAT_EXIF_JPEG;
+                    info.mImagePixWidth = w;
+                    info.mImagePixHeight = h;
+                }
+                env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
             }
             break;
         }
@@ -941,22 +882,19 @@
             case MTP_FORMAT_EXIF_JPEG:
             case MTP_FORMAT_HEIF:
             case MTP_FORMAT_JFIF: {
-                ExifData *exifdata;
-                if (format == MTP_FORMAT_HEIF) {
-                    exifdata = getExifFromExtractor(path);
-                } else {
-                    exifdata = exif_data_new_from_file(path);
+                JNIEnv* env = AndroidRuntime::getJNIEnv();
+                jbyteArray thumbData = (jbyteArray) env->CallObjectMethod(
+                        mDatabase, method_getThumbnailData, (jint)handle);
+                if (thumbData == NULL) {
+                    return nullptr;
                 }
-                if (exifdata) {
-                    if (exifdata->data) {
-                        result = malloc(exifdata->size);
-                        if (result) {
-                            memcpy(result, exifdata->data, exifdata->size);
-                            outThumbSize = exifdata->size;
-                        }
-                    }
-                    exif_data_unref(exifdata);
+                jsize thumbSize = env->GetArrayLength(thumbData);
+                result = malloc(thumbSize);
+                if (result) {
+                    env->GetByteArrayRegion(thumbData, 0, thumbSize, (jbyte*)result);
+                    outThumbSize = thumbSize;
                 }
+                env->DeleteLocalRef(thumbData);
                 break;
             }
 
@@ -1388,6 +1326,8 @@
     GET_METHOD_ID(getObjectPropertyList, clazz, "(IIIII)Landroid/mtp/MtpPropertyList;");
     GET_METHOD_ID(getObjectInfo, clazz, "(I[I[C[J)Z");
     GET_METHOD_ID(getObjectFilePath, clazz, "(I[C[J)I");
+    GET_METHOD_ID(getThumbnailInfo, clazz, "(I[J)Z");
+    GET_METHOD_ID(getThumbnailData, clazz, "(I)[B");
     GET_METHOD_ID(beginDeleteObject, clazz, "(I)I");
     GET_METHOD_ID(endDeleteObject, clazz, "(IZ)V");
     GET_METHOD_ID(beginMoveObject, clazz, "(III)I");
diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp
index 44f8725..85a007f 100644
--- a/media/lib/signer/Android.bp
+++ b/media/lib/signer/Android.bp
@@ -16,9 +16,9 @@
 
 java_sdk_library {
     name: "com.android.mediadrm.signer",
-    srcs: ["java/**/*.java"],
+    srcs: [
+        "java/**/*.java",
+        ":framework-srcs",
+    ],
     api_packages: ["com.android.mediadrm.signer"],
-    srcs_lib: "framework",
-    srcs_lib_whitelist_dirs: ["media/java"],
-    srcs_lib_whitelist_pkgs: ["android.media"],
 }
diff --git a/packages/CaptivePortalLogin/Android.bp b/packages/CaptivePortalLogin/Android.bp
deleted file mode 100644
index c9183f6..0000000
--- a/packages/CaptivePortalLogin/Android.bp
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-java_defaults {
-    name: "CaptivePortalLoginDefaults",
-    srcs: ["src/**/*.java"],
-    sdk_version: "system_current",
-    min_sdk_version: "28",
-    static_libs: [
-        "android-support-v4",
-        "metrics-constants-protos",
-        "captiveportal-lib",
-    ],
-    manifest: "AndroidManifest.xml",
-}
-
-android_app {
-    name: "CaptivePortalLogin",
-    defaults: ["CaptivePortalLoginDefaults"],
-    certificate: "networkstack",
-}
-
-// Alternative CaptivePortalLogin signed with the platform cert, to use
-// with InProcessNetworkStack.
-android_app {
-    name: "PlatformCaptivePortalLogin",
-    defaults: ["CaptivePortalLoginDefaults"],
-    certificate: "platform",
-    overrides: ["CaptivePortalLogin"],
-}
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
deleted file mode 100644
index 0a03425..0000000
--- a/packages/CaptivePortalLogin/AndroidManifest.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.captiveportallogin"
-    android:versionCode="11"
-    android:versionName="Q-initial">
-
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
-    <uses-permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" />
-    <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" />
-
-    <application android:label="@string/app_name"
-                 android:icon="@drawable/app_icon"
-                 android:usesCleartextTraffic="true"
-                 android:supportsRtl="true" >
-        <activity
-            android:name="com.android.captiveportallogin.CaptivePortalLoginActivity"
-            android:label="@string/action_bar_label"
-            android:theme="@style/AppTheme"
-            android:configChanges="keyboardHidden|orientation|screenSize" >
-            <intent-filter>
-                <action android:name="android.net.conn.CAPTIVE_PORTAL"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/packages/CaptivePortalLogin/OWNERS b/packages/CaptivePortalLogin/OWNERS
deleted file mode 100644
index d3836d4..0000000
--- a/packages/CaptivePortalLogin/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-set noparent
-
-codewiz@google.com
-jchalard@google.com
-junyulai@google.com
-lorenzo@google.com
-reminv@google.com
-satk@google.com
diff --git a/packages/CaptivePortalLogin/assets/quantum_ic_warning_amber_96.png b/packages/CaptivePortalLogin/assets/quantum_ic_warning_amber_96.png
deleted file mode 100644
index 08294ce..0000000
--- a/packages/CaptivePortalLogin/assets/quantum_ic_warning_amber_96.png
+++ /dev/null
Binary files differ
diff --git a/packages/CaptivePortalLogin/res/drawable/app_icon.xml b/packages/CaptivePortalLogin/res/drawable/app_icon.xml
deleted file mode 100644
index 456ca83..0000000
--- a/packages/CaptivePortalLogin/res/drawable/app_icon.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-    Copyright (C) 2019 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background>
-        <color android:color="@*android:color/accent_device_default_light" />
-    </background>
-    <foreground>
-        <inset
-            android:drawable="@drawable/maybe_wifi"
-            android:inset="25%">
-        </inset>
-    </foreground>
-</adaptive-icon>
diff --git a/packages/CaptivePortalLogin/res/drawable/maybe_wifi.xml b/packages/CaptivePortalLogin/res/drawable/maybe_wifi.xml
deleted file mode 100644
index 207aade..0000000
--- a/packages/CaptivePortalLogin/res/drawable/maybe_wifi.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2019 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="26.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="26.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#4DFFFFFF"
-        android:pathData="M19.1,14l-3.4,0l0,-1.5c0,-1.8 0.8,-2.8 1.5,-3.4C18.1,8.3 19.200001,8 20.6,8c1.2,0 2.3,0.3 3.1,0.8l1.9,-2.3C25.1,6.1 20.299999,2.1 13,2.1S0.9,6.1 0.4,6.5L13,22l0,0l0,0l0,0l0,0l6.5,-8.1L19.1,14z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M19.5,17.799999c0,-0.8 0.1,-1.3 0.2,-1.6c0.2,-0.3 0.5,-0.7 1.1,-1.2c0.4,-0.4 0.7,-0.8 1,-1.1s0.4,-0.8 0.4,-1.2c0,-0.5 -0.1,-0.9 -0.4,-1.2c-0.3,-0.3 -0.7,-0.4 -1.2,-0.4c-0.4,0 -0.8,0.1 -1.1,0.3c-0.3,0.2 -0.4,0.6 -0.4,1.1l-1.9,0c0,-1 0.3,-1.7 1,-2.2c0.6,-0.5 1.5,-0.8 2.5,-0.8c1.1,0 2,0.3 2.6,0.8c0.6,0.5 0.9,1.3 0.9,2.3c0,0.7 -0.2,1.3 -0.6,1.8c-0.4,0.6 -0.9,1.1 -1.5,1.6c-0.3,0.3 -0.5,0.5 -0.6,0.7c-0.1,0.2 -0.1,0.6 -0.1,1L19.5,17.700001zM21.4,21l-1.9,0l0,-1.8l1.9,0L21.4,21z"/>
-</vector>
diff --git a/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml
deleted file mode 100644
index c292323..0000000
--- a/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/container"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:context="com.android.captiveportallogin.CaptivePortalLoginActivity"
-    tools:ignore="MergeRootFrame" >
-
-    <LinearLayout
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical" >
-
-      <FrameLayout
-          android:layout_width="match_parent"
-          android:layout_height="4dp" >
-
-        <!-- Eliminates ProgressBar padding by boxing it into a 4dp high container -->
-        <ProgressBar
-            android:id="@+id/progress_bar"
-            style="@android:style/Widget.Material.Light.ProgressBar.Horizontal"
-            android:indeterminate="false"
-            android:max="100"
-            android:progress="0"
-            android:layout_gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-      </FrameLayout>
-
-      <android.support.v4.widget.SwipeRefreshLayout
-            android:id="@+id/swipe_refresh"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
-        <WebView
-              android:id="@+id/webview"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout_alignParentBottom="false"
-              android:layout_alignParentRight="false" />
-      </android.support.v4.widget.SwipeRefreshLayout>
-
-    </LinearLayout>
-</FrameLayout>
diff --git a/packages/CaptivePortalLogin/res/layout/ssl_warning.xml b/packages/CaptivePortalLogin/res/layout/ssl_warning.xml
deleted file mode 100644
index ce05e78..0000000
--- a/packages/CaptivePortalLogin/res/layout/ssl_warning.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical" >
-
-    <!-- ssl error type -->
-    <TextView
-        android:id="@+id/ssl_error_type"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="start"
-        android:text="SSL_UNKNOWN"
-        android:layout_marginStart="24dip"
-        android:layout_marginEnd="24dip"
-        android:layout_marginBottom="0dip"
-        android:layout_marginTop="24dip" />
-
-    <!-- Page info: -->
-    <TextView
-        android:id="@+id/page_info"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/page_info"
-        android:textStyle="bold"
-        android:layout_marginStart="24dip"
-        android:layout_marginEnd="24dip" />
-
-    <!-- Title: -->
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textStyle="bold"
-        android:layout_marginStart="24dip"
-        android:layout_marginEnd="24dip" />
-
-    <!-- Address: -->
-    <TextView
-        android:id="@+id/address_header"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/page_info_address"
-        android:layout_marginStart="24dip"
-        android:layout_marginEnd="24dip" />
-
-    <TextView
-        android:id="@+id/address"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="24dip"
-        android:layout_marginEnd="24dip" />
-
-    <ScrollView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingStart="4dip"
-        android:paddingEnd="4dip" >
-
-        <!-- certificate view: -->
-        <LinearLayout
-            android:id="@+id/certificate_layout"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            android:layout_marginBottom="16dip" >
-            <TextView
-                android:id="@+id/ssl_error_msg"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:textAppearance="?android:attr/textAppearanceSmall"
-                android:layout_marginStart="20dip"
-                android:layout_marginEnd="20dip"
-                android:gravity="center_vertical"
-                android:layout_marginBottom="4dip"
-                android:layout_marginTop="16dip" />
-        </LinearLayout>
-
-    </ScrollView>
-
-</LinearLayout>
diff --git a/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml b/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml
deleted file mode 100644
index 1a88c5c..0000000
--- a/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:context="com.android.captiveportallogin.CaptivePortalLoginActivity" >
-    <item
-        android:id="@+id/action_do_not_use_network"
-        android:orderInCategory="100"
-        android:showAsAction="never"
-        android:title="@string/action_do_not_use_network"/>
-    <item
-        android:id="@+id/action_use_network"
-        android:orderInCategory="200"
-        android:showAsAction="never"
-        android:title="@string/action_use_network"/>
-
-</menu>
diff --git a/packages/CaptivePortalLogin/res/values-af/strings.xml b/packages/CaptivePortalLogin/res/values-af/strings.xml
deleted file mode 100644
index cf4dc82..0000000
--- a/packages/CaptivePortalLogin/res/values-af/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortal-aanmelding"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Gebruik hierdie netwerk nes dit is"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Moenie hierdie netwerk gebruik nie"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Meld by netwerk aan"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Meld aan by %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Die netwerk waarby jy probeer aansluit, het sekuriteitkwessies."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Byvoorbeeld, die aanmeldbladsy behoort dalk nie aan die organisasie wat gewys word nie."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Gaan in elk geval deur blaaier voort"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Bladsy-inligting"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sekuriteitswaarskuwing"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Bekyk sertifikaat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Hierdie sertifikaat is nie van \'n betroubare owerheid nie."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Die naam van die werf kom nie ooreen met die naam op die sertifikaat nie."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Hierdie sertifikaat het verval."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Hierdie sertifikaat is nog nie geldig nie."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Hierdie sertifikaat het \'n ongeldige datum."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Hierdie sertifikaat is ongeldig."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Onbekende sertifikaatfout."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-am/strings.xml b/packages/CaptivePortalLogin/res/values-am/strings.xml
deleted file mode 100644
index cdcb5a5..0000000
--- a/packages/CaptivePortalLogin/res/values-am/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"ይህን አውታረ መረብ እንዳለ ተጠቀምበት"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"ይህን አውታረ መረብ አትጠቀምበት"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ወደ አውታረ መረብ በመለያ ይግቡ"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"ወደ %1$s ይግቡ"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"ለመቀላቀል እየሞከሩ ያሉት አውታረ መረብ የደህንነት ችግሮች አሉበት።"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ለምሳሌ፣ የመግቢያ ገጹ የሚታየው ድርጅት ላይሆን ይችላል።"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ለማንኛውም በአሳሽ በኩል ይቀጥሉ"</string>
-    <string name="ok" msgid="1509280796718850364">"እሺ"</string>
-    <string name="page_info" msgid="4048529256302257195">"የገፅ መረጃ"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"አድራሻ:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"የደህንነት ቅንብሮች"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"ምስክሮች ይመልከቱ"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"ይህ ምስክር ከታማኝ ቦታ አይደለም።"</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"የጣቢያው ስም ከምስክር ወረቀቱ ስም ጋር አይዛመድም።"</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"ይህ ምስክር ጊዜው አልፏል"</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"ይህ ምስክር ገና ትክክል አይደለም።"</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"ይህ ምስክር ትክክለኛ ቀን አለው።"</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"ይህ ምስክር ትክክል ያልሆነ ነው።"</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"ያልታወቀ የምስክር ስህተት።"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ar/strings.xml b/packages/CaptivePortalLogin/res/values-ar/strings.xml
deleted file mode 100644
index 799b8aa..0000000
--- a/packages/CaptivePortalLogin/res/values-ar/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"استخدام هذه الشبكة كما هي"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"عدم استخدام هذه الشبكة"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"تسجيل الدخول إلى الشبكة"</string>
-    <!-- String.format failed for translation -->
-    <!-- no translation found for action_bar_title (5645564790486983117) -->
-    <skip />
-    <string name="ssl_error_warning" msgid="6653188881418638872">"الشبكة التي تحاول الانضمام إليها بها مشكلات أمنية."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"على سبيل المثال، قد لا تنتمي صفحة تسجيل الدخول إلى المنظمة المعروضة."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"المتابعة على أي حال عبر المتصفح"</string>
-    <string name="ok" msgid="1509280796718850364">"موافق"</string>
-    <string name="page_info" msgid="4048529256302257195">"معلومات الصفحة"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"العنوان:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"تحذير أمان"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"عرض الشهادة"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"هذه الشهادة ليست من جهة موثوق بها."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"لا يتطابق اسم الموقع مع الاسم على الشهادة."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"انتهت صلاحية هذه الشهادة."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"هذه الشهادة ليست صالحة بعد."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"تشتمل هذه الشهادة على تاريخ غير صالح."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"هذه الشهادة غير صالحة."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"حدث خطأ غير معروف بالشهادة."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-as/strings.xml b/packages/CaptivePortalLogin/res/values-as/strings.xml
deleted file mode 100644
index 94c3147..0000000
--- a/packages/CaptivePortalLogin/res/values-as/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"এই নেটৱৰ্কটো এইদৰে ব্যৱহাৰ কৰক"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"এই নেটৱৰ্কটো ব্যৱহাৰ নকৰিব"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"নেটৱৰ্কত ছাইন ইন কৰক"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$st ছাইন ইন কৰক"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"আপুনি সংযোগ কৰিবলৈ চেষ্টা কৰি থকা নেটৱৰ্কটোত সুৰক্ষাজনিত সমস্যা আছে।"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"উদাহৰণস্বৰূপে, আপোনাক দেখুওৱা লগ ইনৰ পৃষ্ঠাটো প্ৰতিষ্ঠানটোৰ নিজা নহ\'বও পাৰে।"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"তথাপি ব্ৰাউজাৰৰ জৰিয়তে অব্যাহত ৰাখক"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-az/strings.xml b/packages/CaptivePortalLogin/res/values-az/strings.xml
deleted file mode 100644
index 44b406d..0000000
--- a/packages/CaptivePortalLogin/res/values-az/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Bu şəbəkəni olduğu kimi istifadə edin"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Bu şəbəkəni istifadə etməyin"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Şəbəkəyə daxil olun"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Daxil olun: %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Qoşulmaq istədiyiniz şəbəkənin təhlükəsizlik problemləri var."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Məsələn, giriş səhifəsi göstərilən təşkilata aid olmaya bilər."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Hər bir halda brazuer ilə davam edin"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-b+sr+Latn/strings.xml b/packages/CaptivePortalLogin/res/values-b+sr+Latn/strings.xml
deleted file mode 100644
index f2a6e07..0000000
--- a/packages/CaptivePortalLogin/res/values-b+sr+Latn/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Koristi ovu mrežu takvu kakva je"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ne koristi ovu mrežu"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Prijavi me na mrežu"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Prijavite se u: %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Mreža kojoj pokušavate da se pridružite ima bezbednosnih problema."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Na primer, stranica za prijavljivanje možda ne pripada prikazanoj organizaciji."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Ipak nastavi preko pregledača"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-be/strings.xml b/packages/CaptivePortalLogin/res/values-be/strings.xml
deleted file mode 100644
index 09ed1de..0000000
--- a/packages/CaptivePortalLogin/res/values-be/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Выкарыстоўваць гэтую сетку як ёсць"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Не выкарыстоўваць гэту сетку"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Увайсці ў сетку"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Увайсці ў %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"У сеткі, да якой вы спрабуеце далучыцца, ёсць праблемы з бяспекай."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Напрыклад, старонка ўваходу можа не належаць указанай арганізацыі."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Усё роўна працягнуць праз браўзер"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-bg/strings.xml b/packages/CaptivePortalLogin/res/values-bg/strings.xml
deleted file mode 100644
index 4dd8aa0..0000000
--- a/packages/CaptivePortalLogin/res/values-bg/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Директно използване на тази мрежа"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Без използване на тази мрежа"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Вход в мрежата"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Влезте в/ъв %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Мрежата, към която опитвате да се присъедините, има проблеми със сигурността."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Например страницата за вход може да не принадлежи на показаната организация."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Продължаване през браузър въпреки това"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Данни за страницата"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Адрес:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Предупреждение относно защитата"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Преглед на сертификата"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Сертификатът не е от надежден орган."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Името на сайта не съответства на името в сертификата."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Сертификатът е изтекъл."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Сертификатът още не е валиден."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Този сертификат е с невалидна дата."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Този сертификат е невалиден."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Неизвестна грешка в сертификата."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-bn/strings.xml b/packages/CaptivePortalLogin/res/values-bn/strings.xml
deleted file mode 100644
index fb703cf..0000000
--- a/packages/CaptivePortalLogin/res/values-bn/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"যেভাবে আছে সেভাবেই এই নেটওয়ার্ক ব্যবহার করুন"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"এই নেটওয়ার্ক ব্যবহার করবেন না"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"নেটওয়ার্কে সাইন-ইন করুন"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s তে সাইন-ইন করুন"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"আপনি যে নেটওয়ার্কে যোগ দেওয়ার চেষ্টা করছেন তাতে নিরাপত্তার সমস্যা আছে।"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"উদাহরণস্বরূপ, লগ-ইন পৃষ্ঠাটি প্রদর্শিত প্রতিষ্ঠানের অন্তর্গত নাও হতে পারে৷"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"যাই হোক না কেন ব্রাউজারের মাধ্যমে অবিরত রাখুন"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Sideinfo"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sikkerhetsadvarsel"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vis sertifikat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sertifikatet er ikke fra en pålitelig myndighet."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Navnet på nettstedet samsvarer ikke med navnet på sertifikatet."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Sertifikatet er utløpt."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sertifikatet er ikke gyldig ennå."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dette sertifikatet har en ugyldig dato."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Dette sertifikatet er ugyldig."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Ukjent sertifikatfeil."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-bs/strings.xml b/packages/CaptivePortalLogin/res/values-bs/strings.xml
deleted file mode 100644
index 10be0e5..0000000
--- a/packages/CaptivePortalLogin/res/values-bs/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"Prijava na zaštitnom portalu"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Koristi ovu mrežu kakva jeste"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ne koristi ovu mrežu"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Prijava na mrežu"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Prijava na %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Mreža kojoj pokušavate pristupiti ima sigurnosnih problema."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Naprimjer, stranica za prijavu možda ne pripada prikazanoj organizaciji."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Ipak nastavi preko preglednika"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ca/strings.xml b/packages/CaptivePortalLogin/res/values-ca/strings.xml
deleted file mode 100644
index a2c9ed8..0000000
--- a/packages/CaptivePortalLogin/res/values-ca/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Fes servir aquesta xarxa tal com està."</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"No facis servir aquesta xarxa."</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Inicia la sessió a la xarxa"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Inicia la sessió a %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"La xarxa a què et vols connectar té problemes de seguretat."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Per exemple, la pàgina d\'inici de sessió podria no pertànyer a l\'organització que es mostra."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continua igualment mitjançant el navegador"</string>
-    <string name="ok" msgid="1509280796718850364">"D\'acord"</string>
-    <string name="page_info" msgid="4048529256302257195">"Informació de la pàgina"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adreça:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Advertiment de seguretat"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visualitza el certificat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Aquest certificat no és d\'una autoritat de confiança."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"El nom del lloc no coincideix amb el del certificat."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Aquest certificat ha caducat."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Aquest certificat encara no és vàlid."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Aquest certificat té una data no vàlida."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Aquest certificat no és vàlid."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Error de certificat desconegut."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-cs/strings.xml b/packages/CaptivePortalLogin/res/values-cs/strings.xml
deleted file mode 100644
index be649a5..0000000
--- a/packages/CaptivePortalLogin/res/values-cs/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Použít tuto síť tak, jak je"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Tuto síť nepoužívat"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Přihlásit se k síti"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Přihlaste se k síti %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Síť, ke které se pokoušíte připojit, má bezpečnostní problémy."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Například přihlašovací stránka nemusí patřit do zobrazované organizace."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Přesto pokračovat prostřednictvím prohlížeče"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Informace o stránce"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adresa:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Upozornění zabezpečení"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Zobrazit certifikát"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Tento certifikát nepochází od důvěryhodné autority."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Název webu se neshoduje s názvem uvedeným v certifikátu."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Platnost certifikátu vypršela."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Tento certifikát ještě není platný."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Datum tohoto certifikátu není platné."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Tento certifikát je neplatný."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Neznámá chyba certifikátu."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-da/strings.xml b/packages/CaptivePortalLogin/res/values-da/strings.xml
deleted file mode 100644
index 8183105..0000000
--- a/packages/CaptivePortalLogin/res/values-da/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"Login til captive portal"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Brug dette netværk, som det er"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Brug ikke dette netværk"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Log ind på netværk"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Log ind på %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Der er sikkerhedsproblemer på det netværk, du forsøger at logge ind på."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Det er f.eks. ikke sikkert, at loginsiden tilhører den anførte organisation."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Fortsæt alligevel via browseren"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Sideoplysninger"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sikkerhedsadvarsel"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vis certifikat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Dette certifikat stammer ikke fra en troværdig autoritet."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Navnet på websitet stemmer ikke overens med navnet på certifikatet."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Dette certifikat er udløbet."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Dette certifikat er endnu ikke gyldigt."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dette certifikat har en ugyldig dato."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Dette certifikat er ugyldigt."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Ukendt fejl i certifikatet."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-de/strings.xml b/packages/CaptivePortalLogin/res/values-de/strings.xml
deleted file mode 100644
index 68862bf..0000000
--- a/packages/CaptivePortalLogin/res/values-de/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Dieses Netzwerk im Istzustand verwenden"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Dieses Netzwerk nicht verwenden"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Im Netzwerk anmelden"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"In %1$s anmelden"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Im Netzwerk, zu dem du eine Verbindung herstellen möchtest, liegen Sicherheitsprobleme vor."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Beispiel: Die Log-in-Seite gehört möglicherweise nicht zur angezeigten Organisation."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Trotzdem in einem Browser fortfahren"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Seiteninfo"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sicherheitswarnung"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Zertifikat ansehen"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Dieses Zertifikat wurde nicht von einer vertrauenswürdigen Stelle ausgegeben."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Name der Website stimmt nicht mit dem Namen auf dem Zertifikat überein."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Dieses Zertifikat ist abgelaufen."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Dieses Zertifikat ist noch nicht gültig."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dieses Zertifikat weist ein ungültiges Datum auf."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Dieses Zertifikat ist ungültig."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Unbekannter Zertifikatfehler"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-el/strings.xml b/packages/CaptivePortalLogin/res/values-el/strings.xml
deleted file mode 100644
index 16bf6e2..0000000
--- a/packages/CaptivePortalLogin/res/values-el/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Χρήση αυτού του δικτύου ως έχει"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Να μη χρησιμοποιείται αυτό το δίκτυο"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Σύνδεση στο δίκτυο"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Συνδεθείτε στο %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Παρουσιάζονται προβλήματα ασφάλειας στο δίκτυο στο οποίο προσπαθείτε να συνδεθείτε."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Για παράδειγμα, η σελίδα σύνδεσης ενδέχεται να μην ανήκει στον οργανισμό που εμφανίζεται."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Συνέχεια ούτως ή άλλως μέσω του προγράμματος περιήγησης"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Πληροφορίες σελίδας"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Διεύθυνση:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Προειδοποίηση ασφαλείας"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Προβολή πιστοποιητικού"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Αυτό το πιστοποιητικό δεν προέρχεται από αξιόπιστη αρχή."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Το όνομα του ιστότοπου δεν αντιστοιχεί με το όνομα στο πιστοποιητικό."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Αυτό το πιστοποιητικό έχει λήξει."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Αυτό το πιστοποιητικό δεν είναι έγκυρο ακόμα."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Αυτό το πιστοποιητικό δεν έχει έγκυρη ημερομηνία."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Αυτό το πιστοποιητικό δεν είναι έγκυρο."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Άγνωστο σφάλμα πιστοποιητικού."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-en-rAU/strings.xml b/packages/CaptivePortalLogin/res/values-en-rAU/strings.xml
deleted file mode 100644
index 2e8d1f0..0000000
--- a/packages/CaptivePortalLogin/res/values-en-rAU/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Use this network as is"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Do not use this network"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Sign in to network"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Sign in to %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"The network that you’re trying to join has security issues."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"For example, the login page might not belong to the organisation shown."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continue anyway via browser"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-en-rCA/strings.xml b/packages/CaptivePortalLogin/res/values-en-rCA/strings.xml
deleted file mode 100644
index 2e8d1f0..0000000
--- a/packages/CaptivePortalLogin/res/values-en-rCA/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Use this network as is"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Do not use this network"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Sign in to network"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Sign in to %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"The network that you’re trying to join has security issues."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"For example, the login page might not belong to the organisation shown."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continue anyway via browser"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml b/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml
deleted file mode 100644
index f940299..0000000
--- a/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Use this network as is"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Do not use this network"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Sign in to network"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Sign in to %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"The network that you’re trying to join has security issues."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"For example, the login page might not belong to the organisation shown."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continue anyway via browser"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Page info"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Address:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Security warning"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"View certificate"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"This certificate isn\'t from a trusted authority."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"The name of the site doesn\'t match the name on the certificate."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"This certificate has expired."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"This certificate isn\'t valid yet."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"This certificate has an invalid date."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"This certificate is invalid."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Unknown certificate error."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml b/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml
deleted file mode 100644
index f940299..0000000
--- a/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Use this network as is"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Do not use this network"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Sign in to network"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Sign in to %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"The network that you’re trying to join has security issues."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"For example, the login page might not belong to the organisation shown."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continue anyway via browser"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Page info"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Address:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Security warning"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"View certificate"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"This certificate isn\'t from a trusted authority."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"The name of the site doesn\'t match the name on the certificate."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"This certificate has expired."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"This certificate isn\'t valid yet."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"This certificate has an invalid date."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"This certificate is invalid."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Unknown certificate error."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-en-rXC/strings.xml b/packages/CaptivePortalLogin/res/values-en-rXC/strings.xml
deleted file mode 100644
index 6d29fd9..0000000
--- a/packages/CaptivePortalLogin/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎CaptivePortalLogin‎‏‎‎‏‎"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎Use this network as is‎‏‎‎‏‎"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‏‎‏‎‎‏‎‏‏‎Do not use this network‎‏‎‎‏‎"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‎‎‎Sign in to network‎‏‎‎‏‎"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‏‏‎‏‎Sign in to %1$s‎‏‎‎‏‎"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‎‏‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‎The network you’re trying to join has security issues.‎‏‎‎‏‎"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‎‏‎‎‎For example, the login page may not belong to the organization shown.‎‏‎‎‏‎"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‏‎‏‏‎‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‎Continue anyway via browser‎‏‎‎‏‎"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml b/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml
deleted file mode 100644
index c011664..0000000
--- a/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Usar esta red como está"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"No usar esta red"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Acceder a la red"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Acceder a %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"La red a la que intentas conectarte tiene problemas de seguridad."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Por ejemplo, es posible que la página de acceso no pertenezca a la organización que aparece."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continuar de todos modos desde el navegador"</string>
-    <string name="ok" msgid="1509280796718850364">"Aceptar"</string>
-    <string name="page_info" msgid="4048529256302257195">"Información de la página"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Dirección:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Advertencia de seguridad"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Ver certificado"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado no proviene de una autoridad confiable."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"El nombre del sitio no coincide con el nombre del certificado."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado ha expirado."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado aún no es válido."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"La fecha de este certificado no es válida."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado no es válido."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Error de certificado desconocido"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-es/strings.xml b/packages/CaptivePortalLogin/res/values-es/strings.xml
deleted file mode 100644
index 65244e7..0000000
--- a/packages/CaptivePortalLogin/res/values-es/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Utilizar esta red tal cual"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"No utilizar esta red"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Iniciar sesión en la red"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Inicia sesión en %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"La red a la que intentas unirte tiene problemas de seguridad."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Por ejemplo, es posible que la página de inicio de sesión no pertenezca a la organización mostrada."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continuar de todos modos a través del navegador"</string>
-    <string name="ok" msgid="1509280796718850364">"Aceptar"</string>
-    <string name="page_info" msgid="4048529256302257195">"Información de la página"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Dirección:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Advertencia de seguridad"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Ver certificado"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado no procede de una entidad de certificación de confianza."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"El nombre del sitio no coincide con el del certificado."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado ha caducado."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado aún no es válido."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"La fecha de este certificado no es válida."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado no es válido."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Error de certificado desconocido"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-et/strings.xml b/packages/CaptivePortalLogin/res/values-et/strings.xml
deleted file mode 100644
index e4c4c98..0000000
--- a/packages/CaptivePortalLogin/res/values-et/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Kasuta seda võrku olemasoleval kujul"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ära kasuta seda võrku"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Logi võrku sisse"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Logige sisse: %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Võrgul, millega üritate ühenduse luua, on turvaprobleeme."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Näiteks ei pruugi sisselogimisleht kuuluda kuvatavale organisatsioonile."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Jätka siiski brauseris"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Lehe teave"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Aadress:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Turvahoiatus"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Kuva sertifikaat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"See sertifikaat ei pärine usaldusväärselt asutuselt."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Saidi nimi ei vasta sertifikaadil olevale nimele."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"See sertifikaat on aegunud."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"See sertifikaat pole veel kehtiv."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Sellel sertifikaadil on kehtetu kuupäev."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"See sertifikaat on kehtetu."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Tundmatu sertifikaadiviga."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-eu/strings.xml b/packages/CaptivePortalLogin/res/values-eu/strings.xml
deleted file mode 100644
index 8925aac..0000000
--- a/packages/CaptivePortalLogin/res/values-eu/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Erabili sare hau bere horretan"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ez erabili sare hau"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Hasi saioa sarean"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Hasi saioa %1$s sarean"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Erabili nahi duzun sareak segurtasun-arazoak ditu."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Adibidez, baliteke saioa hasteko orria adierazitako erakundearena ez izatea."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Jarraitu arakatzailearen bidez, halere"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-fa/strings.xml b/packages/CaptivePortalLogin/res/values-fa/strings.xml
deleted file mode 100644
index 27b9b7f..0000000
--- a/packages/CaptivePortalLogin/res/values-fa/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"از این شبکه همانطور که هست استفاده شود"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"از این شبکه استفاده نشود"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ورود به سیستم شبکه"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"‏ورود به سیستم %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"شبکه‌ای که می‌خواهید به آن بپیوندید مشکلات امنیتی دارد."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"به عنوان مثال، صفحه ورود به سیستم ممکن است متعلق به سازمان نشان داده شده نباشد."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"در هر صورت از طریق مرورگر ادامه یابد"</string>
-    <string name="ok" msgid="1509280796718850364">"تأیید"</string>
-    <string name="page_info" msgid="4048529256302257195">"اطلاعات صفحه"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"آدرس:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"اخطار امنیتی"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"مشاهده گواهی"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"این گواهی از یک منبع مورد اطمینان صادر نشده است."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"نام سایت با نام موجود در گواهی مطابقت ندارد."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"این گواهی منقضی شده است."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"این گواهی هنوز معتبر نیست."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"تاریخ این گواهی نامعتبر است."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"این گواهی نامعتبر است."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"خطای ناشناخته در گواهی."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-fi/strings.xml b/packages/CaptivePortalLogin/res/values-fi/strings.xml
deleted file mode 100644
index 8086fbf..0000000
--- a/packages/CaptivePortalLogin/res/values-fi/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Käytä tätä verkkoa sellaisenaan"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Älä käytä tätä verkkoa"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Kirjaudu verkkoon"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Kirjaudu sisään kohteeseen %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Verkossa, johon yrität muodostaa yhteyttä, on turvallisuusongelmia."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Kirjautumissivu ei välttämättä kuulu näytetylle organisaatiolle."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Jatka silti selaimen kautta."</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Sivun tiedot"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Osoite:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Suojausvaroitus"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Näytä varmenne"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Varmenteen myöntäjä ei ole luotettava taho."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Sivuston nimi ei vastaa varmenteessa olevaa nimeä."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Varmenne ei ole enää voimassa."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Varmenne ei ole vielä voimassa."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Varmenteen päiväys ei kelpaa."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Varmenne on virheellinen."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Tuntematon varmennevirhe."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-fr-rCA/strings.xml b/packages/CaptivePortalLogin/res/values-fr-rCA/strings.xml
deleted file mode 100644
index a7525a5..0000000
--- a/packages/CaptivePortalLogin/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Utiliser ce réseau tel quel"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ne pas utiliser ce réseau"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Connectez-vous au réseau"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Connexion à %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Le réseau que vous essayez de rejoindre présente des problèmes de sécurité."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Par exemple, la page de connexion pourrait ne pas appartenir à l\'organisation représentée."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continuer quand même dans un navigateur"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-fr/strings.xml b/packages/CaptivePortalLogin/res/values-fr/strings.xml
deleted file mode 100644
index 39fc569..0000000
--- a/packages/CaptivePortalLogin/res/values-fr/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Utiliser ce réseau tel quel"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ne pas utiliser ce réseau"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Se connecter au réseau"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Se connecter à %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Le réseau que vous essayez de rejoindre présente des problèmes de sécurité."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Par exemple, la page de connexion peut ne pas appartenir à l\'organisation représentée."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continuer quand même dans le navigateur"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Infos sur la page"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adresse :"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Avertissement de sécurité"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Afficher le certificat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ce certificat provient d\'une autorité non approuvée."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Le nom du site ne correspond pas au nom indiqué dans le certificat."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Le certificat a expiré."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Ce certificat n\'est pas encore valide."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"La date de ce certificat n\'est pas valide."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Ce certificat n\'est pas valide."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Erreur : Certificat inconnu."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-gl/strings.xml b/packages/CaptivePortalLogin/res/values-gl/strings.xml
deleted file mode 100644
index 6578285..0000000
--- a/packages/CaptivePortalLogin/res/values-gl/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Utilizar esta rede tal como está"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Non utilizar esta rede"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Inicia sesión na rede"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Iniciar sesión en %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"A rede á que tentas unirte ten problemas de seguranza."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Por exemplo, é posible que a páxina de inicio de sesión non pertenza á organización que se mostra."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continuar igualmente co navegador"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-gu/strings.xml b/packages/CaptivePortalLogin/res/values-gu/strings.xml
deleted file mode 100644
index c15eca4..0000000
--- a/packages/CaptivePortalLogin/res/values-gu/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"આ નેટવર્કનો જેમનો તેમ ઉપયોગ કરો"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"આ નેટવર્કનો ઉપયોગ કરશો નહીં"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"નેટવર્ક પર સાઇન ઇન કરો"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$sમાં સઇન ઇન કરો"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"તમે જોડાવાનો પ્રયાસ કરી રહ્યાં છો તે નેટવર્કમાં સુરક્ષા સમસ્યાઓ છે."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ઉદાહરણ તરીકે, લોગિન પૃષ્ઠ દર્શાવેલ સંસ્થાનું હોઈ શકતું નથી."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"બ્રાઉઝર મારફતે કોઈપણ રીતે ચાલુ રાખો"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-hi/strings.xml b/packages/CaptivePortalLogin/res/values-hi/strings.xml
deleted file mode 100644
index d924fff..0000000
--- a/packages/CaptivePortalLogin/res/values-hi/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"इस नेटवर्क का उपयोग जैसा है वैसा ही करें"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"इस नेटवर्क का उपयोग न करें"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"नेटवर्क में साइन इन करें"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s में साइन इन करें"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"आप जिस नेटवर्क में शामिल होने का प्रयास कर रहे हैं उसमें सुरक्षा समस्‍याएं हैं."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"उदाहरण के लिए, हो सकता है कि लॉगिन पृष्‍ठ दिखाए गए संगठन से संबद्ध ना हो."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ब्राउज़र के द्वारा फिर जारी रखें"</string>
-    <string name="ok" msgid="1509280796718850364">"ठीक"</string>
-    <string name="page_info" msgid="4048529256302257195">"पृष्ठ जानकारी"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"पता:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"सुरक्षा चेतावनी"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"प्रमाणपत्र देखें"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"यह प्रमाणपत्र किसी विश्वस्त प्राधिकारी का नहीं है."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"साइट का नाम, प्रमाणपत्र के नाम से मिलान नहीं करता."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"इस प्रमाणपत्र की समय सीमा समाप्त हो गई है."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"यह प्रमाणपत्र अभी तक मान्य नहीं है."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"इस प्रमाणपत्र में एक अमान्‍य दिनांक है."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"यह प्रमाणपत्र अमान्य है."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"अज्ञात प्रमाणपत्र त्रुटि."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-hr/strings.xml b/packages/CaptivePortalLogin/res/values-hr/strings.xml
deleted file mode 100644
index 11b1dd3..0000000
--- a/packages/CaptivePortalLogin/res/values-hr/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Upotrebljavaj ovu mrežu u zatečenom stanju"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ne upotrebljavaj ovu mrežu"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Prijava na mrežu"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Prijavite se na %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Mreža kojoj se pokušavate pridružiti ima sigurnosne poteškoće."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Na primjer, stranica za prijavu možda ne pripada prikazanoj organizaciji."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Ipak nastavi putem preglednika"</string>
-    <string name="ok" msgid="1509280796718850364">"U redu"</string>
-    <string name="page_info" msgid="4048529256302257195">"Informacije o stranici"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adresa:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Upozorenje o sigurnosti"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Prikaži certifikat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ovaj certifikat ne potječe iz pouzdanog izvora."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Naziv web-lokacije ne podudara se s nazivom na certifikatu."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Ovaj je certifikat istekao."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Ovaj certifikat još nije važeći."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Ovaj certifikat ima nevažeći datum."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Ovaj certifikat nije valjan."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Nepoznata pogreška certifikata."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-hu/strings.xml b/packages/CaptivePortalLogin/res/values-hu/strings.xml
deleted file mode 100644
index 145e2ab..0000000
--- a/packages/CaptivePortalLogin/res/values-hu/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Hálózat használata jelen állapotában"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ne használja ezt a hálózatot"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Bejelentkezés a hálózatba"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Bejelentkezés a következőbe: %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Biztonsági problémák vannak azzal a hálózattal, amelyhez csatlakozni szeretne."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Például lehet, hogy a bejelentkezési oldal nem a megjelenített szervezethez tartozik."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Folytatás ennek ellenére böngészőn keresztül"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Oldaladatok"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Cím:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Biztonsági figyelmeztetés"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Tanúsítvány megtekintése"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ez a tanúsítvány nem hiteles tanúsítványkibocsátótól származik."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"A webhely neve nem egyezik a tanúsítványon lévő névvel."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"A tanúsítvány lejárt."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"A tanúsítvány még nem érvényes."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"A tanúsítvány dátuma érvénytelen."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Ez a tanúsítvány érvénytelen."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Ismeretlen tanúsítványhiba."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-hy/strings.xml b/packages/CaptivePortalLogin/res/values-hy/strings.xml
deleted file mode 100644
index a0ee862..0000000
--- a/packages/CaptivePortalLogin/res/values-hy/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Օգտագործել այս ցանցն ինչպես կա"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Չօգտագործել այս ցանցը"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Մուտք գործել ցանց"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Մուտք գործել %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Ցանցը, որին փորձում եք միանալ, անվտանգության խնդիրներ ունի:"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Օրինակ՝ մուտքի էջը կարող է ցուցադրված կազմակերպության էջը չլինել:"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Շարունակել այնուամենայնիվ դիտարկիչի միջոցով"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-in/strings.xml b/packages/CaptivePortalLogin/res/values-in/strings.xml
deleted file mode 100644
index e5b4eb4..0000000
--- a/packages/CaptivePortalLogin/res/values-in/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Gunakan jaringan ini sebagaimana adanya"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Jangan gunakan jaringan ini"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Masuk ke jaringan"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Login ke %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Jaringan yang ingin Anda masuki mengalami masalah keamanan."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Misalnya, halaman masuk mungkin bukan milik organisasi yang ditampilkan."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Tetap lanjutkan melalui browser"</string>
-    <string name="ok" msgid="1509280796718850364">"Oke"</string>
-    <string name="page_info" msgid="4048529256302257195">"Info laman"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Alamat:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Peringatan sertifikat"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Lihat sertifikat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sertifikat ini tidak berasal dari otoritas tepercaya."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Nama situs tidak cocok dengan nama pada sertifikat."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Sertifikat ini telah kedaluwarsa."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sertifikat ini belum valid."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Tanggal sertifikat ini tidak valid."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Sertifikat ini tidak valid."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Kesalahan sertifikat tak dikenal."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-is/strings.xml b/packages/CaptivePortalLogin/res/values-is/strings.xml
deleted file mode 100644
index 8fde24b..0000000
--- a/packages/CaptivePortalLogin/res/values-is/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Nota þetta net óbreytt"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ekki nota þetta net"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Skrá inn á net"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Skrá inn á %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Öryggisvandamál eru á netinu sem þú ert að reyna að tengjast."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Til dæmis getur verið að innskráningarsíðan tilheyri ekki fyrirtækinu sem birtist."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Halda samt áfram í vafra"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-it/strings.xml b/packages/CaptivePortalLogin/res/values-it/strings.xml
deleted file mode 100644
index 2cc4038..0000000
--- a/packages/CaptivePortalLogin/res/values-it/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Utilizza questa rete così com\'è"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Non utilizzare questa rete"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Accedi alla rete"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Accedi a %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"La rete a cui stai tentando di accedere presenta problemi di sicurezza."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Ad esempio, la pagina di accesso potrebbe non appartenere all\'organizzazione indicata."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continua comunque dal browser"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Info pagina"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Indirizzo:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Avviso di sicurezza"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visualizza certificato"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Questo certificato non proviene da un\'autorità attendibile."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Il nome del sito non corrisponde al nome nel certificato."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Il certificato è scaduto."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Questo certificato non è ancora valido."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Questo certificato presenta una data non valida."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Questo certificato non è valido."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Errore certificato sconosciuto."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-iw/strings.xml b/packages/CaptivePortalLogin/res/values-iw/strings.xml
deleted file mode 100644
index 527e692..0000000
--- a/packages/CaptivePortalLogin/res/values-iw/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"השתמש ברשת זו כפי שהיא"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"אל תשתמש ברשת זו"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"היכנס לרשת"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"‏כניסה אל %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"יש בעיות אבטחה ברשת שאליה אתה מנסה להתחבר."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"לדוגמה, ייתכן שדף ההתחברות אינו שייך לארגון המוצג."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"המשך בכל זאת באמצעות דפדפן"</string>
-    <string name="ok" msgid="1509280796718850364">"אישור"</string>
-    <string name="page_info" msgid="4048529256302257195">"פרטי דף"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"כתובת:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"אזהרת אבטחה"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"הצג אישור"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"אישור זה אינו מגיע מרשות אמינה."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"שם האתר לא תואם לשם באישור."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"פג תוקפו של אישור זה."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"אישור זה אינו חוקי עדיין."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"לאישור זה יש תאריך בלתי חוקי."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"אישור זה אינו חוקי."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"שגיאת אישור לא ידועה."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ja/strings.xml b/packages/CaptivePortalLogin/res/values-ja/strings.xml
deleted file mode 100644
index bcc8686..0000000
--- a/packages/CaptivePortalLogin/res/values-ja/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"このネットワークをそのまま使用する"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"このネットワークを使用しない"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ネットワークにログイン"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s にログイン"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"接続しようとしているネットワークにセキュリティの問題があります。"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"たとえば、ログインページが表示されている組織に属していない可能性があります。"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ブラウザから続行"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"ページ情報"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"アドレス:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"セキュリティ警告"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"証明書を表示"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"この証明書は信頼できる認証機関のものではありません。"</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"サイト名と証明書上の名前が一致しません。"</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"この証明書は有効期限切れです。"</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"この証明書はまだ有効ではありません。"</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"この証明書の日付は無効です。"</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"この証明書は無効です。"</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"不明な証明書エラーです。"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ka/strings.xml b/packages/CaptivePortalLogin/res/values-ka/strings.xml
deleted file mode 100644
index 1ccff12..0000000
--- a/packages/CaptivePortalLogin/res/values-ka/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"ამ ქსელის გამოყენება, როგორც არის"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"ეს ქსელი არ გამოიყენო"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ქსელში შესვლა"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s-ში შესვლა"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"ქსელს, რომელზედაც მიერთებას ცდილობთ, უსაფრთხოების პრობლემები აქვს."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"მაგალითად, სისტემაში შესვლის გვერდი შეიძლება არ ეკუთვნოდეს ნაჩვენებ ორგანიზაციას."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ბრაუზერში გაგრძელება"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-kk/strings.xml b/packages/CaptivePortalLogin/res/values-kk/strings.xml
deleted file mode 100644
index a904dea..0000000
--- a/packages/CaptivePortalLogin/res/values-kk/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Осы желіні бар күйінде пайдалану"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Осы желіні пайдаланбау"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Желіге кіру"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s жүйесіне кіру"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Қосылайын деп жатқан желіңіз қауіпсіз болуы мүмкін."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Мысалы, кіру беті көрсетілген ұйымға тиесілі болмауы мүмкін."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Бәрібір браузер арқылы жалғастыру"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-km/strings.xml b/packages/CaptivePortalLogin/res/values-km/strings.xml
deleted file mode 100644
index a0497f8..0000000
--- a/packages/CaptivePortalLogin/res/values-km/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"ប្រើ​បណ្ដាញ​នេះ​ជា"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"កុំ​ប្រើ​បណ្ដាញ​នេះ"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ចូលទៅបណ្ដាញ"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"ចូលទៅ %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"បណ្តាញដែលអ្នកកំពុងព្យាយាមចូលមានបញ្ហាសុវត្ថិភាព។"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ឧបករណ៍៖ ទំព័រចូលនេះអាចនឹងមិនមែនជាកម្មសិទ្ធិរបស់ស្ថាប័នដែលបានបង្ហាញនេះទេ។"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"យ៉ាងណាក៏ដោយនៅតែបន្តតាមរយៈកម្មវិធីរុករក"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-kn/strings.xml b/packages/CaptivePortalLogin/res/values-kn/strings.xml
deleted file mode 100644
index 3084504..0000000
--- a/packages/CaptivePortalLogin/res/values-kn/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"ಈ ನೆಟ್‌ವರ್ಕ್ ಅನ್ನು ಹೀಗೆ ಬಳಸಿ"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"ಈ ನೆಟ್‌ವರ್ಕ್ ಬಳಸಬೇಡಿ"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"ನೀವು ಸೇರಬೇಕೆಂದಿರುವ ನೆಟ್‌ವರ್ಕ್ ಭದ್ರತೆ ಸಮಸ್ಯೆಗಳನ್ನು ಹೊಂದಿದೆ."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ಉದಾಹರಣೆಗೆ, ಲಾಗಿನ್ ಪುಟವು ತೋರಿಸಲಾಗಿರುವ ಸಂಸ್ಥೆಗೆ ಸಂಬಂಧಿಸಿರುವುದಿಲ್ಲ."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ಹೇಗಾದರೂ ಬ್ರೌಸರ್ ಮೂಲಕ ಮುಂದುವರಿಸಿ"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ko/strings.xml b/packages/CaptivePortalLogin/res/values-ko/strings.xml
deleted file mode 100644
index 7a7f7e0..0000000
--- a/packages/CaptivePortalLogin/res/values-ko/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"현재 상태로 이 네트워크 사용"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"이 네트워크 사용 안함"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"네트워크에 로그인"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s에 로그인"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"가입하려는 네트워크에 보안 문제가 있습니다."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"예를 들어 로그인 페이지가 표시된 조직에 속하지 않을 수 있습니다."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"브라우저를 통해 계속하기"</string>
-    <string name="ok" msgid="1509280796718850364">"확인"</string>
-    <string name="page_info" msgid="4048529256302257195">"페이지 정보"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"주소:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"보안 경고"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"인증서 보기"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"신뢰할 수 있는 인증 기관에서 발급한 인증서가 아닙니다."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"사이트 이름이 인증서에 있는 것과 일치하지 않습니다."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"인증서가 만료되었습니다."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"인증서가 아직 유효하지 않습니다."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"인증서 날짜가 유효하지 않습니다."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"인증서가 잘못되었습니다."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"알 수 없는 인증서 오류입니다."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ky/strings.xml b/packages/CaptivePortalLogin/res/values-ky/strings.xml
deleted file mode 100644
index af81ce3..0000000
--- a/packages/CaptivePortalLogin/res/values-ky/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Бул тармак кандай болсо, ошондой колдонулсун"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Бул тармак колдонулбасын"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Тармакка кирүү"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s каттоо эсебине кириңиз"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Кошулайын деген тармагыңызда коопсуздук көйгөйлөрү бар."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Мисалы, каттоо эсебине кирүү баракчасы көрсөтүлгөн уюмга таандык эмес болушу мүмкүн."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Баары бир серепчи аркылуу улантуу"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-lo/strings.xml b/packages/CaptivePortalLogin/res/values-lo/strings.xml
deleted file mode 100644
index ee2b263..0000000
--- a/packages/CaptivePortalLogin/res/values-lo/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"​ໃຊ້​ເຄືອ​ຂ່າຍ​ນີ້​ຕາມ​ທີ່​ມັນ​ເປັນ"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"ບໍ່​ໃຊ້​ເຄືອ​ຂ່າຍ​ນີ້"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ລົງຊື່ເຂົ້າເຄືອຂ່າຍ"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"ເຂົ້າສູ່ລະບົບ %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"ເຄືອ​ຂ່າຍ​ທີ່​ທ່ານ​ກຳ​ລັງ​ເຂົ້າ​ຮ່ວມ​ມີ​ບັນ​ຫາ​ຄວາມ​ປອດ​ໄພ."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ຕົວ​ຢ່າງ, ໜ້າ​ລົງ​ຊື່​ເຂົ້າ​ໃຊ້​ອາດ​ຈະ​ບໍ່​ເປັນ​ຂອງ​ອົງ​ການ​ທີ່​ສະ​ແດງ​ຂຶ້ນ."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ແນວ​ໃດ​ກໍ່​ສືບ​ຕໍ່​ຜ່ານບ​ຣາວ​ເຊີ"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-lt/strings.xml b/packages/CaptivePortalLogin/res/values-lt/strings.xml
deleted file mode 100644
index 158f7ce..0000000
--- a/packages/CaptivePortalLogin/res/values-lt/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Naudoti šį tinklą tokį, koks yra"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Nenaudoti šio tinklo"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Prisijungti prie tinklo"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Prisijungimas prie „%1$s“"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Kilo tinklo, prie kurio bandote prisijungti, problemų."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Pavyzdžiui, prisijungimo puslapis gali nepriklausyti rodomai organizacijai."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Vis tiek tęsti naudojant naršyklę"</string>
-    <string name="ok" msgid="1509280796718850364">"Gerai"</string>
-    <string name="page_info" msgid="4048529256302257195">"Puslapio informacija"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adresas:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Saugos įspėjimas"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Žiūrėti sertifikatą"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Šį sertifikatą išdavė nepatikima įstaiga."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Svetainės pavadinimas neatitinka sertifikate nurodyto pavadinimo."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Šio sertifikato galiojimo laikas baigėsi."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Šis sertifikatas dar negalioja."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Šio sertifikato data netinkama."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Šis sertifikatas netinkamas."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Nežinoma sertifikato klaida."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-lv/strings.xml b/packages/CaptivePortalLogin/res/values-lv/strings.xml
deleted file mode 100644
index a42cb22..0000000
--- a/packages/CaptivePortalLogin/res/values-lv/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Izmantot tīklu ar pašreizējiem iestatījumiem"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Neizmantot šo tīklu"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Pierakstīties tīklā"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Pierakstieties produktā %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Tīklam, kuram mēģināt pievienoties, ir drošības problēmas."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Piemēram, pieteikšanās lapa, iespējams, nepieder norādītajai organizācijai."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Tik un tā turpināt, izmantojot pārlūkprogrammu"</string>
-    <string name="ok" msgid="1509280796718850364">"Labi"</string>
-    <string name="page_info" msgid="4048529256302257195">"Lapas informācija"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adrese:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Drošības brīdinājums"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Skatīt sertifikātu"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Šo sertifikātu nav izsniegusi uzticama iestāde."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Vietnes nosaukums neatbilst nosaukumam sertifikātā."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Šī sertifikāta derīguma termiņš ir beidzies."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Šis sertifikāts vēl nav derīgs."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Šī sertifikāta datums nav derīgs."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Šis sertifikāts nav derīgs."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Nezināma sertifikāta kļūda."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-mk/strings.xml b/packages/CaptivePortalLogin/res/values-mk/strings.xml
deleted file mode 100644
index 2ae32c8..0000000
--- a/packages/CaptivePortalLogin/res/values-mk/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Користи ја мрежата во оваа состојба"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Не ја користи мрежата"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Најавете се на мрежа"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Најавете се на %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Мрежата на која се обидувате да се придружите има проблеми со безбедноста."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"На пример, страницата за најавување може да не припаѓа на организацијата што е прикажана."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Сепак продолжи преку прелистувач"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ml/strings.xml b/packages/CaptivePortalLogin/res/values-ml/strings.xml
deleted file mode 100644
index 79551f8..0000000
--- a/packages/CaptivePortalLogin/res/values-ml/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"ഈ നെറ്റ്‌വർക്ക് മാറ്റമൊന്നും വരുത്താതെ ഉപയോഗിക്കുക"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"ഈ നെറ്റ്‌വർക്ക് ഉപയോഗിക്കരുത്"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"നെറ്റ്‌വർക്കിൽ സൈൻ ഇൻ ചെയ്യുക"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s എന്നതിലേക്ക് സൈൻ ഇൻ ചെയ്യുക"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"നിങ്ങൾ ചേരാൻ ശ്രമിക്കുന്ന നെറ്റ്‌വർക്കിൽ സുരക്ഷാ പ്രശ്‌നങ്ങളുണ്ടായിരിക്കാം."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ഉദാഹരണത്തിന്, കാണിച്ചിരിക്കുന്ന ഓർഗനൈസേഷന്റേതായിരിക്കില്ല ലോഗിൻ പേജ്."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"എന്തായാലും ബ്രൗസർ വഴി തുടരുക"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-mn/strings.xml b/packages/CaptivePortalLogin/res/values-mn/strings.xml
deleted file mode 100644
index 67670915..0000000
--- a/packages/CaptivePortalLogin/res/values-mn/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Энэ сүлжээг ашиглана уу"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Энэ сүлжээг бүү ашиглана уу"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Сүлжээнд нэвтэрнэ үү"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s-д нэвтрэх"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Таны нэгдэх гэж буй сүлжээ аюулгүй байдлын асуудалтай байна."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Жишээлбэл нэвтрэх хуудас нь харагдах байгууллагынх биш байж болзошгүй."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Ямартаа ч хөтчөөр үргэлжлүүлэх"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-mr/strings.xml b/packages/CaptivePortalLogin/res/values-mr/strings.xml
deleted file mode 100644
index fac0a08..0000000
--- a/packages/CaptivePortalLogin/res/values-mr/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"हे नेटवर्क जसेच्या तसे वापरा"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"हे नेटवर्क वापरू नका"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"नेटवर्क मध्‍ये साइन इन करा"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$sमध्‍ये साइन इन करा"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"ज्या नेटवर्कमध्‍ये आपण सामील होण्याचा प्रयत्न करीत आहात त्यात सुरक्षितता समस्या आहेत."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"उदाहरणार्थ, लॉगिन पृष्‍ठ कदाचित दर्शविलेल्या संस्थेच्या मालकीचे नसावे."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ब्राउझरद्वारे तरीही सुरु ठेवा"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ms/strings.xml b/packages/CaptivePortalLogin/res/values-ms/strings.xml
deleted file mode 100644
index aaa51c8..0000000
--- a/packages/CaptivePortalLogin/res/values-ms/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Gunakan rangkaian ini"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Jangan gunakan rangkaian ini"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Log masuk ke rangkaian"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Log masuk ke %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Rangkaian yang anda cuba sertai mempunyai isu keselamatan."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Contohnya, halaman log masuk mungkin bukan milik organisasi yang ditunjukkan."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Teruskan juga melalui penyemak imbas"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Maklumat halaman"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Alamat:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Amaran keselamatan"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Lihat sijil"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sijil ini bukan daripada pihak berkuasa yang dipercayai."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Nama tapak tidak sepadan dengan nama pada sijil."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Sijil ini telah tamat tempoh."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sijil ini belum lagi sah."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Sijil ini mempunyai tarikh yang tidak sah."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Sijil ini tidak sah."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Ralat sijil tidak diketahui."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-my/strings.xml b/packages/CaptivePortalLogin/res/values-my/strings.xml
deleted file mode 100644
index 902834b..0000000
--- a/packages/CaptivePortalLogin/res/values-my/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"ဒီကွန်ရက်ကို လက်ရှိအတိုင်း သုံးရန်"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"ဒီကွန်ရက်ကို မသုံးပါနှင့်"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ကွန်ယက်သို့ လက်မှတ်ထိုးဝင်ရန်"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s သို့ လက်မှတ်ထိုးဝင်ပါ"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"သင်ချိတ်ဆက်ရန် ကြိုးစားနေသည့် ကွန်ရက်သည် လုံခြုံရေးပြဿနာ ရှိနေသည်။"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ဥပမာ၊ ဝင်ရောက်ရန် စာမျက်နှာသည် ပြသထားသည့် အဖွဲ့အစည်းနှင့် သက်ဆိုင်မှု မရှိနိုင်ပါ။"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ဘရောက်ဇာမှတစ်ဆင့် ဆက်လုပ်ရန်"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-nb/strings.xml b/packages/CaptivePortalLogin/res/values-nb/strings.xml
deleted file mode 100644
index 29c23ed..0000000
--- a/packages/CaptivePortalLogin/res/values-nb/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Bruk dette nettverket som det er"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ikke bruk dette nettverket"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Logg på nettverk"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Logg på %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Nettverket du prøver å logge på, har sikkerhetsproblemer."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Det er for eksempel mulig at påloggingssiden kanskje ikke tilhører organisasjonen som vises."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Fortsett likevel via nettleseren"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Sideinfo"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sikkerhetsadvarsel"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vis sertifikat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sertifikatet er ikke fra en pålitelig myndighet."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Navnet på nettstedet samsvarer ikke med navnet på sertifikatet."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Sertifikatet er utløpt."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sertifikatet er ikke gyldig ennå."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dette sertifikatet har en ugyldig dato."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Dette sertifikatet er ugyldig."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Ukjent sertifikatfeil."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ne/strings.xml b/packages/CaptivePortalLogin/res/values-ne/strings.xml
deleted file mode 100644
index 87a30c0..0000000
--- a/packages/CaptivePortalLogin/res/values-ne/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"यो सञ्जाल जस्तो छ प्रयोग गर्नुहोस्"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"यो सञ्जाल प्रयोग नगर्नुहोस्"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"सञ्जालमा साइन इन गर्नुहोस्"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s मा साइन इन गर्नुहोस्"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"तपाईँले सामेल हुन प्रयास गरिरहनु भएको नेटवर्कमा सुरक्षा मुद्दाहरू छन्।"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"उदाहरणका लागि, लग इन पृष्ठ देखाइएको संस्थाको नहुन सक्छ।"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"जे भए पनि ब्राउजर मार्फत जारी राख्नुहोस्"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-nl/strings.xml b/packages/CaptivePortalLogin/res/values-nl/strings.xml
deleted file mode 100644
index 2cbca06..0000000
--- a/packages/CaptivePortalLogin/res/values-nl/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Dit netwerk in de huidige staat gebruiken"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Dit netwerk niet gebruiken"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Inloggen bij netwerk"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Inloggen bij %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Het netwerk waarmee u verbinding probeert te maken, heeft beveiligingsproblemen."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Zo hoort de weergegeven inlogpagina misschien niet bij de weergegeven organisatie."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Toch doorgaan via browser"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Pagina-informatie"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Beveiligingsmelding"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Certificaat weergeven"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Dit is geen certificaat van een vertrouwde autoriteit."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"De naam van deze site komt niet overeen met de naam op het certificaat."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Dit certificaat is verlopen."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Dit certificaat is nog niet geldig."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dit certificaat heeft een ongeldige datum."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Dit certificaat is ongeldig."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Onbekende certificaatfout."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-or/strings.xml b/packages/CaptivePortalLogin/res/values-or/strings.xml
deleted file mode 100644
index 80074c3..0000000
--- a/packages/CaptivePortalLogin/res/values-or/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"ଏହି ନେଟ୍‌ୱର୍କ ଯେପରି ଅଛି, ସେହିପରି ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"ଏହି ନେଟ୍‌ୱର୍କକୁ ବ୍ୟବହାର କରନ୍ତୁ ନାହିଁ"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ନେଟ୍‌ୱର୍କରେ ସାଇନ୍‍ ଇନ୍‍ କରନ୍ତୁ"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$sରେ ସାଇନ୍‍-ଇନ୍‍ କରନ୍ତୁ"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"ଆପଣ ଯୋଗ ଦେବାକୁ ଚେଷ୍ଟା କରୁଥିବା ନେଟ୍‌ୱର୍କର ସୁରକ୍ଷା ସମସ୍ୟା ଅଛି।"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ଉଦାହରଣସ୍ୱରୂପ, ଲଗଇନ୍‍ ପୃଷ୍ଠା ଦେଖାଯାଇଥିବା ସଂସ୍ଥାର ନହୋଇଥାଇପାରେ।"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ବ୍ରାଉଜର୍‍ ଜରିଆରେ ଯେମିତିବି ହେଉ ଜାରି ରଖନ୍ତୁ"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-pa/strings.xml b/packages/CaptivePortalLogin/res/values-pa/strings.xml
deleted file mode 100644
index 03e252f..0000000
--- a/packages/CaptivePortalLogin/res/values-pa/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"ਇਸ ਨੈੱਟਵਰਕ ਨੂੰ ਉਵੇਂ ਵਰਤੋ ਜਿਵੇਂ ਇਹ ਹੈ"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"ਇਹ ਨੈੱਟਵਰਕ ਨਾ ਵਰਤੋ"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ਨੈੱਟਵਰਕ \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"ਤੁਹਾਡੇ ਦੁਆਰਾ ਸ਼ਾਮਿਲ ਹੋਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੇ ਜਾ ਰਹੇ ਨੈੱਟਵਰਕ ਵਿੱਚ ਸੁਰੱਖਿਆ ਸੰਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ।"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ਉਦਾਹਰਣ ਵੱਜੋਂ, ਲੌਗ-ਇਨ ਪੰਨਾ ਦਿਖਾਈ ਗਈ ਸੰਸਥਾ ਨਾਲ ਸੰਬੰਧਿਤ ਨਹੀਂ ਹੋ ਸਕਦਾ ਹੈ।"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ਬ੍ਰਾਊਜ਼ਰ ਰਾਹੀਂ ਫਿਰ ਵੀ ਜਾਰੀ ਰੱਖੋ"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-pl/strings.xml b/packages/CaptivePortalLogin/res/values-pl/strings.xml
deleted file mode 100644
index 9ba066e..0000000
--- a/packages/CaptivePortalLogin/res/values-pl/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Używaj tej sieci tak jak jest"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Nie używaj tej sieci"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Zaloguj się do sieci"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Zaloguj się w aplikacji %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"W sieci, z którą próbujesz się połączyć, występują problemy z zabezpieczeniami."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Na przykład strona logowania może nie należeć do wyświetlanej organizacji."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Kontynuuj mimo to w przeglądarce"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Informacje o stronie"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Ostrzeżenie zabezpieczeń"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Wyświetl certyfikat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Certyfikat nie pochodzi od zaufanego urzędu."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Nazwa witryny nie pasuje do nazwy na certyfikacie."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Ten certyfikat wygasł."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Certyfikat nie jest jeszcze ważny."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Certyfikat ma nieprawidłową datę."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Certyfikat jest nieprawidłowy."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Nieznany błąd certyfikatu"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-pt-rBR/strings.xml b/packages/CaptivePortalLogin/res/values-pt-rBR/strings.xml
deleted file mode 100644
index 3d1064c..0000000
--- a/packages/CaptivePortalLogin/res/values-pt-rBR/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Usar esta rede como está"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Não usar esta rede"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Fazer login na rede"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Fazer login em %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continuar mesmo assim pelo navegador"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml b/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml
deleted file mode 100644
index 5bef235..0000000
--- a/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Utilizar esta rede como está"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Não utilizar esta rede"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Início de sessão na rede"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Iniciar sessão em %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"A rede à qual está a tentar aceder tem problemas de segurança."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Por exemplo, a página de início de sessão pode não pertencer à entidade apresentada."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continuar mesmo assim através do navegador"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Informações da página"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Endereço:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Aviso de segurança"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Ver certificado"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado não pertence a uma autoridade fidedigna."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"O nome do Web site não corresponde ao nome constante no certificado."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado expirou."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado ainda não é válido."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Este certificado tem uma data inválida."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado é inválido."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Erro: certificado desconhecido."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-pt/strings.xml b/packages/CaptivePortalLogin/res/values-pt/strings.xml
deleted file mode 100644
index ebe4148..0000000
--- a/packages/CaptivePortalLogin/res/values-pt/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Usar esta rede como está"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Não usar esta rede"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Fazer login na rede"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Fazer login em %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continuar mesmo assim pelo navegador"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Informações da página"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Endereço:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Aviso de segurança"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visualizar certificado"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado não é de uma autoridade confiável."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"O nome do site não corresponde ao nome no certificado."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado expirou."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado ainda não é válido."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Este certificado tem uma data inválida."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado é inválido."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Erro de certificado desconhecido."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ro/strings.xml b/packages/CaptivePortalLogin/res/values-ro/strings.xml
deleted file mode 100644
index e2e4eac..0000000
--- a/packages/CaptivePortalLogin/res/values-ro/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Utilizați această rețea în starea actuală"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Nu utilizați această rețea"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Conectați-vă la rețea"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Conectați-vă la %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Rețeaua la care încercați să vă conectați are probleme de securitate."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"De exemplu, este posibil ca pagina de conectare să nu aparțină organizației afișate."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Continuați oricum prin browser"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Informaţii pagină"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adresă:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Avertisment de securitate"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vizualizaţi certificatul"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Acest certificat nu provine de la o autoritate de încredere."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Numele acestui site nu se potriveşte cu numele de pe certificat."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Acest certificat a expirat."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Acest certificat nu este încă valid."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Acest certificat are o dată nevalidă."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Acest certificat este nevalid."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Eroare de certificat necunoscută."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ru/strings.xml b/packages/CaptivePortalLogin/res/values-ru/strings.xml
deleted file mode 100644
index c0153e6..0000000
--- a/packages/CaptivePortalLogin/res/values-ru/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Использовать эту сеть"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Не использовать эту сеть"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Регистрация в сети"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Войти: %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Сеть, к которой вы хотите подключиться, небезопасна."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Например, страница входа в аккаунт может быть фиктивной."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Игнорировать и открыть браузер"</string>
-    <string name="ok" msgid="1509280796718850364">"ОК"</string>
-    <string name="page_info" msgid="4048529256302257195">"Информация о странице"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Адрес:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Угроза безопасности"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Просмотреть сертификат"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Этот сертификат получен из ненадежных источников."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Название сайта не соответствует названию в сертификате."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Срок действия сертификата истек."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Сертификат еще не действителен."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Дата этого сертификата недействительна."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Этот сертификат недействителен."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Неизвестная ошибка сертификата."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-si/strings.xml b/packages/CaptivePortalLogin/res/values-si/strings.xml
deleted file mode 100644
index a307913..0000000
--- a/packages/CaptivePortalLogin/res/values-si/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"මෙම ජාලය ලෙසම භාවිතා කරන්න"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"මෙම ජාලය භාවිතා කරන්න එපා"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ජාලයට පුරනය වන්න"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s වෙත පුරන්න"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"ඔබ සම්බන්ධ වීමට උත්සහ කරන ජාලයේ ආරක්ෂක ගැටළු ඇත."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"උදාහරණයක් ලෙස, පුරනය වන පිටුව පෙන්වා ඇති සංවිධානයට අයිති නැති විය හැක."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"කෙසේ වුවත් බ්‍රවුසරය හරහා ඉදිරියට යන්න"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sk/strings.xml b/packages/CaptivePortalLogin/res/values-sk/strings.xml
deleted file mode 100644
index 8ba24b1..0000000
--- a/packages/CaptivePortalLogin/res/values-sk/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Použiť túto sieť tak, ako je"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Túto sieť nepoužívať"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Prihlásiť sa do siete"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Prihláste sa do služby %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Sieť, ku ktorej sa pokúšate pripojiť, má problémy so zabezpečením"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Napríklad prihlasovacia stránka nemusí patriť uvedenej organizácii."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Pokračovať pomocou prehliadača"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Informácie o stránke"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adresa:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Upozornenie zabezpečenia"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Zobraziť certifikát"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Tento certifikát nepochádza od dôveryhodnej autority."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Názov stránky sa nezhoduje s názvom uvedeným v certifikáte."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Platnosť certifikátu skončila."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Tento certifikát zatiaľ nie je platný."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Tento certifikát má neplatný dátum."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Tento certifikát je neplatný."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Neznáma chyba certifikátu."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sl/strings.xml b/packages/CaptivePortalLogin/res/values-sl/strings.xml
deleted file mode 100644
index b7d9a8a..0000000
--- a/packages/CaptivePortalLogin/res/values-sl/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Uporabljajte to omrežje, »kakršno je«"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ne uporabljajte tega omrežja"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Prijavite se v omrežje"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Prijava v %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Omrežje, ki se mu poskušate pridružiti, ima varnostne težave."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Stran za prijavo na primer morda ne pripada prikazani organizaciji."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Vseeno nadaljuj v brskalniku"</string>
-    <string name="ok" msgid="1509280796718850364">"V redu"</string>
-    <string name="page_info" msgid="4048529256302257195">"Podatki o strani"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Naslov:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Varnostno opozorilo"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Prikaži potrdilo"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Potrdila ni izdal zaupanja vreden overitelj."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Ime spletnega mesta se ne ujema z imenom na potrdilu."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Potrdilo je poteklo."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"To potrdilo še ni veljavno."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Potrdilo ima neveljaven datum."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"To potrdilo ni veljavno."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Neznana napaka potrdila."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sq/strings.xml b/packages/CaptivePortalLogin/res/values-sq/strings.xml
deleted file mode 100644
index b06da6d..0000000
--- a/packages/CaptivePortalLogin/res/values-sq/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Përdore këtë rrjet siç është"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Mos e përdor këtë rrjet"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Identifikohu në rrjet"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Identifikohu në %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Rrjeti në të cilin po përpiqesh të bashkohesh ka probleme sigurie."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"për shembull, faqja e identifikimit mund të mos i përkasë organizatës së shfaqur."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Vazhdo gjithsesi nëpërmjet shfletuesit"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sr/strings.xml b/packages/CaptivePortalLogin/res/values-sr/strings.xml
deleted file mode 100644
index 967c8ba..0000000
--- a/packages/CaptivePortalLogin/res/values-sr/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Користи ову мрежу такву каква је"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Не користи ову мрежу"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Пријави ме на мрежу"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Пријавите се у: %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Мрежа којој покушавате да се придружите има безбедносних проблема."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"На пример, страница за пријављивање можда не припада приказаној организацији."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Ипак настави преко прегледача"</string>
-    <string name="ok" msgid="1509280796718850364">"Потврди"</string>
-    <string name="page_info" msgid="4048529256302257195">"Информације о страници"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Адреса:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Безбедносно упозорење"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Прикажи сертификат"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Овај сертификат не потиче од поузданог ауторитета."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Назив сајта се не подудара са називом на сертификату."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Овај сертификат је истекао."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Овај сертификат још увек није важећи."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Датум овог сертификата је неважећи."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Овај сертификат је неважећи."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Непозната грешка сертификата."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sv/strings.xml b/packages/CaptivePortalLogin/res/values-sv/strings.xml
deleted file mode 100644
index 75356f0..0000000
--- a/packages/CaptivePortalLogin/res/values-sv/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Använd det här nätverket som det är"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Använd inte det här nätverket"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Logga in på nätverket"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Logga in på %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Nätverket du försöker ansluta till har säkerhetsproblem."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Det kan t.ex. hända att inloggningssidan inte tillhör den organisation som visas."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Fortsätt ändå via webbläsaren"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Sidinformation"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adress:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Säkerhetsvarning"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visa certifikat"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Certifikatet kommer inte från en betrodd utfärdare."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Webbplatsens namn stämmer inte med namnet på certifikatet."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Certifikatet har upphört att gälla."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Certifikatet är inte giltigt än."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Det här certifikatet har ett ogiltigt datum."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Certifikatet är ogiltigt."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Okänt certifikatfel."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sw/strings.xml b/packages/CaptivePortalLogin/res/values-sw/strings.xml
deleted file mode 100644
index feb2dde..0000000
--- a/packages/CaptivePortalLogin/res/values-sw/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Tumia mtandao huu jinsi ulivyo"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Usitumie mtandao huu"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Ingia katika mtandao"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Ingia katika akaunti ya %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Mtandao unaojaribu kujiunga nao una matatizo ya usalama."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Kwa mfano, ukurasa wa kuingia katika akaunti unaweza usiwe unamilikiwa na shirika lililoonyeshwa."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Endelea hata hivyo kupitia kivinjari"</string>
-    <string name="ok" msgid="1509280796718850364">"Sawa"</string>
-    <string name="page_info" msgid="4048529256302257195">"Maelezo ya ukurasa"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Anwani:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Ilani ya usalama"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Tazama cheti"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Cheti hiki hakijatoka kwa mamlaka inayoaminika."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Jina la tovuti halilingani na jina lililo katika cheti."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Cheti hiki kimepitwa na muda"</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Cheti bado si halali."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Cheti hiki kina tarehe batili."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Hati hii ni batili."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Hitilafu isiyojulikana ya cheti."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ta/strings.xml b/packages/CaptivePortalLogin/res/values-ta/strings.xml
deleted file mode 100644
index 6a60ed7..0000000
--- a/packages/CaptivePortalLogin/res/values-ta/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"இந்த நெட்வொர்க்கைப் பயன்படுத்து"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"இந்த நெட்வொர்க்கைப் பயன்படுத்த வேண்டாம்"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"நெட்வொர்க்கில் உள்நுழையவும்"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s இல் உள்நுழைக"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"நீங்கள் சேர முயற்சிக்கும் நெட்வொர்க்கில் பாதுகாப்புச் சிக்கல்கள் உள்ளன."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"எடுத்துக்காட்டாக, உள்நுழைவுப் பக்கமானது காட்டப்படும் அமைப்பிற்குச் சொந்தமானதாக இல்லாமல் இருக்கலாம்."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"பரவாயில்லை, உலாவி வழியாகத் தொடரவும்"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-te/strings.xml b/packages/CaptivePortalLogin/res/values-te/strings.xml
deleted file mode 100644
index c209d34..0000000
--- a/packages/CaptivePortalLogin/res/values-te/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"ఈ నెట్‌వర్క్‌ని యథావిధిగా ఉపయోగించు"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"ఈ నెట్‌వర్క్‌ని ఉపయోగించవద్దు"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"నెట్‌వర్క్‌కి సైన్ ఇన్ చేయండి"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$sకి సైన్ ఇన్ చేయండి"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"మీరు చేరడానికి ప్రయత్నిస్తున్న నెట్‌వర్క్ భద్రతా సమస్యలను కలిగి ఉంది."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ఉదాహరణకు, లాగిన్ పేజీ చూపిన సంస్థకు చెందినది కాకపోవచ్చు."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ఏదేమైనా బ్రౌజర్ ద్వారా కొనసాగించు"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-th/strings.xml b/packages/CaptivePortalLogin/res/values-th/strings.xml
deleted file mode 100644
index 11a2131..0000000
--- a/packages/CaptivePortalLogin/res/values-th/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"ใช้เครือข่ายนี้ตามที่เป็นอยู่"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"ไม่ใช้เครือข่ายนี้"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"ลงชื่อเข้าใช้เครือข่าย"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"ลงชื่อเข้าใช้ %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"เครือข่ายที่คุณพยายามเข้าร่วมมีปัญหาด้านความปลอดภัย"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"ตัวอย่างเช่น หน้าเข้าสู่ระบบอาจไม่ใช่ขององค์กรที่แสดงไว้"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"ดำเนินการต่อผ่านเบราว์เซอร์"</string>
-    <string name="ok" msgid="1509280796718850364">"ตกลง"</string>
-    <string name="page_info" msgid="4048529256302257195">"ข้อมูลหน้าเว็บ"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"ที่อยู่:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"คำเตือนเกี่ยวกับความปลอดภัย"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"ดูใบรับรอง"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"ใบรับรองนี้ไม่ได้มาจากผู้ออกที่เชื่อถือได้"</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"ชื่อไซต์ไม่ตรงกับในใบรับรอง"</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"ใบรับรองนี้หมดอายุแล้ว"</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"ใบรับรองนี้ยังใช้งานไม่ได้"</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"ใบรับรองนี้มีวันที่ไม่ถูกต้อง"</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"ใบรับรองนี้ไม่ถูกต้อง"</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"ข้อผิดพลาดใบรับรองที่ไม่รู้จัก"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-tl/strings.xml b/packages/CaptivePortalLogin/res/values-tl/strings.xml
deleted file mode 100644
index 07a2479..0000000
--- a/packages/CaptivePortalLogin/res/values-tl/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Gamitin ang network na ito nang walang pagbabago"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Huwag gamitin ang network na ito"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Mag-sign in sa network"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Mag-sign in sa %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"May mga isyu sa seguridad ang network kung saan mo sinusubukang sumali."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Halimbawa, maaaring hindi sa organisasyong ipinapakita ang page sa pag-log in."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Magpatuloy pa rin sa pamamagitan ng browser"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Impormasyon ng pahina"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Address:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Babala sa seguridad"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Tingnan ang certificate"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ang certificate ay hindi mula sa isang pinagkakatiwalaang kinauukulan."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Ang pangalan ng site ay hindi tumutugma sa pangalan sa certificate."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Nag-expire na ang certificate na ito."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Wala pang bisa ang certificate na ito."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Ang certificate ay mayroong di-wastong petsa."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Di-wasto ang certificate na ito."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Hindi kilalang error ng certificate."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-tr/strings.xml b/packages/CaptivePortalLogin/res/values-tr/strings.xml
deleted file mode 100644
index cdedd33..0000000
--- a/packages/CaptivePortalLogin/res/values-tr/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Bu ağı olduğu gibi kullan"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Bu ağı kullanma"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Ağda oturum açın"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s üzerinde oturum açın"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Katılmaya çalıştığınız ağda güvenlik sorunları var."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Örneğin, giriş sayfası, gösterilen kuruluşa ait olmayabilir."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Yine de tarayıcıyla devam et"</string>
-    <string name="ok" msgid="1509280796718850364">"Tamam"</string>
-    <string name="page_info" msgid="4048529256302257195">"Sayfa bilgileri"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Güvenlik uyarısı"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Sertifikayı görüntüle"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Bu sertifika güvenilir bir yetkiliden değil."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Sitenin adı sertifika üzerindeki adla eşleşmiyor."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Bu sertifikanın süresi dolmuş."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Bu sertifika henüz geçerli değil."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Bu sertifikanın tarihi geçersiz."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Bu sertifika geçersiz."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Bilinmeyen sertifika hatası."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-uk/strings.xml b/packages/CaptivePortalLogin/res/values-uk/strings.xml
deleted file mode 100644
index 0f4cd16..0000000
--- a/packages/CaptivePortalLogin/res/values-uk/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Використовувати цю мережу як є"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Не використовувати цю мережу"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Увійти в мережу"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Увійти в обліковий запис %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"У мережі, до якої ви намагаєтеся під’єднатись, є проблеми з безпекою."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Наприклад, сторінка входу може не належати вказаній організації."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Усе одно продовжити у веб-переглядачі"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Інфо про стор."</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Адреса:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Застереж. про небезп."</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Переглянути сертиф."</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Сертифікат видано ненадійним центром сертифікації."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Назва сайту не збігається з назвою в сертифікаті."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Термін дії сертиф. завершився."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Цей сертифікат ще не дійсний."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Цей сертифікат має недійсну дату."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Цей сертифікат недійсний."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Помилка невідомого сертифіката."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ur/strings.xml b/packages/CaptivePortalLogin/res/values-ur/strings.xml
deleted file mode 100644
index 05d8fb9..0000000
--- a/packages/CaptivePortalLogin/res/values-ur/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"جوں کا توں اس نیٹ ورک کا استعمال کریں"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"اس نیٹ ورک کا استعمال نہ کریں"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"نیٹ ورک میں سائن ان کریں"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"‏%1$s میں سائن ان کریں"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"جس نیٹ ورک میں آپ شامل ہونے کی کوشش کر رہے ہیں اس میں سیکیورٹی کے مسائل ہیں۔"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"مثال کے طور پر ہو سکتا ہے کہ لاگ ان صفحہ دکھائی گئی تنظیم سے تعلق نہ رکھتا ہو۔"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"براؤزر کے ذریعے بہرحال جاری رکھیں"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-uz/strings.xml b/packages/CaptivePortalLogin/res/values-uz/strings.xml
deleted file mode 100644
index cac96ea..0000000
--- a/packages/CaptivePortalLogin/res/values-uz/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Ushbu tarmoqdan o‘z holicha foydalanilsin"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ushbu tarmoqdan foydalanilmasin"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Tarmoqqa kirish"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"%1$s hisobiga kirish"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Siz ulanmoqchi bo‘lgan tarmoqda xavfsizlik bilan bog‘liq muammolar mavjud."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Masalan, tizimga kirish sahifasi ko‘rsatilgan tashkilotga tegishli bo‘lmasligi mumkin."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"E’tiborsiz qoldirilsin va brauzer ochilsin"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-vi/strings.xml b/packages/CaptivePortalLogin/res/values-vi/strings.xml
deleted file mode 100644
index 9c702b9..0000000
--- a/packages/CaptivePortalLogin/res/values-vi/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Sử dụng mạng này"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Không sử dụng mạng này"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Đăng nhập vào mạng"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Đăng nhập vào %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Mạng mà bạn đang cố gắng tham gia có vấn đề về bảo mật."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Ví dụ, trang đăng nhập có thể không thuộc về tổ chức được hiển thị."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Vẫn tiếp tục qua trình duyệt"</string>
-    <string name="ok" msgid="1509280796718850364">"OK"</string>
-    <string name="page_info" msgid="4048529256302257195">"Thông tin trang"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Địa chỉ:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Cảnh báo bảo mật"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Xem chứng chỉ"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Chứng chỉ này không xuất phát từ tổ chức phát hành đáng tin cậy."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Tên của trang web không khớp với tên trên chứng chỉ."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Chứng chỉ này đã hết hạn."</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Chứng chỉ này chưa hợp lệ."</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Chứng chỉ này có ngày không hợp lệ."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Chứng chỉ này không hợp lệ."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Lỗi chứng chỉ không xác định."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml b/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 70c2a08..0000000
--- a/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"直接使用此网络"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"不要使用此网络"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"登录到网络"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"登录%1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"您尝试加入的网络存在安全问题。"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"例如,登录页面可能并不属于页面上显示的单位。"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"仍然通过浏览器继续操作"</string>
-    <string name="ok" msgid="1509280796718850364">"确定"</string>
-    <string name="page_info" msgid="4048529256302257195">"网页信息"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"网址:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"安全警告"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"查看证书"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"该证书并非来自可信的授权中心。"</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"网站的名称与证书上的名称不一致。"</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"该证书已过期。"</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"该证书尚未生效。"</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"该证书的日期无效。"</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"该证书无效。"</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"未知证书错误。"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml b/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml
deleted file mode 100644
index df1c700..0000000
--- a/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"依照現況使用這個網絡"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"不要使用這個網絡"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"登入網絡"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"登入「%1$s」"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"您正在嘗試加入的網絡有安全性問題。"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"例如,登入頁面並不屬於所顯示的機構。"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"透過瀏覽器繼續"</string>
-    <string name="ok" msgid="1509280796718850364">"確定"</string>
-    <string name="page_info" msgid="4048529256302257195">"網頁資訊"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"地址:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"安全性警告"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"查看憑證"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"這個憑證並非由受信任的權威機構發出。"</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"網站名稱與憑證上的名稱不相符。"</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"這個憑證已過期。"</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"這個憑證尚未生效。"</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"此憑證的日期無效。"</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"此憑證是無效的。"</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"不明的憑證錯誤。"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml b/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml
deleted file mode 100644
index 2a2e397..0000000
--- a/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"依現況使用這個網路"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"不使用這個網路"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"登入網路"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"登入 %1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"你嘗試加入的網路有安全問題。"</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"例如,登入網頁中顯示的機構可能並非該網頁實際隸屬的機構。"</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"透過瀏覽器繼續"</string>
-    <string name="ok" msgid="1509280796718850364">"確定"</string>
-    <string name="page_info" msgid="4048529256302257195">"頁面資訊"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"位址:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"安全性警告"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"檢視憑證"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"這個憑證並非來自信任的授權單位。"</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"網站名稱與憑證上的名稱不相符。"</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"此憑證已過期"</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"這個憑證尚未生效。"</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"這個憑證的日期無效。"</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"這個憑證無效。"</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"不明的憑證錯誤。"</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values-zu/strings.xml b/packages/CaptivePortalLogin/res/values-zu/strings.xml
deleted file mode 100644
index 7943645..0000000
--- a/packages/CaptivePortalLogin/res/values-zu/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5934709770924185752">"I-CaptivePortalLogin"</string>
-    <string name="action_use_network" msgid="6076184727448466030">"Sebenzisa le nethiwekhi njengoba injalo"</string>
-    <string name="action_do_not_use_network" msgid="4577366536956516683">"Ungasebenzisi le nethiwekhi"</string>
-    <string name="action_bar_label" msgid="917235635415966620">"Ngena ngemvume kunethiwekhi"</string>
-    <string name="action_bar_title" msgid="5645564790486983117">"Ngena ngemvume ku-%1$s"</string>
-    <string name="ssl_error_warning" msgid="6653188881418638872">"Inethiwekhi ozama ukuyijoyina inezinkinga zokuvikela."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Isibonelo, ikhasi lokungena ngemvume kungenzeka lingelenhlangano ebonisiwe."</string>
-    <string name="ssl_error_continue" msgid="6492718244923937110">"Qhubeka noma kunjalo ngesiphequluli"</string>
-    <string name="ok" msgid="1509280796718850364">"KULUNGILE"</string>
-    <string name="page_info" msgid="4048529256302257195">"Ulwazi lekhasi"</string>
-    <string name="page_info_address" msgid="2222306609532903254">"Ikheli:"</string>
-    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Isexwayiso sokuvikeleka"</string>
-    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Buka isitifiketi"</string>
-    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Lesi sitifiketi asiphumi embusweni othembekile."</string>
-    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Igama lale ngosi alifani negama elikusitifiketi."</string>
-    <string name="ssl_error_expired" msgid="5739349389499575559">"Lesi sitifiketi siphelelwe yisikhathi"</string>
-    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Lesi sitifiketi asilungile okwamanje"</string>
-    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Lesi sitifiketi sinosuku olungalungile."</string>
-    <string name="ssl_error_invalid" msgid="9041704741505449967">"Lesi sitifiketi asilungile."</string>
-    <string name="ssl_error_unknown" msgid="5679243486524754571">"Iphutha lesitifiketi elingaziwa."</string>
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values/dimens.xml b/packages/CaptivePortalLogin/res/values/dimens.xml
deleted file mode 100644
index 55c1e59..0000000
--- a/packages/CaptivePortalLogin/res/values/dimens.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<resources>
-
-    <!-- Default screen margins, per the Android Design guidelines. -->
-    <dimen name="activity_horizontal_margin">16dp</dimen>
-    <dimen name="activity_vertical_margin">16dp</dimen>
-
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values/strings.xml b/packages/CaptivePortalLogin/res/values/strings.xml
deleted file mode 100644
index e9698db..0000000
--- a/packages/CaptivePortalLogin/res/values/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
-    <string name="app_name">CaptivePortalLogin</string>
-    <string name="action_use_network">Use this network as is</string>
-    <string name="action_do_not_use_network">Do not use this network</string>
-    <string name="action_bar_label">Sign in to network</string>
-    <string name="action_bar_title">Sign in to %1$s</string>
-    <string name="ssl_error_warning">The network you&#8217;re trying to join has security issues.</string>
-    <string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>
-    <string name="ssl_error_continue">Continue anyway via browser</string>
-    <string name="ssl_error_untrusted">This certificate isn\'t from a trusted authority.</string>
-    <string name="ssl_error_mismatch">The name of the site doesn\'t match the name on the certificate.</string>
-    <string name="ssl_error_expired">This certificate has expired.</string>
-    <string name="ssl_error_not_yet_valid">This certificate isn\'t valid yet.</string>
-    <string name="ssl_error_date_invalid">This certificate has an invalid date.</string>
-    <string name="ssl_error_invalid">This certificate is invalid.</string>
-    <string name="ssl_error_unknown">Unknown certificate error.</string>
-    <string name="ssl_security_warning_title">Security warning</string>
-    <string name="ssl_error_view_certificate">View certificate</string>
-    <string name="ok">OK</string>
-    <string name="page_info_address">Address:</string>
-    <string name="page_info">Page info</string>
-
-</resources>
diff --git a/packages/CaptivePortalLogin/res/values/styles.xml b/packages/CaptivePortalLogin/res/values/styles.xml
deleted file mode 100644
index f6c2339..0000000
--- a/packages/CaptivePortalLogin/res/values/styles.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<resources>
-
-    <!--
-        Base application theme, dependent on API level. This theme is replaced
-        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
-    -->
-    <style name="AppBaseTheme" parent="@android:style/Theme.DeviceDefault.Settings">
-        <!--
-            Theme customizations available in newer API levels can go in
-            res/values-vXX/styles.xml, while customizations related to
-            backward-compatibility can go here.
-        -->
-    </style>
-
-    <!-- Application theme. -->
-    <style name="AppTheme" parent="AppBaseTheme">
-        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
-    </style>
-</resources>
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
deleted file mode 100644
index 9488afb..0000000
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ /dev/null
@@ -1,706 +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.captiveportallogin;
-
-import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Application;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.net.CaptivePortal;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.Proxy;
-import android.net.Uri;
-import android.net.captiveportal.CaptivePortalProbeSpec;
-import android.net.http.SslCertificate;
-import android.net.http.SslError;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.os.SystemProperties;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.webkit.CookieManager;
-import android.webkit.SslErrorHandler;
-import android.webkit.WebChromeClient;
-import android.webkit.WebResourceRequest;
-import android.webkit.WebSettings;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Objects;
-import java.util.Random;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class CaptivePortalLoginActivity extends Activity {
-    private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName();
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
-
-    private static final int SOCKET_TIMEOUT_MS = 10000;
-    public static final String HTTP_LOCATION_HEADER_NAME = "Location";
-
-    private enum Result {
-        DISMISSED(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED),
-        UNWANTED(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_UNWANTED),
-        WANTED_AS_IS(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_WANTED_AS_IS);
-
-        final int metricsEvent;
-        Result(int metricsEvent) { this.metricsEvent = metricsEvent; }
-    };
-
-    private URL mUrl;
-    private CaptivePortalProbeSpec mProbeSpec;
-    private String mUserAgent;
-    private Network mNetwork;
-    private CaptivePortal mCaptivePortal;
-    private NetworkCallback mNetworkCallback;
-    private ConnectivityManager mCm;
-    private WifiManager mWifiManager;
-    private boolean mLaunchBrowser = false;
-    private MyWebViewClient mWebViewClient;
-    private SwipeRefreshLayout mSwipeRefreshLayout;
-    // Ensures that done() happens once exactly, handling concurrent callers with atomic operations.
-    private final AtomicBoolean isDone = new AtomicBoolean(false);
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
-        logMetricsEvent(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY);
-
-        mCm = getSystemService(ConnectivityManager.class);
-        mWifiManager = getSystemService(WifiManager.class);
-        mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
-        mUserAgent =
-                getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT);
-        mUrl = getUrl();
-        if (mUrl == null) {
-            // getUrl() failed to parse the url provided in the intent: bail out in a way that
-            // at least provides network access.
-            done(Result.WANTED_AS_IS);
-            return;
-        }
-        if (DBG) {
-            Log.d(TAG, String.format("onCreate for %s", mUrl.toString()));
-        }
-
-        final String spec = getIntent().getStringExtra(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC);
-        try {
-            mProbeSpec = CaptivePortalProbeSpec.parseSpecOrNull(spec);
-        } catch (Exception e) {
-            // Make extra sure that invalid configurations do not cause crashes
-            mProbeSpec = null;
-        }
-
-        mNetworkCallback = new NetworkCallback() {
-            @Override
-            public void onLost(Network lostNetwork) {
-                // If the network disappears while the app is up, exit.
-                if (mNetwork.equals(lostNetwork)) done(Result.UNWANTED);
-            }
-        };
-        mCm.registerNetworkCallback(new NetworkRequest.Builder().build(), mNetworkCallback);
-
-        // If the network has disappeared, exit.
-        final NetworkCapabilities networkCapabilities = mCm.getNetworkCapabilities(mNetwork);
-        if (networkCapabilities == null) {
-            finishAndRemoveTask();
-            return;
-        }
-
-        // Also initializes proxy system properties.
-        mNetwork = mNetwork.getPrivateDnsBypassingCopy();
-        mCm.bindProcessToNetwork(mNetwork);
-
-        // Proxy system properties must be initialized before setContentView is called because
-        // setContentView initializes the WebView logic which in turn reads the system properties.
-        setContentView(R.layout.activity_captive_portal_login);
-
-        getActionBar().setDisplayShowHomeEnabled(false);
-        getActionBar().setElevation(0); // remove shadow
-        getActionBar().setTitle(getHeaderTitle());
-        getActionBar().setSubtitle("");
-
-        final WebView webview = getWebview();
-        webview.clearCache(true);
-        CookieManager.getInstance().setAcceptThirdPartyCookies(webview, true);
-        WebSettings webSettings = webview.getSettings();
-        webSettings.setJavaScriptEnabled(true);
-        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
-        webSettings.setUseWideViewPort(true);
-        webSettings.setLoadWithOverviewMode(true);
-        webSettings.setSupportZoom(true);
-        webSettings.setBuiltInZoomControls(true);
-        webSettings.setDisplayZoomControls(false);
-        webSettings.setDomStorageEnabled(true);
-        mWebViewClient = new MyWebViewClient();
-        webview.setWebViewClient(mWebViewClient);
-        webview.setWebChromeClient(new MyWebChromeClient());
-        // Start initial page load so WebView finishes loading proxy settings.
-        // Actual load of mUrl is initiated by MyWebViewClient.
-        webview.loadData("", "text/html", null);
-
-        mSwipeRefreshLayout = findViewById(R.id.swipe_refresh);
-        mSwipeRefreshLayout.setOnRefreshListener(() -> {
-                webview.reload();
-                mSwipeRefreshLayout.setRefreshing(true);
-            });
-
-    }
-
-    // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties.
-    private void setWebViewProxy() {
-        // TODO: migrate to androidx WebView proxy setting API as soon as it is finalized
-        try {
-            final Field loadedApkField = Application.class.getDeclaredField("mLoadedApk");
-            final Class<?> loadedApkClass = loadedApkField.getType();
-            final Object loadedApk = loadedApkField.get(getApplication());
-            Field receiversField = loadedApkClass.getDeclaredField("mReceivers");
-            receiversField.setAccessible(true);
-            ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
-            for (Object receiverMap : receivers.values()) {
-                for (Object rec : ((ArrayMap) receiverMap).keySet()) {
-                    Class clazz = rec.getClass();
-                    if (clazz.getName().contains("ProxyChangeListener")) {
-                        Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class,
-                                Intent.class);
-                        Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
-                        onReceiveMethod.invoke(rec, getApplicationContext(), intent);
-                        Log.v(TAG, "Prompting WebView proxy reload.");
-                    }
-                }
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Exception while setting WebView proxy: " + e);
-        }
-    }
-
-    private void done(Result result) {
-        if (isDone.getAndSet(true)) {
-            // isDone was already true: done() already called
-            return;
-        }
-        if (DBG) {
-            Log.d(TAG, String.format("Result %s for %s", result.name(), mUrl.toString()));
-        }
-        logMetricsEvent(result.metricsEvent);
-        switch (result) {
-            case DISMISSED:
-                mCaptivePortal.reportCaptivePortalDismissed();
-                break;
-            case UNWANTED:
-                mCaptivePortal.ignoreNetwork();
-                break;
-            case WANTED_AS_IS:
-                mCaptivePortal.useNetwork();
-                break;
-        }
-        finishAndRemoveTask();
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        getMenuInflater().inflate(R.menu.captive_portal_login, menu);
-        return true;
-    }
-
-    @Override
-    public void onBackPressed() {
-        WebView myWebView = findViewById(R.id.webview);
-        if (myWebView.canGoBack() && mWebViewClient.allowBack()) {
-            myWebView.goBack();
-        } else {
-            super.onBackPressed();
-        }
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        final Result result;
-        final String action;
-        final int id = item.getItemId();
-        switch (id) {
-            case R.id.action_use_network:
-                result = Result.WANTED_AS_IS;
-                action = "USE_NETWORK";
-                break;
-            case R.id.action_do_not_use_network:
-                result = Result.UNWANTED;
-                action = "DO_NOT_USE_NETWORK";
-                break;
-            default:
-                return super.onOptionsItemSelected(item);
-        }
-        if (DBG) {
-            Log.d(TAG, String.format("onOptionsItemSelect %s for %s", action, mUrl.toString()));
-        }
-        done(result);
-        return true;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        final WebView webview = (WebView) findViewById(R.id.webview);
-        if (webview != null) {
-            webview.stopLoading();
-            webview.setWebViewClient(null);
-            webview.setWebChromeClient(null);
-            webview.destroy();
-        }
-        if (mNetworkCallback != null) {
-            // mNetworkCallback is not null if mUrl is not null.
-            mCm.unregisterNetworkCallback(mNetworkCallback);
-        }
-        if (mLaunchBrowser) {
-            // Give time for this network to become default. After 500ms just proceed.
-            for (int i = 0; i < 5; i++) {
-                // TODO: This misses when mNetwork underlies a VPN.
-                if (mNetwork.equals(mCm.getActiveNetwork())) break;
-                try {
-                    Thread.sleep(100);
-                } catch (InterruptedException e) {
-                }
-            }
-            final String url = mUrl.toString();
-            if (DBG) {
-                Log.d(TAG, "starting activity with intent ACTION_VIEW for " + url);
-            }
-            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
-        }
-    }
-
-    private URL getUrl() {
-        String url = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
-        if (url == null) {
-            url = mCm.getCaptivePortalServerUrl();
-        }
-        return makeURL(url);
-    }
-
-    private static URL makeURL(String url) {
-        try {
-            return new URL(url);
-        } catch (MalformedURLException e) {
-            Log.e(TAG, "Invalid URL " + url);
-        }
-        return null;
-    }
-
-    private static String host(URL url) {
-        if (url == null) {
-            return null;
-        }
-        return url.getHost();
-    }
-
-    private static String sanitizeURL(URL url) {
-        // In non-Debug build, only show host to avoid leaking private info.
-        return isDebuggable() ? Objects.toString(url) : host(url);
-    }
-
-    private static boolean isDebuggable() {
-        return SystemProperties.getInt("ro.debuggable", 0) == 1;
-    }
-
-    private void testForCaptivePortal() {
-        // TODO: reuse NetworkMonitor facilities for consistent captive portal detection.
-        new Thread(new Runnable() {
-            public void run() {
-                // Give time for captive portal to open.
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                }
-                HttpURLConnection urlConnection = null;
-                int httpResponseCode = 500;
-                String locationHeader = null;
-                try {
-                    urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl);
-                    urlConnection.setInstanceFollowRedirects(false);
-                    urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
-                    urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
-                    urlConnection.setUseCaches(false);
-                    if (mUserAgent != null) {
-                       urlConnection.setRequestProperty("User-Agent", mUserAgent);
-                    }
-                    // cannot read request header after connection
-                    String requestHeader = urlConnection.getRequestProperties().toString();
-
-                    urlConnection.getInputStream();
-                    httpResponseCode = urlConnection.getResponseCode();
-                    locationHeader = urlConnection.getHeaderField(HTTP_LOCATION_HEADER_NAME);
-                    if (DBG) {
-                        Log.d(TAG, "probe at " + mUrl +
-                                " ret=" + httpResponseCode +
-                                " request=" + requestHeader +
-                                " headers=" + urlConnection.getHeaderFields());
-                    }
-                } catch (IOException e) {
-                } finally {
-                    if (urlConnection != null) urlConnection.disconnect();
-                }
-                if (isDismissed(httpResponseCode, locationHeader, mProbeSpec)) {
-                    done(Result.DISMISSED);
-                }
-            }
-        }).start();
-    }
-
-    private static boolean isDismissed(
-            int httpResponseCode, String locationHeader, CaptivePortalProbeSpec probeSpec) {
-        return (probeSpec != null)
-                ? probeSpec.getResult(httpResponseCode, locationHeader).isSuccessful()
-                : (httpResponseCode == 204);
-    }
-
-    private class MyWebViewClient extends WebViewClient {
-        private static final String INTERNAL_ASSETS = "file:///android_asset/";
-
-        private final String mBrowserBailOutToken = Long.toString(new Random().nextLong());
-        private final String mCertificateOutToken = Long.toString(new Random().nextLong());
-        // How many Android device-independent-pixels per scaled-pixel
-        // dp/sp = (px/sp) / (px/dp) = (1/sp) / (1/dp)
-        private final float mDpPerSp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1,
-                    getResources().getDisplayMetrics()) /
-                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1,
-                    getResources().getDisplayMetrics());
-        private int mPagesLoaded;
-        private String mMainFrameUrl;
-
-        // If we haven't finished cleaning up the history, don't allow going back.
-        public boolean allowBack() {
-            return mPagesLoaded > 1;
-        }
-
-        private String mSslErrorTitle = null;
-        private SslErrorHandler mSslErrorHandler = null;
-        private SslError mSslError = null;
-
-        @Override
-        public void onPageStarted(WebView view, String urlString, Bitmap favicon) {
-            if (urlString.contains(mBrowserBailOutToken)) {
-                mLaunchBrowser = true;
-                done(Result.WANTED_AS_IS);
-                return;
-            }
-            // The first page load is used only to cause the WebView to
-            // fetch the proxy settings.  Don't update the URL bar, and
-            // don't check if the captive portal is still there.
-            if (mPagesLoaded == 0) {
-                return;
-            }
-            final URL url = makeURL(urlString);
-            Log.d(TAG, "onPageStarted: " + sanitizeURL(url));
-            // For internally generated pages, leave URL bar listing prior URL as this is the URL
-            // the page refers to.
-            if (!urlString.startsWith(INTERNAL_ASSETS)) {
-                String subtitle = (url != null) ? getHeaderSubtitle(url) : urlString;
-                getActionBar().setSubtitle(subtitle);
-            }
-            getProgressBar().setVisibility(View.VISIBLE);
-            testForCaptivePortal();
-        }
-
-        @Override
-        public void onPageFinished(WebView view, String url) {
-            mPagesLoaded++;
-            getProgressBar().setVisibility(View.INVISIBLE);
-            mSwipeRefreshLayout.setRefreshing(false);
-            if (mPagesLoaded == 1) {
-                // Now that WebView has loaded at least one page we know it has read in the proxy
-                // settings.  Now prompt the WebView read the Network-specific proxy settings.
-                setWebViewProxy();
-                // Load the real page.
-                view.loadUrl(mUrl.toString());
-                return;
-            } else if (mPagesLoaded == 2) {
-                // Prevent going back to empty first page.
-                // Fix for missing focus, see b/62449959 for details. Remove it once we get a
-                // newer version of WebView (60.x.y).
-                view.requestFocus();
-                view.clearHistory();
-            }
-            testForCaptivePortal();
-        }
-
-        // Convert Android scaled-pixels (sp) to HTML size.
-        private String sp(int sp) {
-            // Convert sp to dp's.
-            float dp = sp * mDpPerSp;
-            // Apply a scale factor to make things look right.
-            dp *= 1.3;
-            // Convert dp's to HTML size.
-            // HTML px's are scaled just like dp's, so just add "px" suffix.
-            return Integer.toString((int)dp) + "px";
-        }
-
-        // Check if webview is trying to load the main frame and record its url.
-        @Override
-        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
-            if (request.isForMainFrame()) {
-                mMainFrameUrl = request.getUrl().toString();
-            }
-            return false;
-        }
-
-        // A web page consisting of a large broken lock icon to indicate SSL failure.
-
-        @Override
-        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-            final URL errorUrl = makeURL(error.getUrl());
-            final URL mainFrameUrl = makeURL(mMainFrameUrl);
-            Log.d(TAG, String.format("SSL error: %s, url: %s, certificate: %s",
-                    sslErrorName(error), sanitizeURL(errorUrl), error.getCertificate()));
-            if (errorUrl == null
-                    // Ignore SSL errors from resources by comparing the main frame url with SSL
-                    // error url.
-                    || !errorUrl.equals(mainFrameUrl)) {
-                Log.d(TAG, "onReceivedSslError: mMainFrameUrl = " + mMainFrameUrl);
-                handler.cancel();
-                return;
-            }
-            logMetricsEvent(MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR);
-            final String sslErrorPage = makeSslErrorPage();
-            view.loadDataWithBaseURL(INTERNAL_ASSETS, sslErrorPage, "text/HTML", "UTF-8", null);
-            mSslErrorTitle = view.getTitle() == null ? "" : view.getTitle();
-            mSslErrorHandler = handler;
-            mSslError = error;
-        }
-
-        private String makeSslErrorPage() {
-            final String warningMsg = getString(R.string.ssl_error_warning);
-            final String exampleMsg = getString(R.string.ssl_error_example);
-            final String continueMsg = getString(R.string.ssl_error_continue);
-            final String certificateMsg = getString(R.string.ssl_error_view_certificate);
-            return String.join("\n",
-                    "<html>",
-                    "<head>",
-                    "  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
-                    "  <style>",
-                    "    body {",
-                    "      background-color:#fafafa;",
-                    "      margin:auto;",
-                    "      width:80%;",
-                    "      margin-top: 96px",
-                    "    }",
-                    "    img {",
-                    "      height:48px;",
-                    "      width:48px;",
-                    "    }",
-                    "    div.warn {",
-                    "      font-size:" + sp(16) + ";",
-                    "      line-height:1.28;",
-                    "      margin-top:16px;",
-                    "      opacity:0.87;",
-                    "    }",
-                    "    div.example {",
-                    "      font-size:" + sp(14) + ";",
-                    "      line-height:1.21905;",
-                    "      margin-top:16px;",
-                    "      opacity:0.54;",
-                    "    }",
-                    "    a {",
-                    "      color:#4285F4;",
-                    "      display:inline-block;",
-                    "      font-size:" + sp(14) + ";",
-                    "      font-weight:bold;",
-                    "      height:48px;",
-                    "      margin-top:24px;",
-                    "      text-decoration:none;",
-                    "      text-transform:uppercase;",
-                    "    }",
-                    "    a.certificate {",
-                    "      margin-top:0px;",
-                    "    }",
-                    "  </style>",
-                    "</head>",
-                    "<body>",
-                    "  <p><img src=quantum_ic_warning_amber_96.png><br>",
-                    "  <div class=warn>" + warningMsg + "</div>",
-                    "  <div class=example>" + exampleMsg + "</div>",
-                    "  <a href=" + mBrowserBailOutToken + ">" + continueMsg + "</a><br>",
-                    "  <a class=certificate href=" + mCertificateOutToken + ">" + certificateMsg +
-                            "</a>",
-                    "</body>",
-                    "</html>");
-        }
-
-        @Override
-        public boolean shouldOverrideUrlLoading (WebView view, String url) {
-            if (url.startsWith("tel:")) {
-                startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url)));
-                return true;
-            }
-            if (url.contains(mCertificateOutToken) && mSslError != null) {
-                showSslAlertDialog(mSslErrorHandler, mSslError, mSslErrorTitle);
-                return true;
-            }
-            return false;
-        }
-        private void showSslAlertDialog(SslErrorHandler handler, SslError error, String title) {
-            final LayoutInflater factory = LayoutInflater.from(CaptivePortalLoginActivity.this);
-            final View sslWarningView = factory.inflate(R.layout.ssl_warning, null);
-
-            // Set Security certificate
-            setViewSecurityCertificate(sslWarningView.findViewById(R.id.certificate_layout), error);
-            ((TextView) sslWarningView.findViewById(R.id.ssl_error_type))
-                    .setText(sslErrorName(error));
-            ((TextView) sslWarningView.findViewById(R.id.title)).setText(mSslErrorTitle);
-            ((TextView) sslWarningView.findViewById(R.id.address)).setText(error.getUrl());
-
-            AlertDialog sslAlertDialog = new AlertDialog.Builder(CaptivePortalLoginActivity.this)
-                    .setTitle(R.string.ssl_security_warning_title)
-                    .setView(sslWarningView)
-                    .setPositiveButton(R.string.ok, (DialogInterface dialog, int whichButton) -> {
-                        // handler.cancel is called via OnCancelListener.
-                        dialog.cancel();
-                    })
-                    .setOnCancelListener((DialogInterface dialogInterface) -> handler.cancel())
-                    .create();
-            sslAlertDialog.show();
-        }
-
-        private void setViewSecurityCertificate(LinearLayout certificateLayout, SslError error) {
-            ((TextView) certificateLayout.findViewById(R.id.ssl_error_msg))
-                    .setText(sslErrorMessage(error));
-            SslCertificate cert = error.getCertificate();
-            // TODO: call the method directly once inflateCertificateView is @SystemApi
-            try {
-                final View certificateView = (View) SslCertificate.class.getMethod(
-                        "inflateCertificateView", Context.class)
-                        .invoke(cert, CaptivePortalLoginActivity.this);
-                certificateLayout.addView(certificateView);
-            } catch (ReflectiveOperationException | SecurityException e) {
-                Log.e(TAG, "Could not create certificate view", e);
-            }
-        }
-    }
-
-    private class MyWebChromeClient extends WebChromeClient {
-        @Override
-        public void onProgressChanged(WebView view, int newProgress) {
-            getProgressBar().setProgress(newProgress);
-        }
-    }
-
-    private ProgressBar getProgressBar() {
-        return findViewById(R.id.progress_bar);
-    }
-
-    private WebView getWebview() {
-        return findViewById(R.id.webview);
-    }
-
-    private String getHeaderTitle() {
-        NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork);
-        final String ssid = getSsid();
-        if (TextUtils.isEmpty(ssid)
-                || nc == null || !nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
-            return getString(R.string.action_bar_label);
-        }
-        return getString(R.string.action_bar_title, ssid);
-    }
-
-    // TODO: remove once SSID is obtained from NetworkCapabilities
-    private String getSsid() {
-        if (mWifiManager == null) {
-            return null;
-        }
-        final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
-        return removeDoubleQuotes(wifiInfo.getSSID());
-    }
-
-    private static String removeDoubleQuotes(String string) {
-        if (string == null) return null;
-        final int length = string.length();
-        if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) {
-            return string.substring(1, length - 1);
-        }
-        return string;
-    }
-
-    private String getHeaderSubtitle(URL url) {
-        String host = host(url);
-        final String https = "https";
-        if (https.equals(url.getProtocol())) {
-            return https + "://" + host;
-        }
-        return host;
-    }
-
-    private void logMetricsEvent(int event) {
-        mCaptivePortal.logEvent(event, getPackageName());
-    }
-
-    private static final SparseArray<String> SSL_ERRORS = new SparseArray<>();
-    static {
-        SSL_ERRORS.put(SslError.SSL_NOTYETVALID,  "SSL_NOTYETVALID");
-        SSL_ERRORS.put(SslError.SSL_EXPIRED,      "SSL_EXPIRED");
-        SSL_ERRORS.put(SslError.SSL_IDMISMATCH,   "SSL_IDMISMATCH");
-        SSL_ERRORS.put(SslError.SSL_UNTRUSTED,    "SSL_UNTRUSTED");
-        SSL_ERRORS.put(SslError.SSL_DATE_INVALID, "SSL_DATE_INVALID");
-        SSL_ERRORS.put(SslError.SSL_INVALID,      "SSL_INVALID");
-    }
-
-    private static String sslErrorName(SslError error) {
-        return SSL_ERRORS.get(error.getPrimaryError(), "UNKNOWN");
-    }
-
-    private static final SparseArray<Integer> SSL_ERROR_MSGS = new SparseArray<>();
-    static {
-        SSL_ERROR_MSGS.put(SslError.SSL_NOTYETVALID,  R.string.ssl_error_not_yet_valid);
-        SSL_ERROR_MSGS.put(SslError.SSL_EXPIRED,      R.string.ssl_error_expired);
-        SSL_ERROR_MSGS.put(SslError.SSL_IDMISMATCH,   R.string.ssl_error_mismatch);
-        SSL_ERROR_MSGS.put(SslError.SSL_UNTRUSTED,    R.string.ssl_error_untrusted);
-        SSL_ERROR_MSGS.put(SslError.SSL_DATE_INVALID, R.string.ssl_error_date_invalid);
-        SSL_ERROR_MSGS.put(SslError.SSL_INVALID,      R.string.ssl_error_invalid);
-    }
-
-    private static Integer sslErrorMessage(SslError error) {
-        return SSL_ERROR_MSGS.get(error.getPrimaryError(), R.string.ssl_error_unknown);
-    }
-}
diff --git a/packages/CtsShim/build/shim/AndroidManifest.xml b/packages/CtsShim/build/shim/AndroidManifest.xml
index 9b813ac..3e546f1e 100644
--- a/packages/CtsShim/build/shim/AndroidManifest.xml
+++ b/packages/CtsShim/build/shim/AndroidManifest.xml
@@ -20,7 +20,7 @@
     package="com.android.cts.ctsshim" >
 
     <uses-sdk android:minSdkVersion="24"
-        android:targetSdkVersion="24" />
+        android:targetSdkVersion="28" />
 
     <restrict-update
         android:hash="__CAN_NOT_BE_UPDATED__" />
diff --git a/packages/CtsShim/build/shim_priv/AndroidManifest.xml b/packages/CtsShim/build/shim_priv/AndroidManifest.xml
index 9bf454c..9ab12c9 100644
--- a/packages/CtsShim/build/shim_priv/AndroidManifest.xml
+++ b/packages/CtsShim/build/shim_priv/AndroidManifest.xml
@@ -20,7 +20,7 @@
     package="com.android.cts.priv.ctsshim" >
 
     <uses-sdk android:minSdkVersion="24"
-        android:targetSdkVersion="24" />
+        android:targetSdkVersion="28" />
 
     <restrict-update
         android:hash="__HASH__" />
diff --git a/packages/CtsShim/build/shim_priv_upgrade/AndroidManifest.xml b/packages/CtsShim/build/shim_priv_upgrade/AndroidManifest.xml
index 023e93e..2354061 100644
--- a/packages/CtsShim/build/shim_priv_upgrade/AndroidManifest.xml
+++ b/packages/CtsShim/build/shim_priv_upgrade/AndroidManifest.xml
@@ -20,7 +20,7 @@
     package="com.android.cts.priv.ctsshim" >
 
     <uses-sdk android:minSdkVersion="24"
-        android:targetSdkVersion="24" />
+        android:targetSdkVersion="28" />
 
     <application
         android:hasCode="false"
diff --git a/packages/EasterEgg/src/com/android/egg/paint/BrushPropertyDrawable.kt b/packages/EasterEgg/src/com/android/egg/paint/BrushPropertyDrawable.kt
index 4a02ee6..f10a3ac 100644
--- a/packages/EasterEgg/src/com/android/egg/paint/BrushPropertyDrawable.kt
+++ b/packages/EasterEgg/src/com/android/egg/paint/BrushPropertyDrawable.kt
@@ -62,8 +62,8 @@
         return _size
     }
 
-    override fun draw(c: Canvas?) {
-        c?.let {
+    override fun draw(c: Canvas) {
+        c.let {
             val w = bounds.width().toFloat()
             val h = bounds.height().toFloat()
             val inset = _size / 12 // 2dp in a 24x24 icon
@@ -90,4 +90,4 @@
         //
     }
 
-}
\ No newline at end of file
+}
diff --git a/packages/EasterEgg/src/com/android/egg/paint/CutoutAvoidingToolbar.kt b/packages/EasterEgg/src/com/android/egg/paint/CutoutAvoidingToolbar.kt
index 164fc5a..9855565 100644
--- a/packages/EasterEgg/src/com/android/egg/paint/CutoutAvoidingToolbar.kt
+++ b/packages/EasterEgg/src/com/android/egg/paint/CutoutAvoidingToolbar.kt
@@ -26,15 +26,16 @@
     private var _insets: WindowInsets? = null
 
     constructor(context: Context) : super(context) {
-        init(null, 0)
     }
 
     constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
-        init(attrs, 0)
     }
 
-    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
-        init(attrs, defStyle)
+    constructor(
+        context: Context,
+        attrs: AttributeSet,
+        defStyle: Int
+    ) : super(context, attrs, defStyle) {
     }
 
     override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
@@ -81,8 +82,4 @@
             requestLayout()
         }
     }
-
-    private fun init(attrs: AttributeSet?, defStyle: Int) {
-    }
-
 }
diff --git a/packages/EasterEgg/src/com/android/egg/paint/Painting.kt b/packages/EasterEgg/src/com/android/egg/paint/Painting.kt
index a4a3d3d..9e55d2c 100644
--- a/packages/EasterEgg/src/com/android/egg/paint/Painting.kt
+++ b/packages/EasterEgg/src/com/android/egg/paint/Painting.kt
@@ -17,7 +17,6 @@
 package com.android.egg.paint
 
 import android.content.Context
-import android.content.res.Resources
 import android.graphics.*
 import android.provider.Settings
 import android.util.AttributeSet
@@ -26,7 +25,6 @@
 import android.view.View
 import android.view.WindowInsets
 import java.util.concurrent.TimeUnit
-import android.util.Log
 import android.provider.Settings.System
 
 import org.json.JSONObject
@@ -86,11 +84,11 @@
         }
 
     var bitmap: Bitmap? = null
-    var paperColor : Int = 0xFFFFFFFF.toInt()
+    var paperColor: Int = 0xFFFFFFFF.toInt()
 
     private var _paintCanvas: Canvas? = null
     private val _bitmapLock = Object()
-    
+
     private var _drawPaint = Paint(Paint.ANTI_ALIAS_FLAG)
     private var _lastX = 0f
     private var _lastY = 0f
@@ -113,7 +111,9 @@
                         FADE_TO_BLACK_CF
 
                 synchronized(_bitmapLock) {
-                    c.drawBitmap(bitmap, 0f, 0f, pt)
+                    bitmap?.let {
+                        c.drawBitmap(bitmap!!, 0f, 0f, pt)
+                    }
                 }
                 invalidate()
             }
@@ -122,18 +122,22 @@
     }
 
     constructor(context: Context) : super(context) {
-        init(null, 0)
+        init()
     }
 
     constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
-        init(attrs, 0)
+        init()
     }
 
-    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
-        init(attrs, defStyle)
+    constructor(
+        context: Context,
+        attrs: AttributeSet,
+        defStyle: Int
+    ) : super(context, attrs, defStyle) {
+        init()
     }
 
-    private fun init(attrs: AttributeSet?, defStyle: Int) {
+    private fun init() {
         loadDevicePressureData()
     }
 
@@ -264,7 +268,7 @@
         super.onDraw(canvas)
 
         bitmap?.let {
-            canvas.drawBitmap(bitmap, 0f, 0f, _drawPaint);
+            canvas.drawBitmap(bitmap!!, 0f, 0f, _drawPaint)
         }
     }
 
@@ -330,14 +334,14 @@
                 }
                 if (bits.width != oldBits.height || bits.height != oldBits.width) {
                     matrix.postScale(
-                            bits.width.toFloat()/oldBits.height,
-                            bits.height.toFloat()/oldBits.width)
+                            bits.width.toFloat() / oldBits.height,
+                            bits.height.toFloat() / oldBits.width)
                 }
-                c.matrix = matrix
+                c.setMatrix(matrix)
             }
             // paint the old artwork atop the new
             c.drawBitmap(oldBits, 0f, 0f, _drawPaint)
-            c.matrix = Matrix()
+            c.setMatrix(Matrix())
         } else {
             c.drawColor(paperColor)
         }
@@ -350,9 +354,10 @@
         val invertPaint = Paint()
         invertPaint.colorFilter = INVERT_CF
         synchronized(_bitmapLock) {
-            _paintCanvas?.drawBitmap(bitmap, 0f, 0f, invertPaint)
+            bitmap?.let {
+                _paintCanvas?.drawBitmap(bitmap!!, 0f, 0f, invertPaint)
+            }
         }
         invalidate()
     }
 }
-
diff --git a/packages/EasterEgg/src/com/android/egg/paint/ToolbarView.kt b/packages/EasterEgg/src/com/android/egg/paint/ToolbarView.kt
index 86b11e7..460fa3a 100644
--- a/packages/EasterEgg/src/com/android/egg/paint/ToolbarView.kt
+++ b/packages/EasterEgg/src/com/android/egg/paint/ToolbarView.kt
@@ -17,19 +17,10 @@
 package com.android.egg.paint
 
 import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.Rect
-import android.graphics.drawable.Drawable
-import android.text.TextPaint
-import android.transition.ChangeBounds
 import android.transition.Transition
 import android.transition.TransitionListenerAdapter
-import android.transition.TransitionManager
 import android.util.AttributeSet
 import android.view.*
-import android.view.animation.OvershootInterpolator
 import android.widget.FrameLayout
 
 class ToolbarView : FrameLayout {
@@ -44,15 +35,16 @@
     }
 
     constructor(context: Context) : super(context) {
-        init(null, 0)
     }
 
     constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
-        init(attrs, 0)
     }
 
-    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
-        init(attrs, defStyle)
+    constructor(
+        context: Context,
+        attrs: AttributeSet,
+        defStyle: Int
+    ) : super(context, attrs, defStyle) {
     }
 
     override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets {
@@ -70,8 +62,4 @@
 
         return super.onApplyWindowInsets(insets)
     }
-
-    private fun init(attrs: AttributeSet?, defStyle: Int) {
-    }
-
 }
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
deleted file mode 100644
index 45e557c..0000000
--- a/packages/ExtServices/AndroidManifest.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    package="android.ext.services"
-    android:versionCode="1"
-    android:versionName="1"
-    coreApp="true">
-
-    <uses-permission android:name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" />
-
-    <application android:label="@string/app_name"
-        android:defaultToDeviceProtectedStorage="true"
-        android:directBootAware="true">
-
-        <service android:name=".storage.CacheQuotaServiceImpl"
-             android:permission="android.permission.BIND_CACHE_QUOTA_SERVICE">
-            <intent-filter>
-                <action android:name="android.app.usage.CacheQuotaService" />
-            </intent-filter>
-        </service>
-
-        <service android:name=".resolver.LRResolverRankerService"
-                 android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE"
-                 android:priority="-1" >
-            <intent-filter>
-                <action android:name="android.service.resolver.ResolverRankerService" />
-            </intent-filter>
-        </service>
-
-        <service android:name=".notification.Assistant"
-                 android:label="@string/notification_assistant"
-                 android:permission="android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"
-                 android:exported="true">
-            <intent-filter>
-                <action android:name="android.service.notification.NotificationAssistantService" />
-            </intent-filter>
-        </service>
-
-        <service android:name=".autofill.AutofillFieldClassificationServiceImpl"
-             android:permission="android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE">
-            <intent-filter>
-                <action android:name="android.service.autofill.AutofillFieldClassificationService" />
-            </intent-filter>
-            <meta-data
-                android:name="android.autofill.field_classification.default_algorithm"
-                android:resource="@string/autofill_field_classification_default_algorithm" />
-            <meta-data
-                android:name="android.autofill.field_classification.available_algorithms"
-                android:resource="@array/autofill_field_classification_available_algorithms" />
-        </service>
-
-        <library android:name="android.ext.services"/>
-    </application>
-
-</manifest>
diff --git a/packages/ExtServices/NOTICE b/packages/ExtServices/NOTICE
deleted file mode 100644
index c5b1efa..0000000
--- a/packages/ExtServices/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
-   Copyright (c) 2005-2008, The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
diff --git a/packages/ExtServices/proguard.proguard b/packages/ExtServices/proguard.proguard
deleted file mode 100644
index e5dfbe1..0000000
--- a/packages/ExtServices/proguard.proguard
+++ /dev/null
@@ -1,7 +0,0 @@
--keepparameternames
--keepattributes Exceptions,InnerClasses,Signature,Deprecated,
-                SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
-
--keep public class * {
-    public protected *;
-}
diff --git a/packages/ExtServices/res/values/strings.xml b/packages/ExtServices/res/values/strings.xml
deleted file mode 100644
index 72647ab..0000000
--- a/packages/ExtServices/res/values/strings.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name">Android Services Library</string>
-
-    <string name="notification_assistant">Notification Assistant</string>
-    <string name="prompt_block_reason">Too many dismissals:views</string>
-
-    <string name="autofill_field_classification_default_algorithm">EDIT_DISTANCE</string>
-    <string-array name="autofill_field_classification_available_algorithms">
-        <item>EDIT_DISTANCE</item>
-    </string-array>
-</resources>
diff --git a/packages/ExtServices/src/android/ext/services/Version.java b/packages/ExtServices/src/android/ext/services/Version.java
deleted file mode 100644
index 026cccd..0000000
--- a/packages/ExtServices/src/android/ext/services/Version.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.ext.services;
-
-/**
- * Class that provides the version of the library.
- */
-public final class Version {
-
-    private Version() {
-        /* do nothing - hide constructor */
-    }
-
-    /**
-     * Gets the version of the library.
-     *
-     * @return The version.
-     */
-    public static int getVersionCode() {
-        return 1;
-    }
-}
\ No newline at end of file
diff --git a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
deleted file mode 100644
index 9ba7e09..0000000
--- a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.ext.services.autofill;
-
-import static android.ext.services.autofill.EditDistanceScorer.DEFAULT_ALGORITHM;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.service.autofill.AutofillFieldClassificationService;
-import android.util.Log;
-import android.view.autofill.AutofillValue;
-
-import com.android.internal.util.ArrayUtils;
-
-import java.util.List;
-
-public class AutofillFieldClassificationServiceImpl extends AutofillFieldClassificationService {
-
-    private static final String TAG = "AutofillFieldClassificationServiceImpl";
-
-    @Nullable
-    @Override
-    public float[][] onGetScores(@Nullable String algorithmName,
-            @Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues,
-            @NonNull List<String> userDataValues) {
-        if (ArrayUtils.isEmpty(actualValues) || ArrayUtils.isEmpty(userDataValues)) {
-            Log.w(TAG, "getScores(): empty currentvalues (" + actualValues + ") or userValues ("
-                    + userDataValues + ")");
-            return null;
-        }
-        if (algorithmName != null && !algorithmName.equals(DEFAULT_ALGORITHM)) {
-            Log.w(TAG, "Ignoring invalid algorithm (" + algorithmName + ") and using "
-                    + DEFAULT_ALGORITHM + " instead");
-        }
-
-        return EditDistanceScorer.getScores(actualValues, userDataValues);
-    }
-}
diff --git a/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java
deleted file mode 100644
index 302b160..0000000
--- a/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java
+++ /dev/null
@@ -1,148 +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.ext.services.autofill;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.Log;
-import android.view.autofill.AutofillValue;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.List;
-
-final class EditDistanceScorer {
-
-    private static final String TAG = "EditDistanceScorer";
-
-    // TODO(b/70291841): STOPSHIP - set to false before launching
-    private static final boolean DEBUG = true;
-
-    static final String DEFAULT_ALGORITHM = "EDIT_DISTANCE";
-
-    /**
-     * Gets the field classification score of 2 values based on the edit distance between them.
-     *
-     * <p>The score is defined as: @(max_length - edit_distance) / max_length
-     */
-    @VisibleForTesting
-    static float getScore(@Nullable AutofillValue actualValue, @Nullable String userDataValue) {
-        if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0;
-
-        final String actualValueText = actualValue.getTextValue().toString();
-        final int actualValueLength = actualValueText.length();
-        final int userDatalength = userDataValue.length();
-        if (userDatalength == 0) {
-            return (actualValueLength == 0) ? 1 : 0;
-        }
-
-        final int distance = editDistance(actualValueText.toLowerCase(),
-                userDataValue.toLowerCase());
-        final int maxLength = Math.max(actualValueLength, userDatalength);
-        return ((float) maxLength - distance) / maxLength;
-    }
-
-    /**
-     * Computes the edit distance (number of insertions, deletions or substitutions to edit one
-     * string into the other) between two strings. In particular, this will compute the Levenshtein
-     * distance.
-     *
-     * <p>See http://en.wikipedia.org/wiki/Levenshtein_distance for details.
-     *
-     * @param s the first string to compare
-     * @param t the second string to compare
-     * @return the edit distance between the two strings
-     */
-    // Note: copied verbatim from com.android.tools.lint.detector.api.LintUtils.java
-    public static int editDistance(@NonNull String s, @NonNull String t) {
-        return editDistance(s, t, Integer.MAX_VALUE);
-    }
-
-    /**
-     * Computes the edit distance (number of insertions, deletions or substitutions to edit one
-     * string into the other) between two strings. In particular, this will compute the Levenshtein
-     * distance.
-     *
-     * <p>See http://en.wikipedia.org/wiki/Levenshtein_distance for details.
-     *
-     * @param s the first string to compare
-     * @param t the second string to compare
-     * @param max the maximum edit distance that we care about; if for example the string length
-     *     delta is greater than this we don't bother computing the exact edit distance since the
-     *     caller has indicated they're not interested in the result
-     * @return the edit distance between the two strings, or some other value greater than that if
-     *     the edit distance is at least as big as the {@code max} parameter
-     */
-    // Note: copied verbatim from com.android.tools.lint.detector.api.LintUtils.java
-    private static int editDistance(@NonNull String s, @NonNull String t, int max) {
-        if (s.equals(t)) {
-            return 0;
-        }
-
-        if (Math.abs(s.length() - t.length()) > max) {
-            // The string lengths differ more than the allowed edit distance;
-            // no point in even attempting to compute the edit distance (requires
-            // O(n*m) storage and O(n*m) speed, where n and m are the string lengths)
-            return Integer.MAX_VALUE;
-        }
-
-        int m = s.length();
-        int n = t.length();
-        int[][] d = new int[m + 1][n + 1];
-        for (int i = 0; i <= m; i++) {
-            d[i][0] = i;
-        }
-        for (int j = 0; j <= n; j++) {
-            d[0][j] = j;
-        }
-        for (int j = 1; j <= n; j++) {
-            for (int i = 1; i <= m; i++) {
-                if (s.charAt(i - 1) == t.charAt(j - 1)) {
-                    d[i][j] = d[i - 1][j - 1];
-                } else {
-                    int deletion = d[i - 1][j] + 1;
-                    int insertion = d[i][j - 1] + 1;
-                    int substitution = d[i - 1][j - 1] + 1;
-                    d[i][j] = Math.min(deletion, Math.min(insertion, substitution));
-                }
-            }
-        }
-
-        return d[m][n];
-    }
-    /**
-     * Gets the scores in a batch.
-     */
-    static float[][] getScores(@NonNull List<AutofillValue> actualValues,
-            @NonNull List<String> userDataValues) {
-        final int actualValuesSize = actualValues.size();
-        final int userDataValuesSize = userDataValues.size();
-        if (DEBUG) {
-            Log.d(TAG, "getScores() will return a " + actualValuesSize + "x"
-                    + userDataValuesSize + " matrix for " + DEFAULT_ALGORITHM);
-        }
-        final float[][] scores = new float[actualValuesSize][userDataValuesSize];
-
-        for (int i = 0; i < actualValuesSize; i++) {
-            for (int j = 0; j < userDataValuesSize; j++) {
-                final float score = getScore(actualValues.get(i), userDataValues.get(j));
-                scores[i][j] = score;
-            }
-        }
-        return scores;
-    }
-
-}
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
deleted file mode 100644
index f878822..0000000
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ /dev/null
@@ -1,382 +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.ext.services.notification;
-
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
-
-import android.app.INotificationManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.ext.services.R;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.storage.StorageManager;
-import android.provider.Settings;
-import android.service.notification.Adjustment;
-import android.service.notification.NotificationAssistantService;
-import android.service.notification.NotificationStats;
-import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
-
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Map;
-
-/**
- * Notification assistant that provides guidance on notification channel blocking
- */
-public class Assistant extends NotificationAssistantService {
-    private static final String TAG = "ExtAssistant";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final String TAG_ASSISTANT = "assistant";
-    private static final String TAG_IMPRESSION = "impression-set";
-    private static final String ATT_KEY = "key";
-    private static final int DB_VERSION = 1;
-    private static final String ATTR_VERSION = "version";
-
-    private static final ArrayList<Integer> PREJUDICAL_DISMISSALS = new ArrayList<>();
-    static {
-        PREJUDICAL_DISMISSALS.add(REASON_CANCEL);
-        PREJUDICAL_DISMISSALS.add(REASON_LISTENER_CANCEL);
-    }
-
-    private float mDismissToViewRatioLimit;
-    private int mStreakLimit;
-
-    // key : impressions tracker
-    // TODO: prune deleted channels and apps
-    final ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>();
-    // SBN key : channel id
-    ArrayMap<String, String> mLiveNotifications = new ArrayMap<>();
-
-    private Ranking mFakeRanking = null;
-    private AtomicFile mFile = null;
-
-    public Assistant() {
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        // Contexts are correctly hooked up by the creation step, which is required for the observer
-        // to be hooked up/initialized.
-        new SettingsObserver(mHandler);
-    }
-
-    private void loadFile() {
-        if (DEBUG) Slog.d(TAG, "loadFile");
-        AsyncTask.execute(() -> {
-            InputStream infile = null;
-            try {
-                infile = mFile.openRead();
-                readXml(infile);
-            } catch (FileNotFoundException e) {
-                Log.d(TAG, "File doesn't exist or isn't readable yet");
-            } catch (IOException e) {
-                Log.e(TAG, "Unable to read channel impressions", e);
-            } catch (NumberFormatException | XmlPullParserException e) {
-                Log.e(TAG, "Unable to parse channel impressions", e);
-            } finally {
-                IoUtils.closeQuietly(infile);
-            }
-        });
-    }
-
-    protected void readXml(InputStream stream)
-            throws XmlPullParserException, NumberFormatException, IOException {
-        final XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(stream, StandardCharsets.UTF_8.name());
-        final int outerDepth = parser.getDepth();
-        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-            if (!TAG_ASSISTANT.equals(parser.getName())) {
-                continue;
-            }
-            final int impressionOuterDepth = parser.getDepth();
-            while (XmlUtils.nextElementWithin(parser, impressionOuterDepth)) {
-                if (!TAG_IMPRESSION.equals(parser.getName())) {
-                    continue;
-                }
-                String key = parser.getAttributeValue(null, ATT_KEY);
-                ChannelImpressions ci = createChannelImpressionsWithThresholds();
-                ci.populateFromXml(parser);
-                synchronized (mkeyToImpressions) {
-                    ci.append(mkeyToImpressions.get(key));
-                    mkeyToImpressions.put(key, ci);
-                }
-            }
-        }
-    }
-
-    private void saveFile() throws IOException {
-        AsyncTask.execute(() -> {
-            final FileOutputStream stream;
-            try {
-                stream = mFile.startWrite();
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to save policy file", e);
-                return;
-            }
-            try {
-                final XmlSerializer out = new FastXmlSerializer();
-                out.setOutput(stream, StandardCharsets.UTF_8.name());
-                writeXml(out);
-                mFile.finishWrite(stream);
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to save impressions file, restoring backup", e);
-                mFile.failWrite(stream);
-            }
-        });
-    }
-
-    protected void writeXml(XmlSerializer out) throws IOException {
-        out.startDocument(null, true);
-        out.startTag(null, TAG_ASSISTANT);
-        out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
-        synchronized (mkeyToImpressions) {
-            for (Map.Entry<String, ChannelImpressions> entry
-                    : mkeyToImpressions.entrySet()) {
-                // TODO: ensure channel still exists
-                out.startTag(null, TAG_IMPRESSION);
-                out.attribute(null, ATT_KEY, entry.getKey());
-                entry.getValue().writeXml(out);
-                out.endTag(null, TAG_IMPRESSION);
-            }
-        }
-        out.endTag(null, TAG_ASSISTANT);
-        out.endDocument();
-    }
-
-    @Override
-    public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
-        if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey());
-        return null;
-    }
-
-    @Override
-    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
-        if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
-        try {
-            Ranking ranking = getRanking(sbn.getKey(), rankingMap);
-            if (ranking != null && ranking.getChannel() != null) {
-                String key = getKey(
-                        sbn.getPackageName(), sbn.getUserId(), ranking.getChannel().getId());
-                ChannelImpressions ci = mkeyToImpressions.getOrDefault(key,
-                        createChannelImpressionsWithThresholds());
-                if (ranking.getImportance() > IMPORTANCE_MIN && ci.shouldTriggerBlock()) {
-                    adjustNotification(createNegativeAdjustment(
-                            sbn.getPackageName(), sbn.getKey(), sbn.getUserId()));
-                }
-                mkeyToImpressions.put(key, ci);
-                mLiveNotifications.put(sbn.getKey(), ranking.getChannel().getId());
-            }
-        } catch (Throwable e) {
-            Log.e(TAG, "Error occurred processing post", e);
-        }
-    }
-
-    @Override
-    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
-            NotificationStats stats, int reason) {
-        try {
-            boolean updatedImpressions = false;
-            String channelId = mLiveNotifications.remove(sbn.getKey());
-            String key = getKey(sbn.getPackageName(), sbn.getUserId(), channelId);
-            synchronized (mkeyToImpressions) {
-                ChannelImpressions ci = mkeyToImpressions.getOrDefault(key,
-                        createChannelImpressionsWithThresholds());
-                if (stats.hasSeen()) {
-                    ci.incrementViews();
-                    updatedImpressions = true;
-                }
-                if (PREJUDICAL_DISMISSALS.contains(reason)) {
-                    if ((!sbn.isAppGroup() || sbn.getNotification().isGroupChild())
-                            && !stats.hasInteracted()
-                            && stats.getDismissalSurface() != NotificationStats.DISMISSAL_AOD
-                            && stats.getDismissalSurface() != NotificationStats.DISMISSAL_PEEK
-                            && stats.getDismissalSurface() != NotificationStats.DISMISSAL_OTHER) {
-                        if (DEBUG) Log.i(TAG, "increment dismissals " + key);
-                        ci.incrementDismissals();
-                        updatedImpressions = true;
-                    } else {
-                        if (DEBUG) Slog.i(TAG, "reset streak " + key);
-                        if (ci.getStreak() > 0) {
-                            updatedImpressions = true;
-                        }
-                        ci.resetStreak();
-                    }
-                }
-                mkeyToImpressions.put(key, ci);
-            }
-            if (updatedImpressions) {
-                saveFile();
-            }
-        } catch (Throwable e) {
-            Slog.e(TAG, "Error occurred processing removal", e);
-        }
-    }
-
-    @Override
-    public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
-            String snoozeCriterionId) {
-    }
-
-    @Override
-    public void onListenerConnected() {
-        if (DEBUG) Log.i(TAG, "CONNECTED");
-        try {
-            mFile = new AtomicFile(new File(new File(
-                    Environment.getDataUserCePackageDirectory(
-                            StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()),
-                    "assistant"), "blocking_helper_stats.xml"));
-            loadFile();
-            for (StatusBarNotification sbn : getActiveNotifications()) {
-                onNotificationPosted(sbn);
-            }
-        } catch (Throwable e) {
-            Log.e(TAG, "Error occurred on connection", e);
-        }
-    }
-
-    protected String getKey(String pkg, int userId, String channelId) {
-        return pkg + "|" + userId + "|" + channelId;
-    }
-
-    private Ranking getRanking(String key, RankingMap rankingMap) {
-        if (mFakeRanking != null) {
-            return mFakeRanking;
-        }
-        Ranking ranking = new Ranking();
-        rankingMap.getRanking(key, ranking);
-        return ranking;
-    }
-
-    private Adjustment createNegativeAdjustment(String packageName, String key, int user) {
-        if (DEBUG) Log.d(TAG, "User probably doesn't want " + key);
-        Bundle signals = new Bundle();
-        signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
-        return new Adjustment(packageName, key,  signals,
-                getContext().getString(R.string.prompt_block_reason), user);
-    }
-
-    // for testing
-
-    protected void setFile(AtomicFile file) {
-        mFile = file;
-    }
-
-    protected void setFakeRanking(Ranking ranking) {
-        mFakeRanking = ranking;
-    }
-
-    protected void setNoMan(INotificationManager noMan) {
-        mNoMan = noMan;
-    }
-
-    protected void setContext(Context context) {
-        mSystemContext = context;
-    }
-
-    protected ChannelImpressions getImpressions(String key) {
-        synchronized (mkeyToImpressions) {
-            return mkeyToImpressions.get(key);
-        }
-    }
-
-    protected void insertImpressions(String key, ChannelImpressions ci) {
-        synchronized (mkeyToImpressions) {
-            mkeyToImpressions.put(key, ci);
-        }
-    }
-
-    private ChannelImpressions createChannelImpressionsWithThresholds() {
-        ChannelImpressions impressions = new ChannelImpressions();
-        impressions.updateThresholds(mDismissToViewRatioLimit, mStreakLimit);
-        return impressions;
-    }
-
-    /**
-     * Observer for updates on blocking helper threshold values.
-     */
-    private final class SettingsObserver extends ContentObserver {
-        private final Uri STREAK_LIMIT_URI =
-                Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT);
-        private final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
-                Settings.Global.getUriFor(
-                        Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
-
-        public SettingsObserver(Handler handler) {
-            super(handler);
-            ContentResolver resolver = getApplicationContext().getContentResolver();
-            resolver.registerContentObserver(
-                    DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, getUserId());
-            resolver.registerContentObserver(STREAK_LIMIT_URI, false, this, getUserId());
-
-            // Update all uris on creation.
-            update(null);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            update(uri);
-        }
-
-        private void update(Uri uri) {
-            ContentResolver resolver = getApplicationContext().getContentResolver();
-            if (uri == null || DISMISS_TO_VIEW_RATIO_LIMIT_URI.equals(uri)) {
-                mDismissToViewRatioLimit = Settings.Global.getFloat(
-                        resolver, Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
-                        ChannelImpressions.DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT);
-            }
-            if (uri == null || STREAK_LIMIT_URI.equals(uri)) {
-                mStreakLimit = Settings.Global.getInt(
-                        resolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
-                        ChannelImpressions.DEFAULT_STREAK_LIMIT);
-            }
-
-            // Update all existing channel impression objects with any new limits/thresholds.
-            synchronized (mkeyToImpressions) {
-                for (ChannelImpressions channelImpressions: mkeyToImpressions.values()) {
-                    channelImpressions.updateThresholds(mDismissToViewRatioLimit, mStreakLimit);
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java b/packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java
deleted file mode 100644
index 29ee920..0000000
--- a/packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java
+++ /dev/null
@@ -1,209 +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.ext.services.notification;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-
-public final class ChannelImpressions implements Parcelable {
-    private static final String TAG = "ExtAssistant.CI";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    static final float DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT = .8f;
-    static final int DEFAULT_STREAK_LIMIT = 2;
-    static final String ATT_DISMISSALS = "dismisses";
-    static final String ATT_VIEWS = "views";
-    static final String ATT_STREAK = "streak";
-
-    private int mDismissals = 0;
-    private int mViews = 0;
-    private int mStreak = 0;
-
-    private float mDismissToViewRatioLimit;
-    private int mStreakLimit;
-
-    public ChannelImpressions() {
-        mDismissToViewRatioLimit = DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT;
-        mStreakLimit = DEFAULT_STREAK_LIMIT;
-    }
-
-    protected ChannelImpressions(Parcel in) {
-        mDismissals = in.readInt();
-        mViews = in.readInt();
-        mStreak = in.readInt();
-        mDismissToViewRatioLimit = in.readFloat();
-        mStreakLimit = in.readInt();
-    }
-
-    public int getStreak() {
-        return mStreak;
-    }
-
-    public int getDismissals() {
-        return mDismissals;
-    }
-
-    public int getViews() {
-        return mViews;
-    }
-
-    public void incrementDismissals() {
-        mDismissals++;
-        mStreak++;
-    }
-
-    void updateThresholds(float dismissToViewRatioLimit, int streakLimit) {
-        mDismissToViewRatioLimit = dismissToViewRatioLimit;
-        mStreakLimit = streakLimit;
-    }
-
-    @VisibleForTesting
-    float getDismissToViewRatioLimit() {
-        return mDismissToViewRatioLimit;
-    }
-
-    @VisibleForTesting
-    int getStreakLimit() {
-        return mStreakLimit;
-    }
-
-    public void append(ChannelImpressions additionalImpressions) {
-        if (additionalImpressions != null) {
-            mViews += additionalImpressions.getViews();
-            mStreak += additionalImpressions.getStreak();
-            mDismissals += additionalImpressions.getDismissals();
-        }
-    }
-
-    public void incrementViews() {
-        mViews++;
-    }
-
-    public void resetStreak() {
-        mStreak = 0;
-    }
-
-    public boolean shouldTriggerBlock() {
-        if (getViews() == 0) {
-            return false;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "should trigger? " + getDismissals() + " " + getViews() + " " + getStreak());
-        }
-        return ((float) getDismissals() / getViews()) > mDismissToViewRatioLimit
-                && getStreak() > mStreakLimit;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mDismissals);
-        dest.writeInt(mViews);
-        dest.writeInt(mStreak);
-        dest.writeFloat(mDismissToViewRatioLimit);
-        dest.writeInt(mStreakLimit);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final Creator<ChannelImpressions> CREATOR = new Creator<ChannelImpressions>() {
-        @Override
-        public ChannelImpressions createFromParcel(Parcel in) {
-            return new ChannelImpressions(in);
-        }
-
-        @Override
-        public ChannelImpressions[] newArray(int size) {
-            return new ChannelImpressions[size];
-        }
-    };
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        ChannelImpressions that = (ChannelImpressions) o;
-
-        if (mDismissals != that.mDismissals) return false;
-        if (mViews != that.mViews) return false;
-        return mStreak == that.mStreak;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mDismissals;
-        result = 31 * result + mViews;
-        result = 31 * result + mStreak;
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder("ChannelImpressions{");
-        sb.append("mDismissals=").append(mDismissals);
-        sb.append(", mViews=").append(mViews);
-        sb.append(", mStreak=").append(mStreak);
-        sb.append(", thresholds=(").append(mDismissToViewRatioLimit);
-        sb.append(",").append(mStreakLimit);
-        sb.append(")}");
-        return sb.toString();
-    }
-
-    protected void populateFromXml(XmlPullParser parser) {
-        mDismissals = safeInt(parser, ATT_DISMISSALS, 0);
-        mStreak = safeInt(parser, ATT_STREAK, 0);
-        mViews = safeInt(parser, ATT_VIEWS, 0);
-    }
-
-    protected void writeXml(XmlSerializer out) throws IOException {
-        if (mDismissals != 0) {
-            out.attribute(null, ATT_DISMISSALS, String.valueOf(mDismissals));
-        }
-        if (mStreak != 0) {
-            out.attribute(null, ATT_STREAK, String.valueOf(mStreak));
-        }
-        if (mViews != 0) {
-            out.attribute(null, ATT_VIEWS, String.valueOf(mViews));
-        }
-    }
-
-    private static int safeInt(XmlPullParser parser, String att, int defValue) {
-        final String val = parser.getAttributeValue(null, att);
-        return tryParseInt(val, defValue);
-    }
-
-    private static int tryParseInt(String value, int defValue) {
-        if (TextUtils.isEmpty(value)) return defValue;
-        try {
-            return Integer.parseInt(value);
-        } catch (NumberFormatException e) {
-            return defValue;
-        }
-    }
-}
diff --git a/packages/ExtServices/src/android/ext/services/resolver/LRResolverRankerService.java b/packages/ExtServices/src/android/ext/services/resolver/LRResolverRankerService.java
deleted file mode 100644
index 9d7a568..0000000
--- a/packages/ExtServices/src/android/ext/services/resolver/LRResolverRankerService.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.ext.services.resolver;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Environment;
-import android.os.IBinder;
-import android.os.storage.StorageManager;
-import android.service.resolver.ResolverRankerService;
-import android.service.resolver.ResolverTarget;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A Logistic Regression based {@link android.service.resolver.ResolverRankerService}, to be used
- * in {@link ResolverComparator}.
- */
-public final class LRResolverRankerService extends ResolverRankerService {
-    private static final String TAG = "LRResolverRankerService";
-
-    private static final boolean DEBUG = false;
-
-    private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params";
-    private static final String BIAS_PREF_KEY = "bias";
-    private static final String VERSION_PREF_KEY = "version";
-
-    private static final String LAUNCH_SCORE = "launch";
-    private static final String TIME_SPENT_SCORE = "timeSpent";
-    private static final String RECENCY_SCORE = "recency";
-    private static final String CHOOSER_SCORE = "chooser";
-
-    // parameters for a pre-trained model, to initialize the app ranker. When updating the
-    // pre-trained model, please update these params, as well as initModel().
-    private static final int CURRENT_VERSION = 1;
-    private static final float LEARNING_RATE = 0.0001f;
-    private static final float REGULARIZER_PARAM = 0.0001f;
-
-    private SharedPreferences mParamSharedPref;
-    private ArrayMap<String, Float> mFeatureWeights;
-    private float mBias;
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        initModel();
-        return super.onBind(intent);
-    }
-
-    @Override
-    public void onPredictSharingProbabilities(List<ResolverTarget> targets) {
-        final int size = targets.size();
-        for (int i = 0; i < size; ++i) {
-            ResolverTarget target = targets.get(i);
-            ArrayMap<String, Float> features = getFeatures(target);
-            target.setSelectProbability(predict(features));
-        }
-    }
-
-    @Override
-    public void onTrainRankingModel(List<ResolverTarget> targets, int selectedPosition) {
-        final int size = targets.size();
-        if (selectedPosition < 0 || selectedPosition >= size) {
-            if (DEBUG) {
-                Log.d(TAG, "Invalid Position of Selected App " + selectedPosition);
-            }
-            return;
-        }
-        final ArrayMap<String, Float> positive = getFeatures(targets.get(selectedPosition));
-        final float positiveProbability = targets.get(selectedPosition).getSelectProbability();
-        final int targetSize = targets.size();
-        for (int i = 0; i < targetSize; ++i) {
-            if (i == selectedPosition) {
-                continue;
-            }
-            final ArrayMap<String, Float> negative = getFeatures(targets.get(i));
-            final float negativeProbability = targets.get(i).getSelectProbability();
-            if (negativeProbability > positiveProbability) {
-                update(negative, negativeProbability, false);
-                update(positive, positiveProbability, true);
-            }
-        }
-        commitUpdate();
-    }
-
-    private void initModel() {
-        mParamSharedPref = getParamSharedPref();
-        mFeatureWeights = new ArrayMap<>(4);
-        if (mParamSharedPref == null ||
-                mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) {
-            // Initializing the app ranker to a pre-trained model. When updating the pre-trained
-            // model, please increment CURRENT_VERSION, and update LEARNING_RATE and
-            // REGULARIZER_PARAM.
-            mBias = -1.6568f;
-            mFeatureWeights.put(LAUNCH_SCORE, 2.5543f);
-            mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f);
-            mFeatureWeights.put(RECENCY_SCORE, 0.269f);
-            mFeatureWeights.put(CHOOSER_SCORE, 4.2222f);
-        } else {
-            mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f);
-            mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f));
-            mFeatureWeights.put(
-                    TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f));
-            mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f));
-            mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f));
-        }
-    }
-
-    private ArrayMap<String, Float> getFeatures(ResolverTarget target) {
-        ArrayMap<String, Float> features = new ArrayMap<>(4);
-        features.put(RECENCY_SCORE, target.getRecencyScore());
-        features.put(TIME_SPENT_SCORE, target.getTimeSpentScore());
-        features.put(LAUNCH_SCORE, target.getLaunchScore());
-        features.put(CHOOSER_SCORE, target.getChooserScore());
-        return features;
-    }
-
-    private float predict(ArrayMap<String, Float> target) {
-        if (target == null) {
-            return 0.0f;
-        }
-        final int featureSize = target.size();
-        float sum = 0.0f;
-        for (int i = 0; i < featureSize; i++) {
-            String featureName = target.keyAt(i);
-            float weight = mFeatureWeights.getOrDefault(featureName, 0.0f);
-            sum += weight * target.valueAt(i);
-        }
-        return (float) (1.0 / (1.0 + Math.exp(-mBias - sum)));
-    }
-
-    private void update(ArrayMap<String, Float> target, float predict, boolean isSelected) {
-        if (target == null) {
-            return;
-        }
-        final int featureSize = target.size();
-        float error = isSelected ? 1.0f - predict : -predict;
-        for (int i = 0; i < featureSize; i++) {
-            String featureName = target.keyAt(i);
-            float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f);
-            mBias += LEARNING_RATE * error;
-            currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight +
-                    LEARNING_RATE * error * target.valueAt(i);
-            mFeatureWeights.put(featureName, currentWeight);
-        }
-        if (DEBUG) {
-            Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias);
-        }
-    }
-
-    private void commitUpdate() {
-        try {
-            SharedPreferences.Editor editor = mParamSharedPref.edit();
-            editor.putFloat(BIAS_PREF_KEY, mBias);
-            final int size = mFeatureWeights.size();
-            for (int i = 0; i < size; i++) {
-                editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i));
-            }
-            editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION);
-            editor.apply();
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to commit update" + e);
-        }
-    }
-
-    private SharedPreferences getParamSharedPref() {
-        // The package info in the context isn't initialized in the way it is for normal apps,
-        // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
-        // build the path manually below using the same policy that appears in ContextImpl.
-        if (DEBUG) {
-            Log.d(TAG, "Context Package Name: " + getPackageName());
-        }
-        final File prefsFile = new File(new File(
-                Environment.getDataUserCePackageDirectory(
-                        StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()),
-                "shared_prefs"),
-                PARAM_SHARED_PREF_NAME + ".xml");
-        return getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
-    }
-}
\ No newline at end of file
diff --git a/packages/ExtServices/src/android/ext/services/storage/CacheQuotaServiceImpl.java b/packages/ExtServices/src/android/ext/services/storage/CacheQuotaServiceImpl.java
deleted file mode 100644
index 862f50b2..0000000
--- a/packages/ExtServices/src/android/ext/services/storage/CacheQuotaServiceImpl.java
+++ /dev/null
@@ -1,143 +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.ext.services.storage;
-
-import android.app.usage.CacheQuotaHint;
-import android.app.usage.CacheQuotaService;
-import android.os.Environment;
-import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
-import android.util.ArrayMap;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-/**
- * CacheQuotaServiceImpl implements the CacheQuotaService with a strategy for populating the quota
- * of {@link CacheQuotaHint}.
- */
-public class CacheQuotaServiceImpl extends CacheQuotaService {
-    private static final double CACHE_RESERVE_RATIO = 0.15;
-
-    @Override
-    public List<CacheQuotaHint> onComputeCacheQuotaHints(List<CacheQuotaHint> requests) {
-        ArrayMap<String, List<CacheQuotaHint>> byUuid = new ArrayMap<>();
-        final int requestCount = requests.size();
-        for (int i = 0; i < requestCount; i++) {
-            CacheQuotaHint request = requests.get(i);
-            String uuid = request.getVolumeUuid();
-            List<CacheQuotaHint> listForUuid = byUuid.get(uuid);
-            if (listForUuid == null) {
-                listForUuid = new ArrayList<>();
-                byUuid.put(uuid, listForUuid);
-            }
-            listForUuid.add(request);
-        }
-
-        List<CacheQuotaHint> processed = new ArrayList<>();
-        byUuid.entrySet().forEach(
-                requestListEntry -> {
-                    // Collapse all usage stats to the same uid.
-                    Map<Integer, List<CacheQuotaHint>> byUid = requestListEntry.getValue()
-                            .stream()
-                            .collect(Collectors.groupingBy(CacheQuotaHint::getUid));
-                    byUid.values().forEach(uidGroupedList -> {
-                        int size = uidGroupedList.size();
-                        if (size < 2) {
-                            return;
-                        }
-                        CacheQuotaHint first = uidGroupedList.get(0);
-                        for (int i = 1; i < size; i++) {
-                            /* Note: We can't use the UsageStats built-in addition function because
-                                     UIDs may span multiple packages and usage stats adding has
-                                     matching package names as a precondition. */
-                            first.getUsageStats().mTotalTimeInForeground +=
-                                    uidGroupedList.get(i).getUsageStats().mTotalTimeInForeground;
-                        }
-                    });
-
-                    // Because the foreground stats have been added to the first element, we need
-                    // a list of only the first values (which contain the merged foreground time).
-                    List<CacheQuotaHint> flattenedRequests =
-                            byUid.values()
-                                 .stream()
-                                 .map(entryList -> entryList.get(0))
-                                 .filter(entry -> entry.getUsageStats().mTotalTimeInForeground != 0)
-                                 .sorted(sCacheQuotaRequestComparator)
-                                 .collect(Collectors.toList());
-
-                    // Because the elements are sorted, we can use the index to also be the sorted
-                    // index for cache quota calculation.
-                    double sum = getSumOfFairShares(flattenedRequests.size());
-                    String uuid = requestListEntry.getKey();
-                    long reservedSize = getReservedCacheSize(uuid);
-                    for (int count = 0; count < flattenedRequests.size(); count++) {
-                        double share = getFairShareForPosition(count) / sum;
-                        CacheQuotaHint entry = flattenedRequests.get(count);
-                        CacheQuotaHint.Builder builder = new CacheQuotaHint.Builder(entry);
-                        builder.setQuota(Math.round(share * reservedSize));
-                        processed.add(builder.build());
-                    }
-                }
-        );
-
-        return processed.stream()
-                .filter(request -> request.getQuota() > 0).collect(Collectors.toList());
-    }
-
-    private double getFairShareForPosition(int position) {
-        double value = 1.0 / Math.log(position + 3) - 0.285;
-        return (value > 0.01) ? value : 0.01;
-    }
-
-    private double getSumOfFairShares(int size) {
-        double sum = 0;
-        for (int i = 0; i < size; i++) {
-            sum += getFairShareForPosition(i);
-        }
-        return sum;
-    }
-
-    private long getReservedCacheSize(String uuid) {
-        // TODO: Revisit the cache size after running more storage tests.
-        // TODO: Figure out how to ensure ExtServices has the permissions to call
-        //       StorageStatsManager, because this is ignoring the cache...
-        StorageManager storageManager = getSystemService(StorageManager.class);
-        long freeBytes = 0;
-        if (uuid == StorageManager.UUID_PRIVATE_INTERNAL) { // regular equals because of null
-            freeBytes = Environment.getDataDirectory().getUsableSpace();
-        } else {
-            final VolumeInfo vol = storageManager.findVolumeByUuid(uuid);
-            freeBytes = vol.getPath().getUsableSpace();
-        }
-        return Math.round(freeBytes * CACHE_RESERVE_RATIO);
-    }
-
-    // Compares based upon foreground time.
-    private static Comparator<CacheQuotaHint> sCacheQuotaRequestComparator =
-            new Comparator<CacheQuotaHint>() {
-        @Override
-        public int compare(CacheQuotaHint o, CacheQuotaHint t1) {
-            long x = t1.getUsageStats().getTotalTimeInForeground();
-            long y = o.getUsageStats().getTotalTimeInForeground();
-            return (x < y) ? -1 : ((x == y) ? 0 : 1);
-        }
-    };
-}
diff --git a/packages/ExtServices/tests/Android.bp b/packages/ExtServices/tests/Android.bp
deleted file mode 100644
index db16027..0000000
--- a/packages/ExtServices/tests/Android.bp
+++ /dev/null
@@ -1,19 +0,0 @@
-android_test {
-    name: "ExtServicesUnitTests",
-    certificate: "platform",
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
-    static_libs: [
-        "androidx.test.rules",
-        "mockito-target-minus-junit4",
-        "androidx.test.espresso.core",
-        "truth-prebuilt",
-        "testables",
-    ],
-    // Include all test java files.
-    srcs: ["src/**/*.java"],
-    platform_apis: true,
-    instrumentation_for: "ExtServices",
-}
diff --git a/packages/ExtServices/tests/AndroidManifest.xml b/packages/ExtServices/tests/AndroidManifest.xml
deleted file mode 100644
index 42293b5..0000000
--- a/packages/ExtServices/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.ext.services.tests.unit">
-
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.ext.services"
-                     android:label="ExtServices Test Cases">
-    </instrumentation>
-
-</manifest>
\ No newline at end of file
diff --git a/packages/ExtServices/tests/AndroidTest.xml b/packages/ExtServices/tests/AndroidTest.xml
deleted file mode 100644
index cd26ebc..0000000
--- a/packages/ExtServices/tests/AndroidTest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 Tests for ExtServices">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
-        <option name="test-file-name" value="ExtServicesUnitTests.apk" />
-    </target_preparer>
-
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-suite-tag" value="framework-base-presubmit" />
-    <option name="test-tag" value="ExtServicesUnitTests" />
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.ext.services.tests.unit" />
-        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
-        <option name="hidden-api-checks" value="false"/>
-    </test>
-</configuration>
\ No newline at end of file
diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java
deleted file mode 100644
index 48c076e..0000000
--- a/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.ext.services.autofill;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.view.autofill.AutofillValue;
-
-/**
- * Contains the base tests that does not rely on the specific algorithm implementation.
- */
-public class AutofillFieldClassificationServiceImplTest {
-
-    private final AutofillFieldClassificationServiceImpl mService =
-            new AutofillFieldClassificationServiceImpl();
-
-    @Test
-    public void testOnGetScores_nullActualValues() {
-        assertThat(mService.onGetScores(null, null, null, Arrays.asList("whatever"))).isNull();
-    }
-
-    @Test
-    public void testOnGetScores_emptyActualValues() {
-        assertThat(mService.onGetScores(null, null, Collections.emptyList(),
-                Arrays.asList("whatever"))).isNull();
-    }
-
-    @Test
-    public void testOnGetScores_nullUserDataValues() {
-        assertThat(mService.onGetScores(null, null,
-                Arrays.asList(AutofillValue.forText("whatever")), null)).isNull();
-    }
-
-    @Test
-    public void testOnGetScores_emptyUserDataValues() {
-        assertThat(mService.onGetScores(null, null,
-                Arrays.asList(AutofillValue.forText("whatever")), Collections.emptyList()))
-                        .isNull();
-    }
-}
diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java
deleted file mode 100644
index afe2236..0000000
--- a/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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.ext.services.autofill;
-
-import static android.ext.services.autofill.EditDistanceScorer.getScore;
-import static android.ext.services.autofill.EditDistanceScorer.getScores;
-import static android.view.autofill.AutofillValue.forText;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.view.autofill.AutofillValue;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.List;
-
-public class EditDistanceScorerTest {
-
-    @Test
-    public void testGetScore_nullValue() {
-        assertFloat(getScore(null, "D'OH!"), 0);
-    }
-
-    @Test
-    public void testGetScore_nonTextValue() {
-        assertFloat(getScore(AutofillValue.forToggle(true), "D'OH!"), 0);
-    }
-
-    @Test
-    public void testGetScore_nullUserData() {
-        assertFloat(getScore(AutofillValue.forText("D'OH!"), null), 0);
-    }
-
-    @Test
-    public void testGetScore_fullMatch() {
-        assertFloat(getScore(AutofillValue.forText("D'OH!"), "D'OH!"), 1);
-        assertFloat(getScore(AutofillValue.forText(""), ""), 1);
-    }
-
-    @Test
-    public void testGetScore_fullMatchMixedCase() {
-        assertFloat(getScore(AutofillValue.forText("D'OH!"), "D'oH!"), 1);
-    }
-
-    @Test
-    public void testGetScore_mismatchDifferentSizes() {
-        assertFloat(getScore(AutofillValue.forText("X"), "Xy"), 0.50F);
-        assertFloat(getScore(AutofillValue.forText("Xy"), "X"), 0.50F);
-        assertFloat(getScore(AutofillValue.forText("One"), "MoreThanOne"), 0.27F);
-        assertFloat(getScore(AutofillValue.forText("MoreThanOne"), "One"), 0.27F);
-        assertFloat(getScore(AutofillValue.forText("1600 Amphitheatre Parkway"),
-                "1600 Amphitheatre Pkwy"), 0.88F);
-        assertFloat(getScore(AutofillValue.forText("1600 Amphitheatre Pkwy"),
-                "1600 Amphitheatre Parkway"), 0.88F);
-    }
-
-    @Test
-    public void testGetScore_partialMatch() {
-        assertFloat(getScore(AutofillValue.forText("Dude"), "Dxxx"), 0.25F);
-        assertFloat(getScore(AutofillValue.forText("Dude"), "DUxx"), 0.50F);
-        assertFloat(getScore(AutofillValue.forText("Dude"), "DUDx"), 0.75F);
-        assertFloat(getScore(AutofillValue.forText("Dxxx"), "Dude"), 0.25F);
-        assertFloat(getScore(AutofillValue.forText("DUxx"), "Dude"), 0.50F);
-        assertFloat(getScore(AutofillValue.forText("DUDx"), "Dude"), 0.75F);
-    }
-
-    @Test
-    public void testGetScores() {
-        final List<AutofillValue> actualValues = Arrays.asList(forText("A"), forText("b"));
-        final List<String> userDataValues = Arrays.asList("a", "B", "ab", "c");
-        final float[][] expectedScores = new float[][] {
-            new float[] { 1F, 0F, 0.5F, 0F },
-            new float[] { 0F, 1F, 0.5F, 0F }
-        };
-        final float[][] actualScores = getScores(actualValues, userDataValues);
-
-        // Unfortunately, Truth does not have an easy way to compare float matrices and show useful
-        // messages in case of error, so we need to check.
-        assertWithMessage("actual=%s, expected=%s", toString(actualScores),
-                toString(expectedScores)).that(actualScores.length).isEqualTo(2);
-        assertWithMessage("actual=%s, expected=%s", toString(actualScores),
-                toString(expectedScores)).that(actualScores[0].length).isEqualTo(4);
-        assertWithMessage("actual=%s, expected=%s", toString(actualScores),
-                toString(expectedScores)).that(actualScores[1].length).isEqualTo(4);
-        for (int i = 0; i < actualScores.length; i++) {
-            final float[] line = actualScores[i];
-            for (int j = 0; j < line.length; j++) {
-                float cell = line[j];
-                assertWithMessage("wrong score at [%s, %s]", i, j).that(cell).isWithin(0.01F)
-                        .of(expectedScores[i][j]);
-            }
-        }
-    }
-
-    public static void assertFloat(float actualValue, float expectedValue) {
-        assertThat(actualValue).isWithin(0.01F).of(expectedValue);
-    }
-
-    public static String toString(float[][] matrix) {
-        final StringBuilder string = new StringBuilder("[ ");
-        for (int i = 0; i < matrix.length; i++) {
-            string.append(Arrays.toString(matrix[i])).append(" ");
-        }
-        return string.append(" ]").toString();
-    }
-}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
deleted file mode 100644
index 6ef25e5..0000000
--- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
+++ /dev/null
@@ -1,447 +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.ext.services.notification;
-
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Application;
-import android.app.INotificationManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.service.notification.Adjustment;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.NotificationStats;
-import android.service.notification.StatusBarNotification;
-import android.test.ServiceTestCase;
-import android.testing.TestableContext;
-import android.util.AtomicFile;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.util.FastXmlSerializer;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
-
-public class AssistantTest extends ServiceTestCase<Assistant> {
-
-    private static final String PKG1 = "pkg1";
-    private static final int UID1 = 1;
-    private static final NotificationChannel P1C1 =
-            new NotificationChannel("one", "", IMPORTANCE_LOW);
-    private static final NotificationChannel P1C2 =
-            new NotificationChannel("p1c2", "", IMPORTANCE_DEFAULT);
-    private static final NotificationChannel P1C3 =
-            new NotificationChannel("p1c3", "", IMPORTANCE_MIN);
-    private static final String PKG2 = "pkg2";
-
-    private static final int UID2 = 2;
-    private static final NotificationChannel P2C1 =
-            new NotificationChannel("one", "", IMPORTANCE_LOW);
-
-    @Mock INotificationManager mNoMan;
-    @Mock AtomicFile mFile;
-
-    Assistant mAssistant;
-    Application mApplication;
-
-    @Rule
-    public final TestableContext mContext =
-            new TestableContext(InstrumentationRegistry.getContext(), null);
-
-    public AssistantTest() {
-        super(Assistant.class);
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        Intent startIntent =
-                new Intent("android.service.notification.NotificationAssistantService");
-        startIntent.setPackage("android.ext.services");
-
-        // To bypass real calls to global settings values, set the Settings values here.
-        Settings.Global.putFloat(mContext.getContentResolver(),
-                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
-        mApplication = (Application) InstrumentationRegistry.getInstrumentation().
-                getTargetContext().getApplicationContext();
-        // Force the test to use the correct application instead of trying to use a mock application
-        setApplication(mApplication);
-        bindService(startIntent);
-        mAssistant = getService();
-        mAssistant.setNoMan(mNoMan);
-        mAssistant.setFile(mFile);
-        when(mFile.startWrite()).thenReturn(mock(FileOutputStream.class));
-    }
-
-    private StatusBarNotification generateSbn(String pkg, int uid, NotificationChannel channel,
-            String tag, String groupKey) {
-        Notification n = new Notification.Builder(mContext, channel.getId())
-                .setContentTitle("foo")
-                .setGroup(groupKey)
-                .build();
-
-        StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 0, tag, uid, uid, n,
-                UserHandle.SYSTEM, null, 0);
-
-        return sbn;
-    }
-
-    private Ranking generateRanking(StatusBarNotification sbn, NotificationChannel channel) {
-        Ranking mockRanking = mock(Ranking.class);
-        when(mockRanking.getChannel()).thenReturn(channel);
-        when(mockRanking.getImportance()).thenReturn(channel.getImportance());
-        when(mockRanking.getKey()).thenReturn(sbn.getKey());
-        when(mockRanking.getOverrideGroupKey()).thenReturn(null);
-        return mockRanking;
-    }
-
-    private void almostBlockChannel(String pkg, int uid, NotificationChannel channel) {
-        for (int i = 0; i < ChannelImpressions.DEFAULT_STREAK_LIMIT; i++) {
-            dismissBadNotification(pkg, uid, channel, String.valueOf(i));
-        }
-    }
-
-    private void dismissBadNotification(String pkg, int uid, NotificationChannel channel,
-            String tag) {
-        StatusBarNotification sbn = generateSbn(pkg, uid, channel, tag, null);
-        mAssistant.setFakeRanking(generateRanking(sbn, channel));
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-        mAssistant.setFakeRanking(mock(Ranking.class));
-        NotificationStats stats = new NotificationStats();
-        stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
-        stats.setSeen();
-        mAssistant.onNotificationRemoved(
-                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
-    }
-
-    @Test
-    public void testNoAdjustmentForInitialPost() throws Exception {
-        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, null, null);
-
-        mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-
-        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
-    }
-
-    @Test
-    public void testTriggerAdjustment() throws Exception {
-        almostBlockChannel(PKG1, UID1, P1C1);
-        dismissBadNotification(PKG1, UID1, P1C1, "trigger!");
-
-        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "new one!", null);
-        mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-
-        ArgumentCaptor<Adjustment> captor = ArgumentCaptor.forClass(Adjustment.class);
-        verify(mNoMan, times(1)).applyAdjustmentFromAssistant(any(), captor.capture());
-        assertEquals(sbn.getKey(), captor.getValue().getKey());
-        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE,
-                captor.getValue().getSignals().getInt(Adjustment.KEY_USER_SENTIMENT));
-    }
-
-    @Test
-    public void testMinCannotTriggerAdjustment() throws Exception {
-        almostBlockChannel(PKG1, UID1, P1C3);
-        dismissBadNotification(PKG1, UID1, P1C3, "trigger!");
-
-        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C3, "new one!", null);
-        mAssistant.setFakeRanking(generateRanking(sbn, P1C3));
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-
-        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
-    }
-
-    @Test
-    public void testGroupChildCanTriggerAdjustment() throws Exception {
-        almostBlockChannel(PKG1, UID1, P1C1);
-
-        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", "I HAVE A GROUP");
-        mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
-        NotificationStats stats = new NotificationStats();
-        stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
-        stats.setSeen();
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-        mAssistant.onNotificationRemoved(
-                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
-
-        sbn = generateSbn(PKG1, UID1, P1C1, "new one!", "group");
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-
-        ArgumentCaptor<Adjustment> captor = ArgumentCaptor.forClass(Adjustment.class);
-        verify(mNoMan, times(1)).applyAdjustmentFromAssistant(any(), captor.capture());
-        assertEquals(sbn.getKey(), captor.getValue().getKey());
-        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE,
-                captor.getValue().getSignals().getInt(Adjustment.KEY_USER_SENTIMENT));
-    }
-
-    @Test
-    public void testGroupSummaryCannotTriggerAdjustment() throws Exception {
-        almostBlockChannel(PKG1, UID1, P1C1);
-
-        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", "I HAVE A GROUP");
-        sbn.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
-        mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
-        NotificationStats stats = new NotificationStats();
-        stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
-        stats.setSeen();
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-        mAssistant.onNotificationRemoved(
-                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
-
-        sbn = generateSbn(PKG1, UID1, P1C1, "new one!", "group");
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-
-        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
-    }
-
-    @Test
-    public void testAodCannotTriggerAdjustment() throws Exception {
-        almostBlockChannel(PKG1, UID1, P1C1);
-
-        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", null);
-        mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
-        NotificationStats stats = new NotificationStats();
-        stats.setDismissalSurface(NotificationStats.DISMISSAL_AOD);
-        stats.setSeen();
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-        mAssistant.onNotificationRemoved(
-                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
-
-        sbn = generateSbn(PKG1, UID1, P1C1, "new one!", null);
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-
-        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
-    }
-
-    @Test
-    public void testInteractedCannotTriggerAdjustment() throws Exception {
-        almostBlockChannel(PKG1, UID1, P1C1);
-        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", null);
-        mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
-        NotificationStats stats = new NotificationStats();
-        stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
-        stats.setSeen();
-        stats.setExpanded();
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-        mAssistant.onNotificationRemoved(
-                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
-
-        sbn = generateSbn(PKG1, UID1, P1C1, "new one!", null);
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-
-        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
-    }
-
-    @Test
-    public void testAppDismissedCannotTriggerAdjustment() throws Exception {
-        almostBlockChannel(PKG1, UID1, P1C1);
-
-        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", null);
-        mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
-        NotificationStats stats = new NotificationStats();
-        stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
-        stats.setSeen();
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-        mAssistant.onNotificationRemoved(
-                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_APP_CANCEL);
-
-        sbn = generateSbn(PKG1, UID1, P1C1, "new one!", null);
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-
-        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
-    }
-
-    @Test
-    public void testAppSeparation() throws Exception {
-        almostBlockChannel(PKG1, UID1, P1C1);
-        dismissBadNotification(PKG1, UID1, P1C1, "trigger!");
-
-        StatusBarNotification sbn = generateSbn(PKG2, UID2, P2C1, "new app!", null);
-        mAssistant.setFakeRanking(generateRanking(sbn, P2C1));
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-
-        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
-    }
-
-    @Test
-    public void testChannelSeparation() throws Exception {
-        almostBlockChannel(PKG1, UID1, P1C1);
-        dismissBadNotification(PKG1, UID1, P1C1, "trigger!");
-
-        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C2, "new app!", null);
-        mAssistant.setFakeRanking(generateRanking(sbn, P1C2));
-        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
-
-        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
-    }
-
-    @Test
-    public void testReadXml() throws Exception {
-        String key1 = mAssistant.getKey("pkg1", 1, "channel1");
-        int streak1 = 2;
-        int views1 = 5;
-        int dismiss1 = 9;
-
-        int streak1a = 3;
-        int views1a = 10;
-        int dismiss1a = 99;
-        String key1a = mAssistant.getKey("pkg1", 1, "channel1a");
-
-        int streak2 = 7;
-        int views2 = 77;
-        int dismiss2 = 777;
-        String key2 = mAssistant.getKey("pkg2", 2, "channel2");
-
-        String xml = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
-                + "<assistant version=\"1\">\n"
-                + "<impression-set key=\"" + key1 + "\" "
-                + "dismisses=\"" + dismiss1 + "\" views=\"" + views1
-                + "\" streak=\"" + streak1 + "\"/>\n"
-                + "<impression-set key=\"" + key1a + "\" "
-                + "dismisses=\"" + dismiss1a + "\" views=\"" + views1a
-                + "\" streak=\"" + streak1a + "\"/>\n"
-                + "<impression-set key=\"" + key2 + "\" "
-                + "dismisses=\"" + dismiss2 + "\" views=\"" + views2
-                + "\" streak=\"" + streak2 + "\"/>\n"
-                + "</assistant>\n";
-        mAssistant.readXml(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())));
-
-        ChannelImpressions c1 = mAssistant.getImpressions(key1);
-        assertEquals(2, c1.getStreak());
-        assertEquals(5, c1.getViews());
-        assertEquals(9, c1.getDismissals());
-
-        ChannelImpressions c1a = mAssistant.getImpressions(key1a);
-        assertEquals(3, c1a.getStreak());
-        assertEquals(10, c1a.getViews());
-        assertEquals(99, c1a.getDismissals());
-
-        ChannelImpressions c2 = mAssistant.getImpressions(key2);
-        assertEquals(7, c2.getStreak());
-        assertEquals(77, c2.getViews());
-        assertEquals(777, c2.getDismissals());
-    }
-
-    @Test
-    public void testRoundTripXml() throws Exception {
-        String key1 = mAssistant.getKey("pkg1", 1, "channel1");
-        ChannelImpressions ci1 = new ChannelImpressions();
-        String key2 = mAssistant.getKey("pkg1", 1, "channel2");
-        ChannelImpressions ci2 = new ChannelImpressions();
-        for (int i = 0; i < 3; i++) {
-            ci2.incrementViews();
-            ci2.incrementDismissals();
-        }
-        ChannelImpressions ci3 = new ChannelImpressions();
-        String key3 = mAssistant.getKey("pkg3", 3, "channel2");
-        for (int i = 0; i < 9; i++) {
-            ci3.incrementViews();
-            if (i % 3 == 0) {
-                ci3.incrementDismissals();
-            }
-        }
-
-        mAssistant.insertImpressions(key1, ci1);
-        mAssistant.insertImpressions(key2, ci2);
-        mAssistant.insertImpressions(key3, ci3);
-
-        XmlSerializer serializer = new FastXmlSerializer();
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
-        mAssistant.writeXml(serializer);
-
-        Assistant assistant = new Assistant();
-        assistant.readXml(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())));
-
-        assertEquals(ci1, assistant.getImpressions(key1));
-        assertEquals(ci2, assistant.getImpressions(key2));
-        assertEquals(ci3, assistant.getImpressions(key3));
-    }
-
-    @Test
-    public void testSettingsProviderUpdate() {
-        ContentResolver resolver = mApplication.getContentResolver();
-
-        // Set up channels
-        String key = mAssistant.getKey("pkg1", 1, "channel1");
-        ChannelImpressions ci = new ChannelImpressions();
-        for (int i = 0; i < 3; i++) {
-            ci.incrementViews();
-            if (i % 2 == 0) {
-                ci.incrementDismissals();
-            }
-        }
-
-        mAssistant.insertImpressions(key, ci);
-
-        // With default values, the blocking helper shouldn't be triggered.
-        assertEquals(false, ci.shouldTriggerBlock());
-
-        // Update settings values.
-        float newDismissToViewRatioLimit = 0f;
-        int newStreakLimit = 0;
-        Settings.Global.putFloat(resolver,
-                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
-                newDismissToViewRatioLimit);
-        Settings.Global.putInt(resolver,
-                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, newStreakLimit);
-
-        // Notify for the settings values we updated.
-        resolver.notifyChange(
-                Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT), null);
-        resolver.notifyChange(
-                Settings.Global.getUriFor(
-                        Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT),
-                null);
-
-        // With the new threshold, the blocking helper should be triggered.
-        assertEquals(true, ci.shouldTriggerBlock());
-    }
-}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java
deleted file mode 100644
index 3253802..0000000
--- a/packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java
+++ /dev/null
@@ -1,161 +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.ext.services.notification;
-
-import static android.ext.services.notification.ChannelImpressions.DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT;
-import static android.ext.services.notification.ChannelImpressions.DEFAULT_STREAK_LIMIT;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-
-public class ChannelImpressionsTest {
-
-    @Test
-    public void testNoResultNoBlock() {
-        ChannelImpressions ci = new ChannelImpressions();
-        assertFalse(ci.shouldTriggerBlock());
-    }
-
-    @Test
-    public void testNoStreakNoBlock() {
-        ChannelImpressions ci = new ChannelImpressions();
-
-        for (int i = 0; i < DEFAULT_STREAK_LIMIT - 1; i++) {
-            ci.incrementViews();
-            ci.incrementDismissals();
-        }
-
-        assertFalse(ci.shouldTriggerBlock());
-    }
-
-    @Test
-    public void testNoStreakNoBlock_breakStreak() {
-        ChannelImpressions ci = new ChannelImpressions();
-
-        for (int i = 0; i < DEFAULT_STREAK_LIMIT; i++) {
-            ci.incrementViews();
-            ci.incrementDismissals();
-            if (i == DEFAULT_STREAK_LIMIT - 1) {
-                ci.resetStreak();
-            }
-        }
-
-        assertFalse(ci.shouldTriggerBlock());
-    }
-
-    @Test
-    public void testStreakBlock() {
-        ChannelImpressions ci = new ChannelImpressions();
-
-        for (int i = 0; i <= DEFAULT_STREAK_LIMIT; i++) {
-            ci.incrementViews();
-            ci.incrementDismissals();
-        }
-
-        assertTrue(ci.shouldTriggerBlock());
-    }
-
-    @Test
-    public void testRatio_NoBlockEvenWithStreak() {
-        ChannelImpressions ci = new ChannelImpressions();
-
-        for (int i = 0; i < DEFAULT_STREAK_LIMIT; i++) {
-            ci.incrementViews();
-            ci.incrementDismissals();
-            ci.incrementViews();
-        }
-
-        assertFalse(ci.shouldTriggerBlock());
-    }
-
-    @Test
-    public void testAppend() {
-        ChannelImpressions ci = new ChannelImpressions();
-        ci.incrementViews();
-        ci.incrementDismissals();
-
-        ChannelImpressions ci2 = new ChannelImpressions();
-        ci2.incrementViews();
-        ci2.incrementDismissals();
-        ci2.incrementViews();
-
-        ci.append(ci2);
-        assertEquals(3, ci.getViews());
-        assertEquals(2, ci.getDismissals());
-        assertEquals(2, ci.getStreak());
-
-        assertEquals(2, ci2.getViews());
-        assertEquals(1, ci2.getDismissals());
-        assertEquals(1, ci2.getStreak());
-
-        // no crash
-        ci.append(null);
-    }
-
-    @Test
-    public void testUpdateThresholds_streakLimitsCorrectlyApplied() {
-        int updatedStreakLimit = DEFAULT_STREAK_LIMIT + 3;
-        ChannelImpressions ci = new ChannelImpressions();
-        ci.updateThresholds(DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT, updatedStreakLimit);
-
-        for (int i = 0; i <= updatedStreakLimit; i++) {
-            ci.incrementViews();
-            ci.incrementDismissals();
-        }
-
-        ChannelImpressions ci2 = new ChannelImpressions();
-        ci2.updateThresholds(DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT, updatedStreakLimit);
-
-        for (int i = 0; i < updatedStreakLimit; i++) {
-            ci2.incrementViews();
-            ci2.incrementDismissals();
-        }
-
-        assertTrue(ci.shouldTriggerBlock());
-        assertFalse(ci2.shouldTriggerBlock());
-    }
-
-    @Test
-    public void testUpdateThresholds_ratioLimitsCorrectlyApplied() {
-        float updatedDismissRatio = .99f;
-        ChannelImpressions ci = new ChannelImpressions();
-        ci.updateThresholds(updatedDismissRatio, DEFAULT_STREAK_LIMIT);
-
-        // N views, N-1 dismissals, which doesn't satisfy the ratio = 1 criteria.
-        for (int i = 0; i <= DEFAULT_STREAK_LIMIT; i++) {
-            ci.incrementViews();
-            if (i != DEFAULT_STREAK_LIMIT) {
-                ci.incrementDismissals();
-            }
-        }
-
-        ChannelImpressions ci2 = new ChannelImpressions();
-        ci2.updateThresholds(updatedDismissRatio, DEFAULT_STREAK_LIMIT);
-
-        for (int i = 0; i <= DEFAULT_STREAK_LIMIT; i++) {
-            ci2.incrementViews();
-            ci2.incrementDismissals();
-        }
-
-        assertFalse(ci.shouldTriggerBlock());
-        assertTrue(ci2.shouldTriggerBlock());
-    }
-}
diff --git a/packages/ExtServices/tests/src/android/ext/services/storage/CacheQuotaServiceImplTest.java b/packages/ExtServices/tests/src/android/ext/services/storage/CacheQuotaServiceImplTest.java
deleted file mode 100644
index df4738f..0000000
--- a/packages/ExtServices/tests/src/android/ext/services/storage/CacheQuotaServiceImplTest.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.ext.services.storage;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.app.usage.CacheQuotaHint;
-import android.app.usage.UsageStats;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
-import android.test.ServiceTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Answers;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-public class CacheQuotaServiceImplTest extends ServiceTestCase<CacheQuotaServiceImpl> {
-    private static final String sTestVolUuid = "uuid";
-    private static final String sSecondTestVolUuid = "otherUuid";
-
-    @Mock private Context mContext;
-    @Mock private File mFile;
-    @Mock private VolumeInfo mVolume;
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private StorageManager mStorageManager;
-
-    public CacheQuotaServiceImplTest() {
-        super(CacheQuotaServiceImpl.class);
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        MockitoAnnotations.initMocks(this);
-        mContext = Mockito.spy(new ContextWrapper(getSystemContext()));
-        setContext(mContext);
-        when(mContext.getSystemService(Context.STORAGE_SERVICE)).thenReturn(mStorageManager);
-
-        when(mFile.getUsableSpace()).thenReturn(10000L);
-        when(mVolume.getPath()).thenReturn(mFile);
-        when(mStorageManager.findVolumeByUuid(sTestVolUuid)).thenReturn(mVolume);
-        when(mStorageManager.findVolumeByUuid(sSecondTestVolUuid)).thenReturn(mVolume);
-
-        Intent intent = new Intent(getContext(), CacheQuotaServiceImpl.class);
-        startService(intent);
-    }
-
-    @Test
-    public void testNoApps() {
-        CacheQuotaServiceImpl service = getService();
-        assertEquals(service.onComputeCacheQuotaHints(new ArrayList()).size(), 0);
-    }
-
-    @Test
-    public void testOneApp() throws Exception {
-        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
-        CacheQuotaHint request = makeNewRequest("com.test", sTestVolUuid, 1001, 100L);
-        requests.add(request);
-
-        List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
-
-        assertThat(output).hasSize(1);
-        assertThat(output.get(0).getQuota()).isEqualTo(1500L);
-    }
-
-    @Test
-    public void testTwoAppsOneVolume() throws Exception {
-        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
-        requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
-        requests.add(makeNewRequest("com.test2", sTestVolUuid, 1002, 99L));
-
-        List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
-
-        // Note that the sizes are just the cache area split up.
-        assertThat(output).hasSize(2);
-        assertThat(output.get(0).getQuota()).isEqualTo(883);
-        assertThat(output.get(1).getQuota()).isEqualTo(1500 - 883);
-    }
-
-    @Test
-    public void testTwoAppsTwoVolumes() throws Exception {
-        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
-        requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
-        requests.add(makeNewRequest("com.test2", sSecondTestVolUuid, 1002, 99L));
-
-        List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
-
-        assertThat(output).hasSize(2);
-        assertThat(output.get(0).getQuota()).isEqualTo(1500);
-        assertThat(output.get(1).getQuota()).isEqualTo(1500);
-    }
-
-    @Test
-    public void testMultipleAppsPerUidIsCollated() throws Exception {
-        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
-        requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
-        requests.add(makeNewRequest("com.test2", sTestVolUuid, 1001, 99L));
-
-        List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
-
-        assertThat(output).hasSize(1);
-        assertThat(output.get(0).getQuota()).isEqualTo(1500);
-    }
-
-    @Test
-    public void testTwoAppsTwoVolumesTwoUuidsShouldBESeparate() throws Exception {
-        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
-        requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
-        requests.add(makeNewRequest("com.test2", sSecondTestVolUuid, 1001, 99L));
-
-        List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
-
-        assertThat(output).hasSize(2);
-        assertThat(output.get(0).getQuota()).isEqualTo(1500);
-        assertThat(output.get(1).getQuota()).isEqualTo(1500);
-    }
-
-    private CacheQuotaHint makeNewRequest(String packageName, String uuid, int uid, long foregroundTime) {
-        UsageStats stats = new UsageStats();
-        stats.mPackageName = packageName;
-        stats.mTotalTimeInForeground = foregroundTime;
-        return new CacheQuotaHint.Builder()
-                .setVolumeUuid(uuid).setUid(uid).setUsageStats(stats).setQuota(-1).build();
-    }
-}
diff --git a/packages/ExtServices/Android.bp b/packages/ExtShared/Android.bp
similarity index 91%
rename from packages/ExtServices/Android.bp
rename to packages/ExtShared/Android.bp
index db94eec..a9823b9 100644
--- a/packages/ExtServices/Android.bp
+++ b/packages/ExtShared/Android.bp
@@ -13,14 +13,13 @@
 // limitations under the License.
 
 android_app {
-    name: "ExtServices",
+    name: "ExtShared",
     srcs: ["src/**/*.java"],
-    platform_apis: true,
+    sdk_version: "current",
     certificate: "platform",
     aaptflags: ["--shared-lib"],
     export_package_resources: true,
     optimize: {
         proguard_flags_files: ["proguard.proguard"],
     },
-    privileged: true,
 }
diff --git a/packages/ExtShared/Android.mk b/packages/ExtShared/Android.mk
deleted file mode 100644
index 7dbf79f..0000000
--- a/packages/ExtShared/Android.mk
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := ExtShared
-LOCAL_SDK_VERSION := current
-
-LOCAL_CERTIFICATE := platform
-
-LOCAL_AAPT_FLAGS := --shared-lib
-
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.proguard
-
-include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/packages/InputDevices/Android.bp b/packages/InputDevices/Android.bp
new file mode 100644
index 0000000..7532aea
--- /dev/null
+++ b/packages/InputDevices/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_app {
+    name: "InputDevices",
+
+    srcs: [
+        "**/*.java",
+        ":validate_input_devices_keymaps",
+    ],
+
+    resource_dirs: ["res"],
+
+    sdk_version: "current",
+    certificate: "platform",
+    privileged: true,
+}
+
+// Validate all key maps.
+// Produces an empty srcjar that is used as an input to InputDevices to make sure
+// the check runs for platform builds.
+genrule {
+    name: "validate_input_devices_keymaps",
+    tools: [
+        "validatekeymaps",
+        "soong_zip",
+    ],
+    srcs: ["res/raw/*.kcm"],
+    out: ["validate_input_devices_keymaps.srcjar"],
+    cmd: "$(location validatekeymaps) -q $(in) && $(location soong_zip) -o $(out)",
+}
diff --git a/packages/InputDevices/Android.mk b/packages/InputDevices/Android.mk
deleted file mode 100644
index 6de1f1d..0000000
--- a/packages/InputDevices/Android.mk
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_JAVA_LIBRARIES := 
-
-LOCAL_PACKAGE_NAME := InputDevices
-LOCAL_SDK_VERSION := current
-LOCAL_CERTIFICATE := platform
-LOCAL_PRIVILEGED_MODULE := true
-
-include $(BUILD_PACKAGE)
-
-# Validate all key maps.
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := validate_input_devices_keymaps
-intermediates := $(call intermediates-dir-for,ETC,$(LOCAL_MODULE),,COMMON)
-LOCAL_BUILT_MODULE := $(intermediates)/stamp
-
-validatekeymaps := $(HOST_OUT_EXECUTABLES)/validatekeymaps$(HOST_EXECUTABLE_SUFFIX)
-input_devices_keymaps := $(wildcard $(LOCAL_PATH)/res/raw/*.kcm)
-$(LOCAL_BUILT_MODULE): PRIVATE_VALIDATEKEYMAPS := $(validatekeymaps)
-$(LOCAL_BUILT_MODULE) : $(input_devices_keymaps) | $(validatekeymaps)
-	$(hide) $(PRIVATE_VALIDATEKEYMAPS) $^
-	$(hide) mkdir -p $(dir $@) && touch $@
-
-# Run validatekeymaps unconditionally for platform build.
-droidcore : $(LOCAL_BUILT_MODULE)
-
-# Reset temp vars.
-validatekeymaps :=
-input_devices_keymaps :=
diff --git a/packages/MtpDocumentsProvider/Android.bp b/packages/MtpDocumentsProvider/Android.bp
new file mode 100644
index 0000000..3dafa26
--- /dev/null
+++ b/packages/MtpDocumentsProvider/Android.bp
@@ -0,0 +1,11 @@
+android_app {
+    name: "MtpDocumentsProvider",
+
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    certificate: "media",
+    privileged: true,
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
+}
diff --git a/packages/MtpDocumentsProvider/Android.mk b/packages/MtpDocumentsProvider/Android.mk
deleted file mode 100644
index 2d62a07..0000000
--- a/packages/MtpDocumentsProvider/Android.mk
+++ /dev/null
@@ -1,18 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := MtpDocumentsProvider
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_CERTIFICATE := media
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-
-# Only enable asserts on userdebug/eng builds
-ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
-LOCAL_JACK_FLAGS += -D jack.assert.policy=always
-endif
-
-include $(BUILD_PACKAGE)
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/packages/NetworkPermissionConfig/Android.bp b/packages/NetworkPermissionConfig/Android.bp
deleted file mode 100644
index 6e50459..0000000
--- a/packages/NetworkPermissionConfig/Android.bp
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-java_defaults {
-    name: "NetworkPermissionConfigDefaults",
-    // TODO: mark app as hasCode=false in manifest once soong stops complaining about apps without
-    // a classes.dex.
-    srcs: ["src/**/*.java"],
-    platform_apis: true,
-    min_sdk_version: "28",
-    privileged: true,
-    manifest: "AndroidManifest.xml",
-}
-
-// Stub APK to define permissions for NetworkStack
-android_app {
-    name: "NetworkPermissionConfig",
-    defaults: ["NetworkPermissionConfigDefaults"],
-    certificate: "networkstack",
-}
-
-// Alternative stub APK signed with platform certificate. To use with InProcessNetworkStack.
-android_app {
-    name: "PlatformNetworkPermissionConfig",
-    defaults: ["NetworkPermissionConfigDefaults"],
-    certificate: "platform",
-    overrides: ["NetworkPermissionConfig"],
-}
diff --git a/packages/NetworkPermissionConfig/AndroidManifest.xml b/packages/NetworkPermissionConfig/AndroidManifest.xml
deleted file mode 100644
index 34f987c..0000000
--- a/packages/NetworkPermissionConfig/AndroidManifest.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.networkstack.permissionconfig"
-    android:sharedUserId="android.uid.networkstack"
-    android:versionCode="11"
-    android:versionName="Q-initial">
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
-    <!--
-    This package only exists to define the below permissions, and enforce that they are only
-    granted to apps sharing the same signature.
-    Permissions defined here are intended to be used only by the NetworkStack: both
-    NetworkStack and this stub APK are to be signed with a dedicated certificate to ensure
-    that, with the below permissions being signature permissions.
-
-    This APK *must* be installed, even if the NetworkStack app is not installed, because otherwise,
-    any application will be able to define this permission and the system will give that application
-    full access to the network stack.
-     -->
-    <permission android:name="android.permission.MAINLINE_NETWORK_STACK"
-                android:protectionLevel="signature"/>
-
-    <application android:name="com.android.server.NetworkPermissionConfig"/>
-</manifest>
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
deleted file mode 100644
index e15526a..0000000
--- a/packages/NetworkStack/Android.bp
+++ /dev/null
@@ -1,132 +0,0 @@
-//
-// 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.
-//
-
-java_library {
-    name: "captiveportal-lib",
-    srcs: ["common/**/*.java"],
-    libs: [
-        "androidx.annotation_annotation",
-    ],
-    sdk_version: "system_current",
-}
-
-java_defaults {
-    name: "NetworkStackCommon",
-    sdk_version: "system_current",
-    min_sdk_version: "28",
-}
-
-// Library including the network stack, used to compile both variants of the network stack
-android_library {
-    name: "NetworkStackBase",
-    defaults: ["NetworkStackCommon"],
-    srcs: [
-        "src/**/*.java",
-        ":framework-networkstack-shared-srcs",
-        ":services-networkstack-shared-srcs",
-        ":statslog-networkstack-java-gen",
-    ],
-    static_libs: [
-        "androidx.annotation_annotation",
-        "ipmemorystore-client",
-        "netd_aidl_interface-java",
-        "networkstack-aidl-interfaces-java",
-        "datastallprotosnano",
-        "networkstackprotosnano",
-        "captiveportal-lib",
-    ],
-    manifest: "AndroidManifestBase.xml",
-}
-
-cc_library_shared {
-    name: "libnetworkstackutilsjni",
-    srcs: [
-        "jni/network_stack_utils_jni.cpp"
-    ],
-    sdk_version: "current",
-    shared_libs: [
-        "liblog",
-        "libnativehelper_compat_libc++",
-    ],
-
-    // We cannot use plain "libc++" here to link libc++ dynamically because it results in:
-    //   java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found
-    // even if "libc++" is added into jni_libs below. Adding "libc++_shared" into jni_libs doesn't
-    // build because soong complains of:
-    //   module NetworkStack missing dependencies: libc++_shared
-    //
-    // So, link libc++ statically. This means that we also need to ensure that all the C++ libraries
-    // we depend on do not dynamically link libc++. This is currently the case, because liblog is
-    // C-only and libnativehelper_compat_libc also uses stl: "c++_static".
-    //
-    // TODO: find a better solution for this in R.
-    stl: "c++_static",
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-    ],
-}
-
-java_defaults {
-    name: "NetworkStackAppCommon",
-    defaults: ["NetworkStackCommon"],
-    privileged: true,
-    static_libs: [
-        "NetworkStackBase",
-    ],
-    jni_libs: [
-        "libnativehelper_compat_libc++",
-        "libnetworkstackutilsjni",
-    ],
-    // Resources already included in NetworkStackBase
-    resource_dirs: [],
-    jarjar_rules: "jarjar-rules-shared.txt",
-    optimize: {
-        proguard_flags_files: ["proguard.flags"],
-    },
-}
-
-// Non-updatable network stack running in the system server process for devices not using the module
-android_app {
-    name: "InProcessNetworkStack",
-    defaults: ["NetworkStackAppCommon"],
-    certificate: "platform",
-    manifest: "AndroidManifest_InProcess.xml",
-    // InProcessNetworkStack is a replacement for NetworkStack
-    overrides: ["NetworkStack"],
-    // The permission configuration *must* be included to ensure security of the device
-    required: ["PlatformNetworkPermissionConfig"],
-}
-
-// Updatable network stack packaged as an application
-android_app {
-    name: "NetworkStack",
-    defaults: ["NetworkStackAppCommon"],
-    certificate: "networkstack",
-    manifest: "AndroidManifest.xml",
-    use_embedded_native_libs: true,
-    // The permission configuration *must* be included to ensure security of the device
-    required: ["NetworkPermissionConfig"],
-}
-
-genrule {
-    name: "statslog-networkstack-java-gen",
-    tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" +
-         " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog",
-    out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"],
-}
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
deleted file mode 100644
index de45784..0000000
--- a/packages/NetworkStack/AndroidManifest.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.networkstack"
-          android:sharedUserId="android.uid.networkstack">
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
-
-    <!-- Permissions must be defined here, and not in the base manifest, as the network stack
-         running in the system server process does not need any permission, and having privileged
-         permissions added would cause crashes on startup unless they are also added to the
-         privileged permissions whitelist for that package. -->
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
-    <!-- Send latency broadcast as current user -->
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
-    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
-    <!-- Signature permission defined in NetworkStackStub -->
-    <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" />
-    <application
-        android:extractNativeLibs="false"
-        android:persistent="true">
-        <service android:name="com.android.server.NetworkStackService">
-            <intent-filter>
-                <action android:name="android.net.INetworkStackConnector"/>
-            </intent-filter>
-        </service>
-    </application>
-</manifest>
diff --git a/packages/NetworkStack/AndroidManifestBase.xml b/packages/NetworkStack/AndroidManifestBase.xml
deleted file mode 100644
index d00a551..0000000
--- a/packages/NetworkStack/AndroidManifestBase.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.networkstack"
-          android:versionCode="11"
-          android:versionName="Q-initial">
-    <application
-        android:label="NetworkStack"
-        android:defaultToDeviceProtectedStorage="true"
-        android:directBootAware="true"
-        android:usesCleartextTraffic="true">
-
-        <service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
-                 android:permission="android.permission.BIND_JOB_SERVICE" >
-        </service>
-    </application>
-</manifest>
diff --git a/packages/NetworkStack/AndroidManifest_InProcess.xml b/packages/NetworkStack/AndroidManifest_InProcess.xml
deleted file mode 100644
index 275cd02..0000000
--- a/packages/NetworkStack/AndroidManifest_InProcess.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.networkstack.inprocess"
-          android:sharedUserId="android.uid.system"
-          android:process="system">
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
-    <application>
-        <service android:name="com.android.server.NetworkStackService" android:process="system">
-            <intent-filter>
-                <action android:name="android.net.INetworkStackConnector.InProcess"/>
-            </intent-filter>
-        </service>
-    </application>
-</manifest>
diff --git a/packages/NetworkStack/OWNERS b/packages/NetworkStack/OWNERS
deleted file mode 100644
index 0e1e65d..0000000
--- a/packages/NetworkStack/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-codewiz@google.com
-jchalard@google.com
-junyulai@google.com
-lorenzo@google.com
-reminv@google.com
-satk@google.com
diff --git a/packages/NetworkStack/TEST_MAPPING b/packages/NetworkStack/TEST_MAPPING
deleted file mode 100644
index fe9731fe..0000000
--- a/packages/NetworkStack/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "NetworkStackTests"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/packages/NetworkStack/common/CaptivePortalProbeResult.java b/packages/NetworkStack/common/CaptivePortalProbeResult.java
deleted file mode 100644
index 48cd48b..0000000
--- a/packages/NetworkStack/common/CaptivePortalProbeResult.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.captiveportal;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Result of calling isCaptivePortal().
- * @hide
- */
-public final class CaptivePortalProbeResult {
-    public static final int SUCCESS_CODE = 204;
-    public static final int FAILED_CODE = 599;
-    public static final int PORTAL_CODE = 302;
-    // Set partial connectivity http response code to -1 to prevent conflict with the other http
-    // response codes. Besides the default http response code of probe result is set as 599 in
-    // NetworkMonitor#sendParallelHttpProbes(), so response code will be set as -1 only when
-    // NetworkMonitor detects partial connectivity.
-    /**
-     * @hide
-     */
-    public static final int PARTIAL_CODE = -1;
-
-    @NonNull
-    public static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(FAILED_CODE);
-    @NonNull
-    public static final CaptivePortalProbeResult SUCCESS =
-            new CaptivePortalProbeResult(SUCCESS_CODE);
-    public static final CaptivePortalProbeResult PARTIAL =
-            new CaptivePortalProbeResult(PARTIAL_CODE);
-
-    private final int mHttpResponseCode;  // HTTP response code returned from Internet probe.
-    @Nullable
-    public final String redirectUrl;      // Redirect destination returned from Internet probe.
-    @Nullable
-    public final String detectUrl;        // URL where a 204 response code indicates
-                                          // captive portal has been appeased.
-    @Nullable
-    public final CaptivePortalProbeSpec probeSpec;
-
-    public CaptivePortalProbeResult(int httpResponseCode) {
-        this(httpResponseCode, null, null);
-    }
-
-    public CaptivePortalProbeResult(int httpResponseCode, @Nullable String redirectUrl,
-            @Nullable String detectUrl) {
-        this(httpResponseCode, redirectUrl, detectUrl, null);
-    }
-
-    public CaptivePortalProbeResult(int httpResponseCode, @Nullable String redirectUrl,
-            @Nullable String detectUrl, @Nullable CaptivePortalProbeSpec probeSpec) {
-        mHttpResponseCode = httpResponseCode;
-        this.redirectUrl = redirectUrl;
-        this.detectUrl = detectUrl;
-        this.probeSpec = probeSpec;
-    }
-
-    public boolean isSuccessful() {
-        return mHttpResponseCode == SUCCESS_CODE;
-    }
-
-    public boolean isPortal() {
-        return !isSuccessful() && (mHttpResponseCode >= 200) && (mHttpResponseCode <= 399);
-    }
-
-    public boolean isFailed() {
-        return !isSuccessful() && !isPortal();
-    }
-
-    public boolean isPartialConnectivity() {
-        return mHttpResponseCode == PARTIAL_CODE;
-    }
-}
diff --git a/packages/NetworkStack/common/CaptivePortalProbeSpec.java b/packages/NetworkStack/common/CaptivePortalProbeSpec.java
deleted file mode 100644
index bf983a5..0000000
--- a/packages/NetworkStack/common/CaptivePortalProbeSpec.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * 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.captiveportal;
-
-import static android.net.captiveportal.CaptivePortalProbeResult.PORTAL_CODE;
-import static android.net.captiveportal.CaptivePortalProbeResult.SUCCESS_CODE;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/** @hide */
-public abstract class CaptivePortalProbeSpec {
-    private static final String TAG = CaptivePortalProbeSpec.class.getSimpleName();
-    private static final String REGEX_SEPARATOR = "@@/@@";
-    private static final String SPEC_SEPARATOR = "@@,@@";
-
-    private final String mEncodedSpec;
-    private final URL mUrl;
-
-    CaptivePortalProbeSpec(@NonNull String encodedSpec, @NonNull URL url) {
-        mEncodedSpec = checkNotNull(encodedSpec);
-        mUrl = checkNotNull(url);
-    }
-
-    /**
-     * Parse a {@link CaptivePortalProbeSpec} from a {@link String}.
-     *
-     * <p>The valid format is a URL followed by two regular expressions, each separated by "@@/@@".
-     * @throws MalformedURLException The URL has invalid format for {@link URL#URL(String)}.
-     * @throws ParseException The string is empty, does not match the above format, or a regular
-     * expression is invalid for {@link Pattern#compile(String)}.
-     * @hide
-     */
-    @VisibleForTesting
-    @NonNull
-    public static CaptivePortalProbeSpec parseSpec(@NonNull String spec) throws ParseException,
-            MalformedURLException {
-        if (TextUtils.isEmpty(spec)) {
-            throw new ParseException("Empty probe spec", 0 /* errorOffset */);
-        }
-
-        String[] splits = TextUtils.split(spec, REGEX_SEPARATOR);
-        if (splits.length != 3) {
-            throw new ParseException("Probe spec does not have 3 parts", 0 /* errorOffset */);
-        }
-
-        final int statusRegexPos = splits[0].length() + REGEX_SEPARATOR.length();
-        final int locationRegexPos = statusRegexPos + splits[1].length() + REGEX_SEPARATOR.length();
-        final Pattern statusRegex = parsePatternIfNonEmpty(splits[1], statusRegexPos);
-        final Pattern locationRegex = parsePatternIfNonEmpty(splits[2], locationRegexPos);
-
-        return new RegexMatchProbeSpec(spec, new URL(splits[0]), statusRegex, locationRegex);
-    }
-
-    @Nullable
-    private static Pattern parsePatternIfNonEmpty(@Nullable String pattern, int pos)
-            throws ParseException {
-        if (TextUtils.isEmpty(pattern)) {
-            return null;
-        }
-        try {
-            return Pattern.compile(pattern);
-        } catch (PatternSyntaxException e) {
-            throw new ParseException(
-                    String.format("Invalid status pattern [%s]: %s", pattern, e),
-                    pos /* errorOffset */);
-        }
-    }
-
-    /**
-     * Parse a {@link CaptivePortalProbeSpec} from a {@link String}, or return a fallback spec
-     * based on the status code of the provided URL if the spec cannot be parsed.
-     */
-    @Nullable
-    public static CaptivePortalProbeSpec parseSpecOrNull(@Nullable String spec) {
-        if (spec != null) {
-            try {
-                return parseSpec(spec);
-            } catch (ParseException | MalformedURLException e) {
-                Log.e(TAG, "Invalid probe spec: " + spec, e);
-                // Fall through
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Parse a config String to build an array of {@link CaptivePortalProbeSpec}.
-     *
-     * <p>Each spec is separated by @@,@@ and follows the format for {@link #parseSpec(String)}.
-     * <p>This method does not throw but ignores any entry that could not be parsed.
-     */
-    @NonNull
-    public static Collection<CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(
-            @NonNull String settingsVal) {
-        List<CaptivePortalProbeSpec> specs = new ArrayList<>();
-        if (settingsVal != null) {
-            for (String spec : TextUtils.split(settingsVal, SPEC_SEPARATOR)) {
-                try {
-                    specs.add(parseSpec(spec));
-                } catch (ParseException | MalformedURLException e) {
-                    Log.e(TAG, "Invalid probe spec: " + spec, e);
-                }
-            }
-        }
-
-        if (specs.isEmpty()) {
-            Log.e(TAG, String.format("could not create any validation spec from %s", settingsVal));
-        }
-        return specs;
-    }
-
-    /**
-     * Get the probe result from HTTP status and location header.
-     */
-    @NonNull
-    public abstract CaptivePortalProbeResult getResult(int status, @Nullable String locationHeader);
-
-    @NonNull
-    public String getEncodedSpec() {
-        return mEncodedSpec;
-    }
-
-    @NonNull
-    public URL getUrl() {
-        return mUrl;
-    }
-
-    /**
-     * Implementation of {@link CaptivePortalProbeSpec} that is based on configurable regular
-     * expressions for the HTTP status code and location header (if any). Matches indicate that
-     * the page is not a portal.
-     * This probe cannot fail: it always returns SUCCESS_CODE or PORTAL_CODE
-     */
-    private static class RegexMatchProbeSpec extends CaptivePortalProbeSpec {
-        @Nullable
-        final Pattern mStatusRegex;
-        @Nullable
-        final Pattern mLocationHeaderRegex;
-
-        RegexMatchProbeSpec(
-                String spec, URL url, Pattern statusRegex, Pattern locationHeaderRegex) {
-            super(spec, url);
-            mStatusRegex = statusRegex;
-            mLocationHeaderRegex = locationHeaderRegex;
-        }
-
-        @Override
-        public CaptivePortalProbeResult getResult(int status, String locationHeader) {
-            final boolean statusMatch = safeMatch(String.valueOf(status), mStatusRegex);
-            final boolean locationMatch = safeMatch(locationHeader, mLocationHeaderRegex);
-            final int returnCode = statusMatch && locationMatch ? SUCCESS_CODE : PORTAL_CODE;
-            return new CaptivePortalProbeResult(
-                    returnCode, locationHeader, getUrl().toString(), this);
-        }
-    }
-
-    private static boolean safeMatch(@Nullable String value, @Nullable Pattern pattern) {
-        // No value is a match ("no location header" passes the location rule for non-redirects)
-        return pattern == null || TextUtils.isEmpty(value) || pattern.matcher(value).matches();
-    }
-
-    // Throws NullPointerException if the input is null.
-    private static <T> T checkNotNull(T object) {
-        if (object == null) throw new NullPointerException();
-        return object;
-    }
-}
diff --git a/packages/NetworkStack/jarjar-rules-shared.txt b/packages/NetworkStack/jarjar-rules-shared.txt
deleted file mode 100644
index 7346b1a..0000000
--- a/packages/NetworkStack/jarjar-rules-shared.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-rule com.android.internal.util.** android.net.networkstack.util.@1
-
-rule android.net.shared.Inet4AddressUtils* android.net.networkstack.shared.Inet4AddressUtils@1
-rule android.net.shared.InetAddressUtils* android.net.networkstack.shared.InetAddressUtils@1
-
-# Ignore DhcpResultsParcelable, but jarjar DhcpResults
-# TODO: move DhcpResults into services.net and delete from here
-rule android.net.DhcpResultsParcelable* @0
-rule android.net.DhcpResults* android.net.networkstack.DhcpResults@1
-rule android.net.LocalLog* android.net.networkstack.LocalLog@1
diff --git a/packages/NetworkStack/jni/network_stack_utils_jni.cpp b/packages/NetworkStack/jni/network_stack_utils_jni.cpp
deleted file mode 100644
index f2ba575..0000000
--- a/packages/NetworkStack/jni/network_stack_utils_jni.cpp
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 "NetworkStackUtils-JNI"
-
-#include <errno.h>
-#include <jni.h>
-#include <linux/filter.h>
-#include <linux/if_arp.h>
-#include <net/if.h>
-#include <netinet/ether.h>
-#include <netinet/icmp6.h>
-#include <netinet/ip.h>
-#include <netinet/ip6.h>
-#include <netinet/udp.h>
-#include <stdlib.h>
-
-#include <string>
-
-#include <nativehelper/JNIHelp.h>
-#include <android/log.h>
-
-namespace android {
-constexpr const char NETWORKSTACKUTILS_PKG_NAME[] = "android/net/util/NetworkStackUtils";
-
-static const uint32_t kEtherTypeOffset = offsetof(ether_header, ether_type);
-static const uint32_t kEtherHeaderLen = sizeof(ether_header);
-static const uint32_t kIPv4Protocol = kEtherHeaderLen + offsetof(iphdr, protocol);
-static const uint32_t kIPv4FlagsOffset = kEtherHeaderLen + offsetof(iphdr, frag_off);
-static const uint32_t kIPv6NextHeader = kEtherHeaderLen + offsetof(ip6_hdr, ip6_nxt);
-static const uint32_t kIPv6PayloadStart = kEtherHeaderLen + sizeof(ip6_hdr);
-static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
-static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, source);
-static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
-static const uint16_t kDhcpClientPort = 68;
-
-static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) {
-    if (env->GetArrayLength(addr) != len) {
-        return false;
-    }
-    env->GetByteArrayRegion(addr, 0, len, reinterpret_cast<jbyte*>(dst));
-    return true;
-}
-
-static void network_stack_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray ethAddr,
-        jbyteArray ipv4Addr, jstring ifname, jobject javaFd) {
-    arpreq req = {};
-    sockaddr_in& netAddrStruct = *reinterpret_cast<sockaddr_in*>(&req.arp_pa);
-    sockaddr& ethAddrStruct = req.arp_ha;
-
-    ethAddrStruct.sa_family = ARPHRD_ETHER;
-    if (!checkLenAndCopy(env, ethAddr, ETH_ALEN, ethAddrStruct.sa_data)) {
-        jniThrowException(env, "java/io/IOException", "Invalid ethAddr length");
-        return;
-    }
-
-    netAddrStruct.sin_family = AF_INET;
-    if (!checkLenAndCopy(env, ipv4Addr, sizeof(in_addr), &netAddrStruct.sin_addr)) {
-        jniThrowException(env, "java/io/IOException", "Invalid ipv4Addr length");
-        return;
-    }
-
-    int ifLen = env->GetStringLength(ifname);
-    // IFNAMSIZ includes the terminating NULL character
-    if (ifLen >= IFNAMSIZ) {
-        jniThrowException(env, "java/io/IOException", "ifname too long");
-        return;
-    }
-    env->GetStringUTFRegion(ifname, 0, ifLen, req.arp_dev);
-
-    req.arp_flags = ATF_COM;  // Completed entry (ha valid)
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    if (fd < 0) {
-        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
-        return;
-    }
-    // See also: man 7 arp
-    if (ioctl(fd, SIOCSARP, &req)) {
-        jniThrowExceptionFmt(env, "java/io/IOException", "ioctl error: %s", strerror(errno));
-        return;
-    }
-}
-
-static void network_stack_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd) {
-    static sock_filter filter_code[] = {
-        // Check the protocol is UDP.
-        BPF_STMT(BPF_LD  | BPF_B    | BPF_ABS, kIPv4Protocol),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   IPPROTO_UDP, 0, 6),
-
-        // Check this is not a fragment.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS, kIPv4FlagsOffset),
-        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   IP_OFFMASK, 4, 0),
-
-        // Get the IP header length.
-        BPF_STMT(BPF_LDX | BPF_B    | BPF_MSH, kEtherHeaderLen),
-
-        // Check the destination port.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPDstPortIndirectOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 0, 1),
-
-        // Accept or reject.
-        BPF_STMT(BPF_RET | BPF_K,              0xffff),
-        BPF_STMT(BPF_RET | BPF_K,              0)
-    };
-    static const sock_fprog filter = {
-        sizeof(filter_code) / sizeof(filter_code[0]),
-        filter_code,
-    };
-
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
-    }
-}
-
-static void network_stack_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject javaFd,
-        jint hardwareAddressType) {
-    if (hardwareAddressType != ARPHRD_ETHER) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "attachRaFilter only supports ARPHRD_ETHER");
-        return;
-    }
-
-    static sock_filter filter_code[] = {
-        // Check IPv6 Next Header is ICMPv6.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv6NextHeader),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 3),
-
-        // Check ICMPv6 type is Router Advertisement.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kICMPv6TypeOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    ND_ROUTER_ADVERT, 0, 1),
-
-        // Accept or reject.
-        BPF_STMT(BPF_RET | BPF_K,              0xffff),
-        BPF_STMT(BPF_RET | BPF_K,              0)
-    };
-    static const sock_fprog filter = {
-        sizeof(filter_code) / sizeof(filter_code[0]),
-        filter_code,
-    };
-
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
-    }
-}
-
-// TODO: Move all this filter code into libnetutils.
-static void network_stack_utils_attachControlPacketFilter(
-        JNIEnv *env, jobject clazz, jobject javaFd, jint hardwareAddressType) {
-    if (hardwareAddressType != ARPHRD_ETHER) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "attachControlPacketFilter only supports ARPHRD_ETHER");
-        return;
-    }
-
-    // Capture all:
-    //     - ARPs
-    //     - DHCPv4 packets
-    //     - Router Advertisements & Solicitations
-    //     - Neighbor Advertisements & Solicitations
-    //
-    // tcpdump:
-    //     arp or
-    //     '(ip and udp port 68)' or
-    //     '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)'
-    static sock_filter filter_code[] = {
-        // Load the link layer next payload field.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS,  kEtherTypeOffset),
-
-        // Accept all ARP.
-        // TODO: Figure out how to better filter ARPs on noisy networks.
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   ETHERTYPE_ARP, 16, 0),
-
-        // If IPv4:
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   ETHERTYPE_IP, 0, 9),
-
-        // Check the protocol is UDP.
-        BPF_STMT(BPF_LD  | BPF_B    | BPF_ABS, kIPv4Protocol),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   IPPROTO_UDP, 0, 14),
-
-        // Check this is not a fragment.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS, kIPv4FlagsOffset),
-        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   IP_OFFMASK, 12, 0),
-
-        // Get the IP header length.
-        BPF_STMT(BPF_LDX | BPF_B    | BPF_MSH, kEtherHeaderLen),
-
-        // Check the source port.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPSrcPortIndirectOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 8, 0),
-
-        // Check the destination port.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPDstPortIndirectOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 6, 7),
-
-        // IPv6 ...
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   ETHERTYPE_IPV6, 0, 6),
-        // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ...
-        BPF_STMT(BPF_LD  | BPF_B    | BPF_ABS, kIPv6NextHeader),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   IPPROTO_ICMPV6, 0, 4),
-        // ... and check the ICMPv6 type is one of RS/RA/NS/NA.
-        BPF_STMT(BPF_LD  | BPF_B    | BPF_ABS, kICMPv6TypeOffset),
-        BPF_JUMP(BPF_JMP | BPF_JGE  | BPF_K,   ND_ROUTER_SOLICIT, 0, 2),
-        BPF_JUMP(BPF_JMP | BPF_JGT  | BPF_K,   ND_NEIGHBOR_ADVERT, 1, 0),
-
-        // Accept or reject.
-        BPF_STMT(BPF_RET | BPF_K,              0xffff),
-        BPF_STMT(BPF_RET | BPF_K,              0)
-    };
-    static const sock_fprog filter = {
-        sizeof(filter_code) / sizeof(filter_code[0]),
-        filter_code,
-    };
-
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
-    }
-}
-
-/*
- * JNI registration.
- */
-static const JNINativeMethod gNetworkStackUtilsMethods[] = {
-    /* name, signature, funcPtr */
-    { "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_addArpEntry },
-    { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachDhcpFilter },
-    { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) network_stack_utils_attachRaFilter },
-    { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) network_stack_utils_attachControlPacketFilter },
-};
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
-    JNIEnv *env;
-    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
-        return JNI_ERR;
-    }
-
-    if (jniRegisterNativeMethods(env, NETWORKSTACKUTILS_PKG_NAME,
-            gNetworkStackUtilsMethods, NELEM(gNetworkStackUtilsMethods)) < 0) {
-        return JNI_ERR;
-    }
-
-    return JNI_VERSION_1_6;
-
-}
-}; // namespace android
diff --git a/packages/NetworkStack/proguard.flags b/packages/NetworkStack/proguard.flags
deleted file mode 100644
index c60f6c3..0000000
--- a/packages/NetworkStack/proguard.flags
+++ /dev/null
@@ -1,9 +0,0 @@
--keepclassmembers class android.net.ip.IpClient {
-    static final int CMD_*;
-    static final int EVENT_*;
-}
-
--keepclassmembers class android.net.dhcp.DhcpClient {
-    static final int CMD_*;
-    static final int EVENT_*;
-}
diff --git a/packages/NetworkStack/res/values-mcc460/config.xml b/packages/NetworkStack/res/values-mcc460/config.xml
deleted file mode 100644
index fd4a848..0000000
--- a/packages/NetworkStack/res/values-mcc460/config.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <!-- Network validation URL configuration for devices using a Chinese SIM (MCC 460).
-         The below URLs are often whitelisted by captive portals, so they should not be used in the
-         general case as this could degrade the user experience (portals not detected properly).
-         However in China the default URLs are not accessible in general. The below alternatives
-         should allow users to connect to local networks normally. -->
-    <string name="default_captive_portal_https_url" translatable="false">https://connectivitycheck.gstatic.com/generate_204</string>
-    <string-array name="default_captive_portal_fallback_urls" translatable="false">
-        <item>http://www.googleapis.cn/generate_204</item>
-    </string-array>
-</resources>
diff --git a/packages/NetworkStack/res/values/config.xml b/packages/NetworkStack/res/values/config.xml
deleted file mode 100644
index 478ed6b..0000000
--- a/packages/NetworkStack/res/values/config.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <!--
-    OEMs that wish to change the below settings must do so via a runtime resource overlay package
-    and *NOT* by changing this file. This file is part of the NetworkStack mainline module.
-    The overlays must apply to the config_* values, not the default_* values. The default_*
-    values are meant to be the default when no other configuration is specified.
-    -->
-
-    <!-- DNS probe timeout for network validation. Enough for 3 DNS queries 5 seconds apart. -->
-    <integer name="default_captive_portal_dns_probe_timeout">12500</integer>
-
-    <!-- HTTP URL for network validation, to use for detecting captive portals. -->
-    <string name="default_captive_portal_http_url" translatable="false">http://connectivitycheck.gstatic.com/generate_204</string>
-
-    <!-- HTTPS URL for network validation, to use for confirming internet connectivity. -->
-    <string name="default_captive_portal_https_url" translatable="false">https://www.google.com/generate_204</string>
-
-    <!-- List of fallback URLs to use for detecting captive portals. -->
-    <string-array name="default_captive_portal_fallback_urls" translatable="false">
-        <item>http://www.google.com/gen_204</item>
-        <item>http://play.googleapis.com/generate_204</item>
-    </string-array>
-
-    <!-- List of fallback probe specs to use for detecting captive portals.
-         This is an alternative to fallback URLs that provides more flexibility on detection rules.
-         Empty, so unused by default. -->
-    <string-array name="default_captive_portal_fallback_probe_specs" translatable="false">
-    </string-array>
-
-    <!-- Configuration hooks for the above settings.
-         Empty by default but may be overridden by RROs. -->
-    <integer name="config_captive_portal_dns_probe_timeout"></integer>
-    <!--suppress CheckTagEmptyBody: overlayable resource to use as configuration hook -->
-    <string name="config_captive_portal_http_url" translatable="false"></string>
-    <!--suppress CheckTagEmptyBody: overlayable resource to use as configuration hook -->
-    <string name="config_captive_portal_https_url" translatable="false"></string>
-    <string-array name="config_captive_portal_fallback_urls" translatable="false">
-    </string-array>
-    <string-array name="config_captive_portal_fallback_probe_specs" translatable="false">
-    </string-array>
-
-    <!-- Customized default DNS Servers address. -->
-    <string-array name="config_default_dns_servers" translatable="false">
-    </string-array>
-</resources>
diff --git a/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java b/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java
deleted file mode 100644
index 41715b2..0000000
--- a/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.annotation.NonNull;
-import android.content.Context;
-
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-
-/**
- * service used to communicate with the ip memory store service in network stack,
- * which is running in the same module.
- * @see com.android.server.connectivity.ipmemorystore.IpMemoryStoreService
- * @hide
- */
-public class NetworkStackIpMemoryStore extends IpMemoryStoreClient {
-    @NonNull private final IIpMemoryStore mService;
-
-    public NetworkStackIpMemoryStore(@NonNull final Context context,
-            @NonNull final IIpMemoryStore service) {
-        super(context);
-        mService = service;
-    }
-
-    @Override
-    protected void runWhenServiceReady(Consumer<IIpMemoryStore> cb) throws ExecutionException {
-        cb.accept(mService);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
deleted file mode 100644
index f054319..0000000
--- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java
+++ /dev/null
@@ -1,1981 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.apf;
-
-import static android.net.util.SocketUtils.makePacketSocketAddress;
-import static android.system.OsConstants.AF_PACKET;
-import static android.system.OsConstants.ARPHRD_ETHER;
-import static android.system.OsConstants.ETH_P_ARP;
-import static android.system.OsConstants.ETH_P_IP;
-import static android.system.OsConstants.ETH_P_IPV6;
-import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_TCP;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_RAW;
-
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
-
-import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NattKeepalivePacketDataParcelable;
-import android.net.TcpKeepalivePacketDataParcelable;
-import android.net.apf.ApfGenerator.IllegalInstructionException;
-import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpClient.IpClientCallbacksWrapper;
-import android.net.metrics.ApfProgramEvent;
-import android.net.metrics.ApfStats;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.RaEvent;
-import android.net.util.InterfaceParams;
-import android.net.util.NetworkStackUtils;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.IndentingPrintWriter;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * For networks that support packet filtering via APF programs, {@code ApfFilter}
- * listens for IPv6 ICMPv6 router advertisements (RAs) and generates APF programs to
- * filter out redundant duplicate ones.
- *
- * Threading model:
- * A collection of RAs we've received is kept in mRas. Generating APF programs uses mRas to
- * know what RAs to filter for, thus generating APF programs is dependent on mRas.
- * mRas can be accessed by multiple threads:
- * - ReceiveThread, which listens for RAs and adds them to mRas, and generates APF programs.
- * - callers of:
- *    - setMulticastFilter(), which can cause an APF program to be generated.
- *    - dump(), which dumps mRas among other things.
- *    - shutdown(), which clears mRas.
- * So access to mRas is synchronized.
- *
- * @hide
- */
-public class ApfFilter {
-
-    // Helper class for specifying functional filter parameters.
-    public static class ApfConfiguration {
-        public ApfCapabilities apfCapabilities;
-        public boolean multicastFilter;
-        public boolean ieee802_3Filter;
-        public int[] ethTypeBlackList;
-    }
-
-    // Enums describing the outcome of receiving an RA packet.
-    private static enum ProcessRaResult {
-        MATCH,          // Received RA matched a known RA
-        DROPPED,        // Received RA ignored due to MAX_RAS
-        PARSE_ERROR,    // Received RA could not be parsed
-        ZERO_LIFETIME,  // Received RA had 0 lifetime
-        UPDATE_NEW_RA,  // APF program updated for new RA
-        UPDATE_EXPIRY   // APF program updated for expiry
-    }
-
-    /**
-     * APF packet counters.
-     *
-     * Packet counters are 32bit big-endian values, and allocated near the end of the APF data
-     * buffer, using negative byte offsets, where -4 is equivalent to maximumApfProgramSize - 4,
-     * the last writable 32bit word.
-     */
-    @VisibleForTesting
-    public static enum Counter {
-        RESERVED_OOB,  // Points to offset 0 from the end of the buffer (out-of-bounds)
-        TOTAL_PACKETS,
-        PASSED_ARP,
-        PASSED_DHCP,
-        PASSED_IPV4,
-        PASSED_IPV6_NON_ICMP,
-        PASSED_IPV4_UNICAST,
-        PASSED_IPV6_ICMP,
-        PASSED_IPV6_UNICAST_NON_ICMP,
-        PASSED_ARP_NON_IPV4,
-        PASSED_ARP_UNKNOWN,
-        PASSED_ARP_UNICAST_REPLY,
-        PASSED_NON_IP_UNICAST,
-        DROPPED_ETH_BROADCAST,
-        DROPPED_RA,
-        DROPPED_GARP_REPLY,
-        DROPPED_ARP_OTHER_HOST,
-        DROPPED_IPV4_L2_BROADCAST,
-        DROPPED_IPV4_BROADCAST_ADDR,
-        DROPPED_IPV4_BROADCAST_NET,
-        DROPPED_IPV4_MULTICAST,
-        DROPPED_IPV6_ROUTER_SOLICITATION,
-        DROPPED_IPV6_MULTICAST_NA,
-        DROPPED_IPV6_MULTICAST,
-        DROPPED_IPV6_MULTICAST_PING,
-        DROPPED_IPV6_NON_ICMP_MULTICAST,
-        DROPPED_802_3_FRAME,
-        DROPPED_ETHERTYPE_BLACKLISTED,
-        DROPPED_ARP_REPLY_SPA_NO_HOST,
-        DROPPED_IPV4_KEEPALIVE_ACK,
-        DROPPED_IPV6_KEEPALIVE_ACK,
-        DROPPED_IPV4_NATT_KEEPALIVE;
-
-        // Returns the negative byte offset from the end of the APF data segment for
-        // a given counter.
-        public int offset() {
-            return - this.ordinal() * 4;  // Currently, all counters are 32bit long.
-        }
-
-        // Returns the total size of the data segment in bytes.
-        public static int totalSize() {
-            return (Counter.class.getEnumConstants().length - 1) * 4;
-        }
-    }
-
-    /**
-     * When APFv4 is supported, loads R1 with the offset of the specified counter.
-     */
-    private void maybeSetupCounter(ApfGenerator gen, Counter c) {
-        if (mApfCapabilities.hasDataAccess()) {
-            gen.addLoadImmediate(Register.R1, c.offset());
-        }
-    }
-
-    // When APFv4 is supported, these point to the trampolines generated by emitEpilogue().
-    // Otherwise, they're just aliases for PASS_LABEL and DROP_LABEL.
-    private final String mCountAndPassLabel;
-    private final String mCountAndDropLabel;
-
-    // Thread to listen for RAs.
-    @VisibleForTesting
-    class ReceiveThread extends Thread {
-        private final byte[] mPacket = new byte[1514];
-        private final FileDescriptor mSocket;
-        private final long mStart = SystemClock.elapsedRealtime();
-
-        private int mReceivedRas = 0;
-        private int mMatchingRas = 0;
-        private int mDroppedRas = 0;
-        private int mParseErrors = 0;
-        private int mZeroLifetimeRas = 0;
-        private int mProgramUpdates = 0;
-
-        private volatile boolean mStopped;
-
-        public ReceiveThread(FileDescriptor socket) {
-            mSocket = socket;
-        }
-
-        public void halt() {
-            mStopped = true;
-            // Interrupts the read() call the thread is blocked in.
-            NetworkStackUtils.closeSocketQuietly(mSocket);
-        }
-
-        @Override
-        public void run() {
-            log("begin monitoring");
-            while (!mStopped) {
-                try {
-                    int length = Os.read(mSocket, mPacket, 0, mPacket.length);
-                    updateStats(processRa(mPacket, length));
-                } catch (IOException|ErrnoException e) {
-                    if (!mStopped) {
-                        Log.e(TAG, "Read error", e);
-                    }
-                }
-            }
-            logStats();
-        }
-
-        private void updateStats(ProcessRaResult result) {
-            mReceivedRas++;
-            switch(result) {
-                case MATCH:
-                    mMatchingRas++;
-                    return;
-                case DROPPED:
-                    mDroppedRas++;
-                    return;
-                case PARSE_ERROR:
-                    mParseErrors++;
-                    return;
-                case ZERO_LIFETIME:
-                    mZeroLifetimeRas++;
-                    return;
-                case UPDATE_EXPIRY:
-                    mMatchingRas++;
-                    mProgramUpdates++;
-                    return;
-                case UPDATE_NEW_RA:
-                    mProgramUpdates++;
-                    return;
-            }
-        }
-
-        private void logStats() {
-            final long nowMs = SystemClock.elapsedRealtime();
-            synchronized (this) {
-                final ApfStats stats = new ApfStats.Builder()
-                        .setReceivedRas(mReceivedRas)
-                        .setMatchingRas(mMatchingRas)
-                        .setDroppedRas(mDroppedRas)
-                        .setParseErrors(mParseErrors)
-                        .setZeroLifetimeRas(mZeroLifetimeRas)
-                        .setProgramUpdates(mProgramUpdates)
-                        .setDurationMs(nowMs - mStart)
-                        .setMaxProgramSize(mApfCapabilities.maximumApfProgramSize)
-                        .setProgramUpdatesAll(mNumProgramUpdates)
-                        .setProgramUpdatesAllowingMulticast(mNumProgramUpdatesAllowingMulticast)
-                        .build();
-                mMetricsLog.log(stats);
-                logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS);
-            }
-        }
-    }
-
-    private static final String TAG = "ApfFilter";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
-
-    private static final int ETH_HEADER_LEN = 14;
-    private static final int ETH_DEST_ADDR_OFFSET = 0;
-    private static final int ETH_ETHERTYPE_OFFSET = 12;
-    private static final int ETH_TYPE_MIN = 0x0600;
-    private static final int ETH_TYPE_MAX = 0xFFFF;
-    private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
-            {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
-    // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN.
-    private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2;
-    private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6;
-    // Endianness is not an issue for this constant because the APF interpreter always operates in
-    // network byte order.
-    private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff;
-    private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
-    private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
-    private static final int IPV4_ANY_HOST_ADDRESS = 0;
-    private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255
-    private static final int IPV4_HEADER_LEN = 20; // Without options
-
-    // Traffic class and Flow label are not byte aligned. Luckily we
-    // don't care about either value so we'll consider bytes 1-3 of the
-    // IPv6 header as don't care.
-    private static final int IPV6_FLOW_LABEL_OFFSET = ETH_HEADER_LEN + 1;
-    private static final int IPV6_FLOW_LABEL_LEN = 3;
-    private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
-    private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
-    private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
-    private static final int IPV6_HEADER_LEN = 40;
-    // The IPv6 all nodes address ff02::1
-    private static final byte[] IPV6_ALL_NODES_ADDRESS =
-            { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
-
-    private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
-
-    // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
-    private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
-    private static final int UDP_HEADER_LEN = 8;
-
-    private static final int TCP_HEADER_SIZE_OFFSET = 12;
-
-    private static final int DHCP_CLIENT_PORT = 68;
-    // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
-    private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
-
-    private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
-    private static final byte[] ARP_IPV4_HEADER = {
-            0, 1, // Hardware type: Ethernet (1)
-            8, 0, // Protocol type: IP (0x0800)
-            6,    // Hardware size: 6
-            4,    // Protocol size: 4
-    };
-    private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6;
-    // Opcode: ARP request (0x0001), ARP reply (0x0002)
-    private static final short ARP_OPCODE_REQUEST = 1;
-    private static final short ARP_OPCODE_REPLY = 2;
-    private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
-    private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
-    // Do not log ApfProgramEvents whose actual lifetimes was less than this.
-    private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2;
-    // Limit on the Black List size to cap on program usage for this
-    // TODO: Select a proper max length
-    private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
-
-    private final ApfCapabilities mApfCapabilities;
-    private final IpClientCallbacksWrapper mIpClientCallback;
-    private final InterfaceParams mInterfaceParams;
-    private final IpConnectivityLog mMetricsLog;
-
-    @VisibleForTesting
-    byte[] mHardwareAddress;
-    @VisibleForTesting
-    ReceiveThread mReceiveThread;
-    @GuardedBy("this")
-    private long mUniqueCounter;
-    @GuardedBy("this")
-    private boolean mMulticastFilter;
-    @GuardedBy("this")
-    private boolean mInDozeMode;
-    private final boolean mDrop802_3Frames;
-    private final int[] mEthTypeBlackList;
-
-    // Detects doze mode state transitions.
-    private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
-                PowerManager powerManager =
-                        (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-                final boolean deviceIdle = powerManager.isDeviceIdleMode();
-                setDozeMode(deviceIdle);
-            }
-        }
-    };
-    private final Context mContext;
-
-    // Our IPv4 address, if we have just one, otherwise null.
-    @GuardedBy("this")
-    private byte[] mIPv4Address;
-    // The subnet prefix length of our IPv4 network. Only valid if mIPv4Address is not null.
-    @GuardedBy("this")
-    private int mIPv4PrefixLength;
-
-    @VisibleForTesting
-    ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
-            IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) {
-        mApfCapabilities = config.apfCapabilities;
-        mIpClientCallback = ipClientCallback;
-        mInterfaceParams = ifParams;
-        mMulticastFilter = config.multicastFilter;
-        mDrop802_3Frames = config.ieee802_3Filter;
-        mContext = context;
-
-        if (mApfCapabilities.hasDataAccess()) {
-            mCountAndPassLabel = "countAndPass";
-            mCountAndDropLabel = "countAndDrop";
-        } else {
-            // APFv4 unsupported: turn jumps to the counter trampolines to immediately PASS or DROP,
-            // preserving the original pre-APFv4 behavior.
-            mCountAndPassLabel = ApfGenerator.PASS_LABEL;
-            mCountAndDropLabel = ApfGenerator.DROP_LABEL;
-        }
-
-        // Now fill the black list from the passed array
-        mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
-
-        mMetricsLog = log;
-
-        // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
-        maybeStartFilter();
-
-        // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter.
-        mContext.registerReceiver(mDeviceIdleReceiver,
-                new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
-    }
-
-    public synchronized void setDataSnapshot(byte[] data) {
-        mDataSnapshot = data;
-    }
-
-    private void log(String s) {
-        Log.d(TAG, "(" + mInterfaceParams.name + "): " + s);
-    }
-
-    @GuardedBy("this")
-    private long getUniqueNumberLocked() {
-        return mUniqueCounter++;
-    }
-
-    @GuardedBy("this")
-    private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) {
-        ArrayList<Integer> bl = new ArrayList<Integer>();
-
-        for (int p : ethTypeBlackList) {
-            // Check if the protocol is a valid ether type
-            if ((p < ETH_TYPE_MIN) || (p > ETH_TYPE_MAX)) {
-                continue;
-            }
-
-            // Check if the protocol is not repeated in the passed array
-            if (bl.contains(p)) {
-                continue;
-            }
-
-            // Check if list reach its max size
-            if (bl.size() == APF_MAX_ETH_TYPE_BLACK_LIST_LEN) {
-                Log.w(TAG, "Passed EthType Black List size too large (" + bl.size() +
-                        ") using top " + APF_MAX_ETH_TYPE_BLACK_LIST_LEN + " protocols");
-                break;
-            }
-
-            // Now add the protocol to the list
-            bl.add(p);
-        }
-
-        return bl.stream().mapToInt(Integer::intValue).toArray();
-    }
-
-    /**
-     * Attempt to start listening for RAs and, if RAs are received, generating and installing
-     * filters to ignore useless RAs.
-     */
-    @VisibleForTesting
-    void maybeStartFilter() {
-        FileDescriptor socket;
-        try {
-            mHardwareAddress = mInterfaceParams.macAddr.toByteArray();
-            synchronized(this) {
-                // Clear the APF memory to reset all counters upon connecting to the first AP
-                // in an SSID. This is limited to APFv4 devices because this large write triggers
-                // a crash on some older devices (b/78905546).
-                if (mApfCapabilities.hasDataAccess()) {
-                    byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize];
-                    mIpClientCallback.installPacketFilter(zeroes);
-                }
-
-                // Install basic filters
-                installNewProgramLocked();
-            }
-            socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6);
-            SocketAddress addr = makePacketSocketAddress(
-                    (short) ETH_P_IPV6, mInterfaceParams.index);
-            Os.bind(socket, addr);
-            NetworkStackUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat);
-        } catch(SocketException|ErrnoException e) {
-            Log.e(TAG, "Error starting filter", e);
-            return;
-        }
-        mReceiveThread = new ReceiveThread(socket);
-        mReceiveThread.start();
-    }
-
-    // Returns seconds since device boot.
-    @VisibleForTesting
-    protected long currentTimeSeconds() {
-        return SystemClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS;
-    }
-
-    public static class InvalidRaException extends Exception {
-        public InvalidRaException(String m) {
-            super(m);
-        }
-    }
-
-    // A class to hold information about an RA.
-    @VisibleForTesting
-    class Ra {
-        // From RFC4861:
-        private static final int ICMP6_RA_HEADER_LEN = 16;
-        private static final int ICMP6_RA_CHECKSUM_OFFSET =
-                ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
-        private static final int ICMP6_RA_CHECKSUM_LEN = 2;
-        private static final int ICMP6_RA_OPTION_OFFSET =
-                ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
-        private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
-                ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
-        private static final int ICMP6_RA_ROUTER_LIFETIME_LEN = 2;
-        // Prefix information option.
-        private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
-        private static final int ICMP6_PREFIX_OPTION_LEN = 32;
-        private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
-        private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN = 4;
-        private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
-        private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4;
-
-        // From RFC6106: Recursive DNS Server option
-        private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
-        // From RFC6106: DNS Search List option
-        private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
-
-        // From RFC4191: Route Information option
-        private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
-        // Above three options all have the same format:
-        private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
-        private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
-
-        // Note: mPacket's position() cannot be assumed to be reset.
-        private final ByteBuffer mPacket;
-        // List of binary ranges that include the whole packet except the lifetimes.
-        // Pairs consist of offset and length.
-        private final ArrayList<Pair<Integer, Integer>> mNonLifetimes =
-                new ArrayList<Pair<Integer, Integer>>();
-        // Minimum lifetime in packet
-        long mMinLifetime;
-        // When the packet was last captured, in seconds since Unix Epoch
-        long mLastSeen;
-
-        // For debugging only. Offsets into the packet where PIOs are.
-        private final ArrayList<Integer> mPrefixOptionOffsets = new ArrayList<>();
-
-        // For debugging only. Offsets into the packet where RDNSS options are.
-        private final ArrayList<Integer> mRdnssOptionOffsets = new ArrayList<>();
-
-        // For debugging only. How many times this RA was seen.
-        int seenCount = 0;
-
-        // For debugging only. Returns the hex representation of the last matching packet.
-        String getLastMatchingPacket() {
-            return HexDump.toHexString(mPacket.array(), 0, mPacket.capacity(),
-                    false /* lowercase */);
-        }
-
-        // For debugging only. Returns the string representation of the IPv6 address starting at
-        // position pos in the packet.
-        private String IPv6AddresstoString(int pos) {
-            try {
-                byte[] array = mPacket.array();
-                // Can't just call copyOfRange() and see if it throws, because if it reads past the
-                // end it pads with zeros instead of throwing.
-                if (pos < 0 || pos + 16 > array.length || pos + 16 < pos) {
-                    return "???";
-                }
-                byte[] addressBytes = Arrays.copyOfRange(array, pos, pos + 16);
-                InetAddress address = (Inet6Address) InetAddress.getByAddress(addressBytes);
-                return address.getHostAddress();
-            } catch (UnsupportedOperationException e) {
-                // array() failed. Cannot happen, mPacket is array-backed and read-write.
-                return "???";
-            } catch (ClassCastException|UnknownHostException e) {
-                // Cannot happen.
-                return "???";
-            }
-        }
-
-        // Can't be static because it's in a non-static inner class.
-        // TODO: Make this static once RA is its own class.
-        private void prefixOptionToString(StringBuffer sb, int offset) {
-            String prefix = IPv6AddresstoString(offset + 16);
-            int length = getUint8(mPacket, offset + 2);
-            long valid = getUint32(mPacket, offset + 4);
-            long preferred = getUint32(mPacket, offset + 8);
-            sb.append(String.format("%s/%d %ds/%ds ", prefix, length, valid, preferred));
-        }
-
-        private void rdnssOptionToString(StringBuffer sb, int offset) {
-            int optLen = getUint8(mPacket, offset + 1) * 8;
-            if (optLen < 24) return;  // Malformed or empty.
-            long lifetime = getUint32(mPacket, offset + 4);
-            int numServers = (optLen - 8) / 16;
-            sb.append("DNS ").append(lifetime).append("s");
-            for (int server = 0; server < numServers; server++) {
-                sb.append(" ").append(IPv6AddresstoString(offset + 8 + 16 * server));
-            }
-        }
-
-        public String toString() {
-            try {
-                StringBuffer sb = new StringBuffer();
-                sb.append(String.format("RA %s -> %s %ds ",
-                        IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET),
-                        IPv6AddresstoString(IPV6_DEST_ADDR_OFFSET),
-                        getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET)));
-                for (int i: mPrefixOptionOffsets) {
-                    prefixOptionToString(sb, i);
-                }
-                for (int i: mRdnssOptionOffsets) {
-                    rdnssOptionToString(sb, i);
-                }
-                return sb.toString();
-            } catch (BufferUnderflowException|IndexOutOfBoundsException e) {
-                return "<Malformed RA>";
-            }
-        }
-
-        /**
-         * Add a binary range of the packet that does not include a lifetime to mNonLifetimes.
-         * Assumes mPacket.position() is as far as we've parsed the packet.
-         * @param lastNonLifetimeStart offset within packet of where the last binary range of
-         *                             data not including a lifetime.
-         * @param lifetimeOffset offset from mPacket.position() to the next lifetime data.
-         * @param lifetimeLength length of the next lifetime data.
-         * @return offset within packet of where the next binary range of data not including
-         *         a lifetime. This can be passed into the next invocation of this function
-         *         via {@code lastNonLifetimeStart}.
-         */
-        private int addNonLifetime(int lastNonLifetimeStart, int lifetimeOffset,
-                int lifetimeLength) {
-            lifetimeOffset += mPacket.position();
-            mNonLifetimes.add(new Pair<Integer, Integer>(lastNonLifetimeStart,
-                    lifetimeOffset - lastNonLifetimeStart));
-            return lifetimeOffset + lifetimeLength;
-        }
-
-        private int addNonLifetimeU32(int lastNonLifetimeStart) {
-            return addNonLifetime(lastNonLifetimeStart,
-                    ICMP6_4_BYTE_LIFETIME_OFFSET, ICMP6_4_BYTE_LIFETIME_LEN);
-        }
-
-        // Note that this parses RA and may throw InvalidRaException (from
-        // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException
-        // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with
-        // specifications.
-        Ra(byte[] packet, int length) throws InvalidRaException {
-            if (length < ICMP6_RA_OPTION_OFFSET) {
-                throw new InvalidRaException("Not an ICMP6 router advertisement");
-            }
-
-            mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length));
-            mLastSeen = currentTimeSeconds();
-
-            // Sanity check packet in case a packet arrives before we attach RA filter
-            // to our packet socket. b/29586253
-            if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 ||
-                    getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 ||
-                    getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMPV6_ROUTER_ADVERTISEMENT) {
-                throw new InvalidRaException("Not an ICMP6 router advertisement");
-            }
-
-
-            RaEvent.Builder builder = new RaEvent.Builder();
-
-            // Ignore the flow label and low 4 bits of traffic class.
-            int lastNonLifetimeStart = addNonLifetime(0,
-                    IPV6_FLOW_LABEL_OFFSET,
-                    IPV6_FLOW_LABEL_LEN);
-
-            // Ignore the checksum.
-            lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
-                    ICMP6_RA_CHECKSUM_OFFSET,
-                    ICMP6_RA_CHECKSUM_LEN);
-
-            // Parse router lifetime
-            lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
-                    ICMP6_RA_ROUTER_LIFETIME_OFFSET,
-                    ICMP6_RA_ROUTER_LIFETIME_LEN);
-            builder.updateRouterLifetime(getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET));
-
-            // Ensures that the RA is not truncated.
-            mPacket.position(ICMP6_RA_OPTION_OFFSET);
-            while (mPacket.hasRemaining()) {
-                final int position = mPacket.position();
-                final int optionType = getUint8(mPacket, position);
-                final int optionLength = getUint8(mPacket, position + 1) * 8;
-                long lifetime;
-                switch (optionType) {
-                    case ICMP6_PREFIX_OPTION_TYPE:
-                        // Parse valid lifetime
-                        lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
-                                ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
-                                ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN);
-                        lifetime = getUint32(mPacket,
-                                position + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET);
-                        builder.updatePrefixValidLifetime(lifetime);
-                        // Parse preferred lifetime
-                        lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
-                                ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
-                                ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN);
-                        lifetime = getUint32(mPacket,
-                                position + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET);
-                        builder.updatePrefixPreferredLifetime(lifetime);
-                        mPrefixOptionOffsets.add(position);
-                        break;
-                    // These three options have the same lifetime offset and size, and
-                    // are processed with the same specialized addNonLifetimeU32:
-                    case ICMP6_RDNSS_OPTION_TYPE:
-                        mRdnssOptionOffsets.add(position);
-                        lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
-                        lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
-                        builder.updateRdnssLifetime(lifetime);
-                        break;
-                    case ICMP6_ROUTE_INFO_OPTION_TYPE:
-                        lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
-                        lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
-                        builder.updateRouteInfoLifetime(lifetime);
-                        break;
-                    case ICMP6_DNSSL_OPTION_TYPE:
-                        lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
-                        lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
-                        builder.updateDnsslLifetime(lifetime);
-                        break;
-                    default:
-                        // RFC4861 section 4.2 dictates we ignore unknown options for fowards
-                        // compatibility.
-                        break;
-                }
-                if (optionLength <= 0) {
-                    throw new InvalidRaException(String.format(
-                        "Invalid option length opt=%d len=%d", optionType, optionLength));
-                }
-                mPacket.position(position + optionLength);
-            }
-            // Mark non-lifetime bytes since last lifetime.
-            addNonLifetime(lastNonLifetimeStart, 0, 0);
-            mMinLifetime = minLifetime(packet, length);
-            mMetricsLog.log(builder.build());
-        }
-
-        // Ignoring lifetimes (which may change) does {@code packet} match this RA?
-        boolean matches(byte[] packet, int length) {
-            if (length != mPacket.capacity()) return false;
-            byte[] referencePacket = mPacket.array();
-            for (Pair<Integer, Integer> nonLifetime : mNonLifetimes) {
-                for (int i = nonLifetime.first; i < (nonLifetime.first + nonLifetime.second); i++) {
-                    if (packet[i] != referencePacket[i]) return false;
-                }
-            }
-            return true;
-        }
-
-        // What is the minimum of all lifetimes within {@code packet} in seconds?
-        // Precondition: matches(packet, length) already returned true.
-        long minLifetime(byte[] packet, int length) {
-            long minLifetime = Long.MAX_VALUE;
-            // Wrap packet in ByteBuffer so we can read big-endian values easily
-            ByteBuffer byteBuffer = ByteBuffer.wrap(packet);
-            for (int i = 0; (i + 1) < mNonLifetimes.size(); i++) {
-                int offset = mNonLifetimes.get(i).first + mNonLifetimes.get(i).second;
-
-                // The flow label is in mNonLifetimes, but it's not a lifetime.
-                if (offset == IPV6_FLOW_LABEL_OFFSET) {
-                    continue;
-                }
-
-                // The checksum is in mNonLifetimes, but it's not a lifetime.
-                if (offset == ICMP6_RA_CHECKSUM_OFFSET) {
-                    continue;
-                }
-
-                final int lifetimeLength = mNonLifetimes.get(i+1).first - offset;
-                final long optionLifetime;
-                switch (lifetimeLength) {
-                    case 2:
-                        optionLifetime = getUint16(byteBuffer, offset);
-                        break;
-                    case 4:
-                        optionLifetime = getUint32(byteBuffer, offset);
-                        break;
-                    default:
-                        throw new IllegalStateException("bogus lifetime size " + lifetimeLength);
-                }
-                minLifetime = Math.min(minLifetime, optionLifetime);
-            }
-            return minLifetime;
-        }
-
-        // How many seconds does this RA's have to live, taking into account the fact
-        // that we might have seen it a while ago.
-        long currentLifetime() {
-            return mMinLifetime - (currentTimeSeconds() - mLastSeen);
-        }
-
-        boolean isExpired() {
-            // TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll
-            // have to calculate the filter lifetime specially as a fraction of 0 is still 0.
-            return currentLifetime() <= 0;
-        }
-
-        // Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped.
-        // Jump to the next filter if packet doesn't match this RA.
-        @GuardedBy("ApfFilter.this")
-        long generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
-            String nextFilterLabel = "Ra" + getUniqueNumberLocked();
-            // Skip if packet is not the right size
-            gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
-            gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel);
-            int filterLifetime = (int)(currentLifetime() / FRACTION_OF_LIFETIME_TO_FILTER);
-            // Skip filter if expired
-            gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
-            gen.addJumpIfR0GreaterThan(filterLifetime, nextFilterLabel);
-            for (int i = 0; i < mNonLifetimes.size(); i++) {
-                // Generate code to match the packet bytes
-                Pair<Integer, Integer> nonLifetime = mNonLifetimes.get(i);
-                // Don't generate JNEBS instruction for 0 bytes as it always fails the
-                // ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm - 1) check where cmp_imm is
-                // the number of bytes to compare. nonLifetime is zero between the
-                // valid and preferred lifetimes in the prefix option.
-                if (nonLifetime.second != 0) {
-                    gen.addLoadImmediate(Register.R0, nonLifetime.first);
-                    gen.addJumpIfBytesNotEqual(Register.R0,
-                            Arrays.copyOfRange(mPacket.array(), nonLifetime.first,
-                                               nonLifetime.first + nonLifetime.second),
-                            nextFilterLabel);
-                }
-                // Generate code to test the lifetimes haven't gone down too far
-                if ((i + 1) < mNonLifetimes.size()) {
-                    Pair<Integer, Integer> nextNonLifetime = mNonLifetimes.get(i + 1);
-                    int offset = nonLifetime.first + nonLifetime.second;
-
-                    // Skip the Flow label.
-                    if (offset == IPV6_FLOW_LABEL_OFFSET) {
-                        continue;
-                    }
-                    // Skip the checksum.
-                    if (offset == ICMP6_RA_CHECKSUM_OFFSET) {
-                        continue;
-                    }
-                    int length = nextNonLifetime.first - offset;
-                    switch (length) {
-                        case 4: gen.addLoad32(Register.R0, offset); break;
-                        case 2: gen.addLoad16(Register.R0, offset); break;
-                        default: throw new IllegalStateException("bogus lifetime size " + length);
-                    }
-                    gen.addJumpIfR0LessThan(filterLifetime, nextFilterLabel);
-                }
-            }
-            maybeSetupCounter(gen, Counter.DROPPED_RA);
-            gen.addJump(mCountAndDropLabel);
-            gen.defineLabel(nextFilterLabel);
-            return filterLifetime;
-        }
-    }
-
-    // TODO: Refactor these subclasses to avoid so much repetition.
-    private abstract static class KeepalivePacket {
-        // Note that the offset starts from IP header.
-        // These must be added ether header length when generating program.
-        static final int IP_HEADER_OFFSET = 0;
-        static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12;
-
-        // Append a filter for this keepalive ack to {@code gen}.
-        // Jump to drop if it matches the keepalive ack.
-        // Jump to the next filter if packet doesn't match the keepalive ack.
-        abstract void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException;
-    }
-
-    // A class to hold NAT-T keepalive ack information.
-    private class NattKeepaliveResponse extends KeepalivePacket {
-        static final int UDP_LENGTH_OFFSET = 4;
-        static final int UDP_HEADER_LEN = 8;
-
-        protected class NattKeepaliveResponseData {
-            public final byte[] srcAddress;
-            public final int srcPort;
-            public final byte[] dstAddress;
-            public final int dstPort;
-
-            NattKeepaliveResponseData(final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
-                srcAddress = sentKeepalivePacket.dstAddress;
-                srcPort = sentKeepalivePacket.dstPort;
-                dstAddress = sentKeepalivePacket.srcAddress;
-                dstPort = sentKeepalivePacket.srcPort;
-            }
-        }
-
-        protected final NattKeepaliveResponseData mPacket;
-        protected final byte[] mSrcDstAddr;
-        protected final byte[] mPortFingerprint;
-        // NAT-T keepalive packet
-        protected final byte[] mPayload = {(byte) 0xff};
-
-        NattKeepaliveResponse(final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
-            mPacket = new NattKeepaliveResponseData(sentKeepalivePacket);
-            mSrcDstAddr = concatArrays(mPacket.srcAddress, mPacket.dstAddress);
-            mPortFingerprint = generatePortFingerprint(mPacket.srcPort, mPacket.dstPort);
-        }
-
-        byte[] generatePortFingerprint(int srcPort, int dstPort) {
-            final ByteBuffer fp = ByteBuffer.allocate(4);
-            fp.order(ByteOrder.BIG_ENDIAN);
-            fp.putShort((short) srcPort);
-            fp.putShort((short) dstPort);
-            return fp.array();
-        }
-
-        @Override
-        void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
-            final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked();
-
-            gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
-
-            // A NAT-T keepalive packet contains 1 byte payload with the value 0xff
-            // Check payload length is 1
-            gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-            gen.addAdd(UDP_HEADER_LEN);
-            gen.addSwap();
-            gen.addLoad16(Register.R0, IPV4_TOTAL_LENGTH_OFFSET);
-            gen.addNeg(Register.R1);
-            gen.addAddR1();
-            gen.addJumpIfR0NotEquals(1, nextFilterLabel);
-
-            // Check that the ports match
-            gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-            gen.addAdd(ETH_HEADER_LEN);
-            gen.addJumpIfBytesNotEqual(Register.R0, mPortFingerprint, nextFilterLabel);
-
-            // Payload offset = R0 + UDP header length
-            gen.addAdd(UDP_HEADER_LEN);
-            gen.addJumpIfBytesNotEqual(Register.R0, mPayload, nextFilterLabel);
-
-            maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE);
-            gen.addJump(mCountAndDropLabel);
-            gen.defineLabel(nextFilterLabel);
-        }
-
-        public String toString() {
-            try {
-                return String.format("%s -> %s",
-                        NetworkStackUtils.addressAndPortToString(
-                                InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort),
-                        NetworkStackUtils.addressAndPortToString(
-                                InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort));
-            } catch (UnknownHostException e) {
-                return "Unknown host";
-            }
-        }
-    }
-
-    // A class to hold TCP keepalive ack information.
-    private abstract static class TcpKeepaliveAck extends KeepalivePacket {
-        protected static class TcpKeepaliveAckData {
-            public final byte[] srcAddress;
-            public final int srcPort;
-            public final byte[] dstAddress;
-            public final int dstPort;
-            public final int seq;
-            public final int ack;
-
-            // Create the characteristics of the ack packet from the sent keepalive packet.
-            TcpKeepaliveAckData(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
-                srcAddress = sentKeepalivePacket.dstAddress;
-                srcPort = sentKeepalivePacket.dstPort;
-                dstAddress = sentKeepalivePacket.srcAddress;
-                dstPort = sentKeepalivePacket.srcPort;
-                seq = sentKeepalivePacket.ack;
-                ack = sentKeepalivePacket.seq + 1;
-            }
-        }
-
-        protected final TcpKeepaliveAckData mPacket;
-        protected final byte[] mSrcDstAddr;
-        protected final byte[] mPortSeqAckFingerprint;
-
-        TcpKeepaliveAck(final TcpKeepaliveAckData packet, final byte[] srcDstAddr) {
-            mPacket = packet;
-            mSrcDstAddr = srcDstAddr;
-            mPortSeqAckFingerprint = generatePortSeqAckFingerprint(mPacket.srcPort,
-                    mPacket.dstPort, mPacket.seq, mPacket.ack);
-        }
-
-        static byte[] generatePortSeqAckFingerprint(int srcPort, int dstPort, int seq, int ack) {
-            final ByteBuffer fp = ByteBuffer.allocate(12);
-            fp.order(ByteOrder.BIG_ENDIAN);
-            fp.putShort((short) srcPort);
-            fp.putShort((short) dstPort);
-            fp.putInt(seq);
-            fp.putInt(ack);
-            return fp.array();
-        }
-
-        public String toString() {
-            try {
-                return String.format("%s -> %s , seq=%d, ack=%d",
-                        NetworkStackUtils.addressAndPortToString(
-                                InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort),
-                        NetworkStackUtils.addressAndPortToString(
-                                InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort),
-                        Integer.toUnsignedLong(mPacket.seq),
-                        Integer.toUnsignedLong(mPacket.ack));
-            } catch (UnknownHostException e) {
-                return "Unknown host";
-            }
-        }
-
-        // Append a filter for this keepalive ack to {@code gen}.
-        // Jump to drop if it matches the keepalive ack.
-        // Jump to the next filter if packet doesn't match the keepalive ack.
-        abstract void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException;
-    }
-
-    private class TcpKeepaliveAckV4 extends TcpKeepaliveAck {
-
-        TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
-            this(new TcpKeepaliveAckData(sentKeepalivePacket));
-        }
-        TcpKeepaliveAckV4(final TcpKeepaliveAckData packet) {
-            super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
-        }
-
-        @Override
-        void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
-            final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked();
-
-            gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
-
-            // Skip to the next filter if it's not zero-sized :
-            // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0
-            // Load the IP header size into R1
-            gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-            // Load the TCP header size into R0 (it's indexed by R1)
-            gen.addLoad8Indexed(Register.R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET);
-            // Size offset is in the top nibble, but it must be multiplied by 4, and the two
-            // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2.
-            gen.addRightShift(2);
-            // R0 += R1 -> R0 contains TCP + IP headers length
-            gen.addAddR1();
-            // Load IPv4 total length
-            gen.addLoad16(Register.R1, IPV4_TOTAL_LENGTH_OFFSET);
-            gen.addNeg(Register.R0);
-            gen.addAddR1();
-            gen.addJumpIfR0NotEquals(0, nextFilterLabel);
-            // Add IPv4 header length
-            gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-            gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN);
-            gen.addAddR1();
-            gen.addJumpIfBytesNotEqual(Register.R0, mPortSeqAckFingerprint, nextFilterLabel);
-
-            maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK);
-            gen.addJump(mCountAndDropLabel);
-            gen.defineLabel(nextFilterLabel);
-        }
-    }
-
-    private class TcpKeepaliveAckV6 extends TcpKeepaliveAck {
-        TcpKeepaliveAckV6(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
-            this(new TcpKeepaliveAckData(sentKeepalivePacket));
-        }
-        TcpKeepaliveAckV6(final TcpKeepaliveAckData packet) {
-            super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
-        }
-
-        @Override
-        void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
-            throw new UnsupportedOperationException("IPv6 TCP Keepalive is not supported yet");
-        }
-    }
-
-    // Maximum number of RAs to filter for.
-    private static final int MAX_RAS = 10;
-
-    @GuardedBy("this")
-    private ArrayList<Ra> mRas = new ArrayList<>();
-    @GuardedBy("this")
-    private SparseArray<KeepalivePacket> mKeepalivePackets = new SparseArray<>();
-
-    // There is always some marginal benefit to updating the installed APF program when an RA is
-    // seen because we can extend the program's lifetime slightly, but there is some cost to
-    // updating the program, so don't bother unless the program is going to expire soon. This
-    // constant defines "soon" in seconds.
-    private static final long MAX_PROGRAM_LIFETIME_WORTH_REFRESHING = 30;
-    // We don't want to filter an RA for it's whole lifetime as it'll be expired by the time we ever
-    // see a refresh.  Using half the lifetime might be a good idea except for the fact that
-    // packets may be dropped, so let's use 6.
-    private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6;
-
-    // When did we last install a filter program? In seconds since Unix Epoch.
-    @GuardedBy("this")
-    private long mLastTimeInstalledProgram;
-    // How long should the last installed filter program live for? In seconds.
-    @GuardedBy("this")
-    private long mLastInstalledProgramMinLifetime;
-    @GuardedBy("this")
-    private ApfProgramEvent.Builder mLastInstallEvent;
-
-    // For debugging only. The last program installed.
-    @GuardedBy("this")
-    private byte[] mLastInstalledProgram;
-
-    /**
-     * For debugging only. Contains the latest APF buffer snapshot captured from the firmware.
-     *
-     * A typical size for this buffer is 4KB. It is present only if the WiFi HAL supports
-     * IWifiStaIface#readApfPacketFilterData(), and the APF interpreter advertised support for
-     * the opcodes to access the data buffer (LDDW and STDW).
-     */
-    @GuardedBy("this") @Nullable
-    private byte[] mDataSnapshot;
-
-    // How many times the program was updated since we started.
-    @GuardedBy("this")
-    private int mNumProgramUpdates = 0;
-    // How many times the program was updated since we started for allowing multicast traffic.
-    @GuardedBy("this")
-    private int mNumProgramUpdatesAllowingMulticast = 0;
-
-    /**
-     * Generate filter code to process ARP packets. Execution of this code ends in either the
-     * DROP_LABEL or PASS_LABEL and does not fall off the end.
-     * Preconditions:
-     *  - Packet being filtered is ARP
-     */
-    @GuardedBy("this")
-    private void generateArpFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
-        // Here's a basic summary of what the ARP filter program does:
-        //
-        // if not ARP IPv4
-        //   pass
-        // if not ARP IPv4 reply or request
-        //   pass
-        // if ARP reply source ip is 0.0.0.0
-        //   drop
-        // if unicast ARP reply
-        //   pass
-        // if interface has no IPv4 address
-        //   if target ip is 0.0.0.0
-        //      drop
-        // else
-        //   if target ip is not the interface ip
-        //      drop
-        // pass
-
-        final String checkTargetIPv4 = "checkTargetIPv4";
-
-        // Pass if not ARP IPv4.
-        gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
-        maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4);
-        gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel);
-
-        // Pass if unknown ARP opcode.
-        gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
-        gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
-        maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN);
-        gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel);
-
-        // Drop if ARP reply source IP is 0.0.0.0
-        gen.addLoad32(Register.R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
-        maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST);
-        gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
-
-        // Pass if unicast reply.
-        gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-        maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
-
-        // Either a unicast request, a unicast reply, or a broadcast reply.
-        gen.defineLabel(checkTargetIPv4);
-        if (mIPv4Address == null) {
-            // When there is no IPv4 address, drop GARP replies (b/29404209).
-            gen.addLoad32(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
-            maybeSetupCounter(gen, Counter.DROPPED_GARP_REPLY);
-            gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
-        } else {
-            // When there is an IPv4 address, drop unicast/broadcast requests
-            // and broadcast replies with a different target IPv4 address.
-            gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
-            maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
-            gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel);
-        }
-
-        maybeSetupCounter(gen, Counter.PASSED_ARP);
-        gen.addJump(mCountAndPassLabel);
-    }
-
-    /**
-     * Generate filter code to process IPv4 packets. Execution of this code ends in either the
-     * DROP_LABEL or PASS_LABEL and does not fall off the end.
-     * Preconditions:
-     *  - Packet being filtered is IPv4
-     */
-    @GuardedBy("this")
-    private void generateIPv4FilterLocked(ApfGenerator gen) throws IllegalInstructionException {
-        // Here's a basic summary of what the IPv4 filter program does:
-        //
-        // if filtering multicast (i.e. multicast lock not held):
-        //   if it's DHCP destined to our MAC:
-        //     pass
-        //   if it's L2 broadcast:
-        //     drop
-        //   if it's IPv4 multicast:
-        //     drop
-        //   if it's IPv4 broadcast:
-        //     drop
-        // if keepalive ack
-        //   drop
-        // pass
-
-        if (mMulticastFilter) {
-            final String skipDhcpv4Filter = "skip_dhcp_v4_filter";
-
-            // Pass DHCP addressed to us.
-            // Check it's UDP.
-            gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
-            gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter);
-            // Check it's not a fragment. This matches the BPF filter installed by the DHCP client.
-            gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET);
-            gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter);
-            // Check it's addressed to DHCP client port.
-            gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-            gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET);
-            gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter);
-            // Check it's DHCP to our MAC address.
-            gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
-            // NOTE: Relies on R1 containing IPv4 header offset.
-            gen.addAddR1();
-            gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter);
-            maybeSetupCounter(gen, Counter.PASSED_DHCP);
-            gen.addJump(mCountAndPassLabel);
-
-            // Drop all multicasts/broadcasts.
-            gen.defineLabel(skipDhcpv4Filter);
-
-            // If IPv4 destination address is in multicast range, drop.
-            gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET);
-            gen.addAnd(0xf0);
-            maybeSetupCounter(gen, Counter.DROPPED_IPV4_MULTICAST);
-            gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel);
-
-            // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
-            maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR);
-            gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET);
-            gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel);
-            if (mIPv4Address != null && mIPv4PrefixLength < 31) {
-                maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET);
-                int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength);
-                gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel);
-            }
-
-            // If any TCP keepalive filter matches, drop
-            generateV4KeepaliveFilters(gen);
-
-            // If any NAT-T keepalive filter matches, drop
-            generateV4NattKeepaliveFilters(gen);
-
-            // Otherwise, this is an IPv4 unicast, pass
-            // If L2 broadcast packet, drop.
-            // TODO: can we invert this condition to fall through to the common pass case below?
-            maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
-            gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
-            maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
-            gen.addJump(mCountAndDropLabel);
-        } else {
-            generateV4KeepaliveFilters(gen);
-            generateV4NattKeepaliveFilters(gen);
-        }
-
-        // Otherwise, pass
-        maybeSetupCounter(gen, Counter.PASSED_IPV4);
-        gen.addJump(mCountAndPassLabel);
-    }
-
-    private void generateKeepaliveFilters(ApfGenerator gen, Class<?> filterType, int proto,
-            int offset, String label) throws IllegalInstructionException {
-        final boolean haveKeepaliveResponses = NetworkStackUtils.any(mKeepalivePackets,
-                ack -> filterType.isInstance(ack));
-
-        // If no keepalive packets of this type
-        if (!haveKeepaliveResponses) return;
-
-        // If not the right proto, skip keepalive filters
-        gen.addLoad8(Register.R0, offset);
-        gen.addJumpIfR0NotEquals(proto, label);
-
-        // Drop Keepalive responses
-        for (int i = 0; i < mKeepalivePackets.size(); ++i) {
-            final KeepalivePacket response = mKeepalivePackets.valueAt(i);
-            if (filterType.isInstance(response)) response.generateFilterLocked(gen);
-        }
-
-        gen.defineLabel(label);
-    }
-
-    private void generateV4KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException {
-        generateKeepaliveFilters(gen, TcpKeepaliveAckV4.class, IPPROTO_TCP, IPV4_PROTOCOL_OFFSET,
-                "skip_v4_keepalive_filter");
-    }
-
-    private void generateV4NattKeepaliveFilters(ApfGenerator gen)
-            throws IllegalInstructionException {
-        generateKeepaliveFilters(gen, NattKeepaliveResponse.class,
-                IPPROTO_UDP, IPV4_PROTOCOL_OFFSET, "skip_v4_nattkeepalive_filter");
-    }
-
-    /**
-     * Generate filter code to process IPv6 packets. Execution of this code ends in either the
-     * DROP_LABEL or PASS_LABEL, or falls off the end for ICMPv6 packets.
-     * Preconditions:
-     *  - Packet being filtered is IPv6
-     */
-    @GuardedBy("this")
-    private void generateIPv6FilterLocked(ApfGenerator gen) throws IllegalInstructionException {
-        // Here's a basic summary of what the IPv6 filter program does:
-        //
-        // if we're dropping multicast
-        //   if it's not IPCMv6 or it's ICMPv6 but we're in doze mode:
-        //     if it's multicast:
-        //       drop
-        //     pass
-        // if it's ICMPv6 RS to any:
-        //   drop
-        // if it's ICMPv6 NA to ff02::1:
-        //   drop
-        // if keepalive ack
-        //   drop
-
-        gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET);
-
-        // Drop multicast if the multicast filter is enabled.
-        if (mMulticastFilter) {
-            final String skipIPv6MulticastFilterLabel = "skipIPv6MulticastFilter";
-            final String dropAllIPv6MulticastsLabel = "dropAllIPv6Multicast";
-
-            // While in doze mode, drop ICMPv6 multicast pings, let the others pass.
-            // While awake, let all ICMPv6 multicasts through.
-            if (mInDozeMode) {
-                // Not ICMPv6? -> Proceed to multicast filtering
-                gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, dropAllIPv6MulticastsLabel);
-
-                // ICMPv6 but not ECHO? -> Skip the multicast filter.
-                // (ICMPv6 ECHO requests will go through the multicast filter below).
-                gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
-                gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel);
-            } else {
-                gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel);
-            }
-
-            // Drop all other packets sent to ff00::/8 (multicast prefix).
-            gen.defineLabel(dropAllIPv6MulticastsLabel);
-            maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
-            gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET);
-            gen.addJumpIfR0Equals(0xff, mCountAndDropLabel);
-            // If any keepalive filter matches, drop
-            generateV6KeepaliveFilters(gen);
-            // Not multicast. Pass.
-            maybeSetupCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP);
-            gen.addJump(mCountAndPassLabel);
-            gen.defineLabel(skipIPv6MulticastFilterLabel);
-        } else {
-            generateV6KeepaliveFilters(gen);
-            // If not ICMPv6, pass.
-            maybeSetupCounter(gen, Counter.PASSED_IPV6_NON_ICMP);
-            gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel);
-        }
-
-        // If we got this far, the packet is ICMPv6.  Drop some specific types.
-
-        // Add unsolicited multicast neighbor announcements filter
-        String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
-        gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
-        // Drop all router solicitations (b/32833400)
-        maybeSetupCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
-        gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel);
-        // If not neighbor announcements, skip filter.
-        gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel);
-        // If to ff02::1, drop.
-        // TODO: Drop only if they don't contain the address of on-link neighbours.
-        gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
-        gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS,
-                skipUnsolicitedMulticastNALabel);
-        maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
-        gen.addJump(mCountAndDropLabel);
-        gen.defineLabel(skipUnsolicitedMulticastNALabel);
-    }
-
-    private void generateV6KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException {
-        generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET,
-                "skip_v6_keepalive_filter");
-    }
-
-    /**
-     * Begin generating an APF program to:
-     * <ul>
-     * <li>Drop/Pass 802.3 frames (based on policy)
-     * <li>Drop packets with EtherType within the Black List
-     * <li>Drop ARP requests not for us, if mIPv4Address is set,
-     * <li>Drop IPv4 broadcast packets, except DHCP destined to our MAC,
-     * <li>Drop IPv4 multicast packets, if mMulticastFilter,
-     * <li>Pass all other IPv4 packets,
-     * <li>Drop all broadcast non-IP non-ARP packets.
-     * <li>Pass all non-ICMPv6 IPv6 packets,
-     * <li>Pass all non-IPv4 and non-IPv6 packets,
-     * <li>Drop IPv6 ICMPv6 NAs to ff02::1.
-     * <li>Drop IPv6 ICMPv6 RSs.
-     * <li>Filter IPv4 packets (see generateIPv4FilterLocked())
-     * <li>Filter IPv6 packets (see generateIPv6FilterLocked())
-     * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows
-     *     insertion of RA filters here, or if there aren't any, just passes the packets.
-     * </ul>
-     */
-    @GuardedBy("this")
-    private ApfGenerator emitPrologueLocked() throws IllegalInstructionException {
-        // This is guaranteed to succeed because of the check in maybeCreate.
-        ApfGenerator gen = new ApfGenerator(mApfCapabilities.apfVersionSupported);
-
-        if (mApfCapabilities.hasDataAccess()) {
-            // Increment TOTAL_PACKETS
-            maybeSetupCounter(gen, Counter.TOTAL_PACKETS);
-            gen.addLoadData(Register.R0, 0);  // load counter
-            gen.addAdd(1);
-            gen.addStoreData(Register.R0, 0);  // write-back counter
-        }
-
-        // Here's a basic summary of what the initial program does:
-        //
-        // if it's a 802.3 Frame (ethtype < 0x0600):
-        //    drop or pass based on configurations
-        // if it has a ether-type that belongs to the black list
-        //    drop
-        // if it's ARP:
-        //   insert ARP filter to drop or pass these appropriately
-        // if it's IPv4:
-        //   insert IPv4 filter to drop or pass these appropriately
-        // if it's not IPv6:
-        //   if it's broadcast:
-        //     drop
-        //   pass
-        // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets
-
-        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
-
-        if (mDrop802_3Frames) {
-            // drop 802.3 frames (ethtype < 0x0600)
-            maybeSetupCounter(gen, Counter.DROPPED_802_3_FRAME);
-            gen.addJumpIfR0LessThan(ETH_TYPE_MIN, mCountAndDropLabel);
-        }
-
-        // Handle ether-type black list
-        maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_BLACKLISTED);
-        for (int p : mEthTypeBlackList) {
-            gen.addJumpIfR0Equals(p, mCountAndDropLabel);
-        }
-
-        // Add ARP filters:
-        String skipArpFiltersLabel = "skipArpFilters";
-        gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel);
-        generateArpFilterLocked(gen);
-        gen.defineLabel(skipArpFiltersLabel);
-
-        // Add IPv4 filters:
-        String skipIPv4FiltersLabel = "skipIPv4Filters";
-        // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did not
-        // execute the ARP filter, since that filter does not fall through, but either drops or
-        // passes.
-        gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel);
-        generateIPv4FilterLocked(gen);
-        gen.defineLabel(skipIPv4FiltersLabel);
-
-        // Check for IPv6:
-        // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did not
-        // execute the ARP or IPv4 filters, since those filters do not fall through, but either
-        // drop or pass.
-        String ipv6FilterLabel = "IPv6Filters";
-        gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);
-
-        // Drop non-IP non-ARP broadcasts, pass the rest
-        gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-        maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
-        maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
-        gen.addJump(mCountAndDropLabel);
-
-        // Add IPv6 filters:
-        gen.defineLabel(ipv6FilterLabel);
-        generateIPv6FilterLocked(gen);
-        return gen;
-    }
-
-    /**
-     * Append packet counting epilogue to the APF program.
-     *
-     * Currently, the epilogue consists of two trampolines which count passed and dropped packets
-     * before jumping to the actual PASS and DROP labels.
-     */
-    @GuardedBy("this")
-    private void emitEpilogue(ApfGenerator gen) throws IllegalInstructionException {
-        // If APFv4 is unsupported, no epilogue is necessary: if execution reached this far, it
-        // will just fall-through to the PASS label.
-        if (!mApfCapabilities.hasDataAccess()) return;
-
-        // Execution will reach the bottom of the program if none of the filters match,
-        // which will pass the packet to the application processor.
-        maybeSetupCounter(gen, Counter.PASSED_IPV6_ICMP);
-
-        // Append the count & pass trampoline, which increments the counter at the data address
-        // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting
-        // the entire sequence inline for every counter.
-        gen.defineLabel(mCountAndPassLabel);
-        gen.addLoadData(Register.R0, 0);   // R0 = *(R1 + 0)
-        gen.addAdd(1);                     // R0++
-        gen.addStoreData(Register.R0, 0);  // *(R1 + 0) = R0
-        gen.addJump(gen.PASS_LABEL);
-
-        // Same as above for the count & drop trampoline.
-        gen.defineLabel(mCountAndDropLabel);
-        gen.addLoadData(Register.R0, 0);   // R0 = *(R1 + 0)
-        gen.addAdd(1);                     // R0++
-        gen.addStoreData(Register.R0, 0);  // *(R1 + 0) = R0
-        gen.addJump(gen.DROP_LABEL);
-    }
-
-    /**
-     * Generate and install a new filter program.
-     */
-    @GuardedBy("this")
-    @VisibleForTesting
-    void installNewProgramLocked() {
-        purgeExpiredRasLocked();
-        ArrayList<Ra> rasToFilter = new ArrayList<>();
-        final byte[] program;
-        long programMinLifetime = Long.MAX_VALUE;
-        long maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize;
-        if (mApfCapabilities.hasDataAccess()) {
-            // Reserve space for the counters.
-            maximumApfProgramSize -= Counter.totalSize();
-        }
-
-        try {
-            // Step 1: Determine how many RA filters we can fit in the program.
-            ApfGenerator gen = emitPrologueLocked();
-
-            // The epilogue normally goes after the RA filters, but add it early to include its
-            // length when estimating the total.
-            emitEpilogue(gen);
-
-            // Can't fit the program even without any RA filters?
-            if (gen.programLengthOverEstimate() > maximumApfProgramSize) {
-                Log.e(TAG, "Program exceeds maximum size " + maximumApfProgramSize);
-                return;
-            }
-
-            for (Ra ra : mRas) {
-                ra.generateFilterLocked(gen);
-                // Stop if we get too big.
-                if (gen.programLengthOverEstimate() > maximumApfProgramSize) break;
-                rasToFilter.add(ra);
-            }
-
-            // Step 2: Actually generate the program
-            gen = emitPrologueLocked();
-            for (Ra ra : rasToFilter) {
-                programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen));
-            }
-            emitEpilogue(gen);
-            program = gen.generate();
-        } catch (IllegalInstructionException|IllegalStateException e) {
-            Log.e(TAG, "Failed to generate APF program.", e);
-            return;
-        }
-        final long now = currentTimeSeconds();
-        mLastTimeInstalledProgram = now;
-        mLastInstalledProgramMinLifetime = programMinLifetime;
-        mLastInstalledProgram = program;
-        mNumProgramUpdates++;
-
-        if (VDBG) {
-            hexDump("Installing filter: ", program, program.length);
-        }
-        mIpClientCallback.installPacketFilter(program);
-        logApfProgramEventLocked(now);
-        mLastInstallEvent = new ApfProgramEvent.Builder()
-                .setLifetime(programMinLifetime)
-                .setFilteredRas(rasToFilter.size())
-                .setCurrentRas(mRas.size())
-                .setProgramLength(program.length)
-                .setFlags(mIPv4Address != null, mMulticastFilter);
-    }
-
-    @GuardedBy("this")
-    private void logApfProgramEventLocked(long now) {
-        if (mLastInstallEvent == null) {
-            return;
-        }
-        ApfProgramEvent.Builder ev = mLastInstallEvent;
-        mLastInstallEvent = null;
-        final long actualLifetime = now - mLastTimeInstalledProgram;
-        ev.setActualLifetime(actualLifetime);
-        if (actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) {
-            return;
-        }
-        mMetricsLog.log(ev.build());
-    }
-
-    /**
-     * Returns {@code true} if a new program should be installed because the current one dies soon.
-     */
-    private boolean shouldInstallnewProgram() {
-        long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
-        return expiry < currentTimeSeconds() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING;
-    }
-
-    private void hexDump(String msg, byte[] packet, int length) {
-        log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */));
-    }
-
-    @GuardedBy("this")
-    private void purgeExpiredRasLocked() {
-        for (int i = 0; i < mRas.size();) {
-            if (mRas.get(i).isExpired()) {
-                log("Expiring " + mRas.get(i));
-                mRas.remove(i);
-            } else {
-                i++;
-            }
-        }
-    }
-
-    /**
-     * Process an RA packet, updating the list of known RAs and installing a new APF program
-     * if the current APF program should be updated.
-     * @return a ProcessRaResult enum describing what action was performed.
-     */
-    @VisibleForTesting
-    synchronized ProcessRaResult processRa(byte[] packet, int length) {
-        if (VDBG) hexDump("Read packet = ", packet, length);
-
-        // Have we seen this RA before?
-        for (int i = 0; i < mRas.size(); i++) {
-            Ra ra = mRas.get(i);
-            if (ra.matches(packet, length)) {
-                if (VDBG) log("matched RA " + ra);
-                // Update lifetimes.
-                ra.mLastSeen = currentTimeSeconds();
-                ra.mMinLifetime = ra.minLifetime(packet, length);
-                ra.seenCount++;
-
-                // Keep mRas in LRU order so as to prioritize generating filters for recently seen
-                // RAs. LRU prioritizes this because RA filters are generated in order from mRas
-                // until the filter program exceeds the maximum filter program size allowed by the
-                // chipset, so RAs appearing earlier in mRas are more likely to make it into the
-                // filter program.
-                // TODO: consider sorting the RAs in order of increasing expiry time as well.
-                // Swap to front of array.
-                mRas.add(0, mRas.remove(i));
-
-                // If the current program doesn't expire for a while, don't update.
-                if (shouldInstallnewProgram()) {
-                    installNewProgramLocked();
-                    return ProcessRaResult.UPDATE_EXPIRY;
-                }
-                return ProcessRaResult.MATCH;
-            }
-        }
-        purgeExpiredRasLocked();
-        // TODO: figure out how to proceed when we've received more then MAX_RAS RAs.
-        if (mRas.size() >= MAX_RAS) {
-            return ProcessRaResult.DROPPED;
-        }
-        final Ra ra;
-        try {
-            ra = new Ra(packet, length);
-        } catch (Exception e) {
-            Log.e(TAG, "Error parsing RA", e);
-            return ProcessRaResult.PARSE_ERROR;
-        }
-        // Ignore 0 lifetime RAs.
-        if (ra.isExpired()) {
-            return ProcessRaResult.ZERO_LIFETIME;
-        }
-        log("Adding " + ra);
-        mRas.add(ra);
-        installNewProgramLocked();
-        return ProcessRaResult.UPDATE_NEW_RA;
-    }
-
-    /**
-     * Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for packet
-     * filtering using APF programs.
-     */
-    public static ApfFilter maybeCreate(Context context, ApfConfiguration config,
-            InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback) {
-        if (context == null || config == null || ifParams == null) return null;
-        ApfCapabilities apfCapabilities =  config.apfCapabilities;
-        if (apfCapabilities == null) return null;
-        if (apfCapabilities.apfVersionSupported == 0) return null;
-        if (apfCapabilities.maximumApfProgramSize < 512) {
-            Log.e(TAG, "Unacceptably small APF limit: " + apfCapabilities.maximumApfProgramSize);
-            return null;
-        }
-        // For now only support generating programs for Ethernet frames. If this restriction is
-        // lifted:
-        //   1. the program generator will need its offsets adjusted.
-        //   2. the packet filter attached to our packet socket will need its offset adjusted.
-        if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null;
-        if (!ApfGenerator.supportsVersion(apfCapabilities.apfVersionSupported)) {
-            Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
-            return null;
-        }
-
-        return new ApfFilter(context, config, ifParams, ipClientCallback, new IpConnectivityLog());
-    }
-
-    public synchronized void shutdown() {
-        if (mReceiveThread != null) {
-            log("shutting down");
-            mReceiveThread.halt();  // Also closes socket.
-            mReceiveThread = null;
-        }
-        mRas.clear();
-        mContext.unregisterReceiver(mDeviceIdleReceiver);
-    }
-
-    public synchronized void setMulticastFilter(boolean isEnabled) {
-        if (mMulticastFilter == isEnabled) return;
-        mMulticastFilter = isEnabled;
-        if (!isEnabled) {
-            mNumProgramUpdatesAllowingMulticast++;
-        }
-        installNewProgramLocked();
-    }
-
-    @VisibleForTesting
-    public synchronized void setDozeMode(boolean isEnabled) {
-        if (mInDozeMode == isEnabled) return;
-        mInDozeMode = isEnabled;
-        installNewProgramLocked();
-    }
-
-    /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */
-    private static LinkAddress findIPv4LinkAddress(LinkProperties lp) {
-        LinkAddress ipv4Address = null;
-        for (LinkAddress address : lp.getLinkAddresses()) {
-            if (!(address.getAddress() instanceof Inet4Address)) {
-                continue;
-            }
-            if (ipv4Address != null && !ipv4Address.isSameAddressAs(address)) {
-                // More than one IPv4 address, abort.
-                return null;
-            }
-            ipv4Address = address;
-        }
-        return ipv4Address;
-    }
-
-    public synchronized void setLinkProperties(LinkProperties lp) {
-        // NOTE: Do not keep a copy of LinkProperties as it would further duplicate state.
-        final LinkAddress ipv4Address = findIPv4LinkAddress(lp);
-        final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null;
-        final int prefix = (ipv4Address != null) ? ipv4Address.getPrefixLength() : 0;
-        if ((prefix == mIPv4PrefixLength) && Arrays.equals(addr, mIPv4Address)) {
-            return;
-        }
-        mIPv4Address = addr;
-        mIPv4PrefixLength = prefix;
-        installNewProgramLocked();
-    }
-
-    /**
-     * Add TCP keepalive ack packet filter.
-     * This will add a filter to drop acks to the keepalive packet passed as an argument.
-     *
-     * @param slot The index used to access the filter.
-     * @param sentKeepalivePacket The attributes of the sent keepalive packet.
-     */
-    public synchronized void addTcpKeepalivePacketFilter(final int slot,
-            final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
-        log("Adding keepalive ack(" + slot + ")");
-        if (null != mKeepalivePackets.get(slot)) {
-            throw new IllegalArgumentException("Keepalive slot " + slot + " is occupied");
-        }
-        final int ipVersion = sentKeepalivePacket.srcAddress.length == 4 ? 4 : 6;
-        mKeepalivePackets.put(slot, (ipVersion == 4)
-                ? new TcpKeepaliveAckV4(sentKeepalivePacket)
-                : new TcpKeepaliveAckV6(sentKeepalivePacket));
-        installNewProgramLocked();
-    }
-
-    /**
-     * Add NAT-T keepalive packet filter.
-     * This will add a filter to drop NAT-T keepalive packet which is passed as an argument.
-     *
-     * @param slot The index used to access the filter.
-     * @param sentKeepalivePacket The attributes of the sent keepalive packet.
-     */
-    public synchronized void addNattKeepalivePacketFilter(final int slot,
-            final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
-        log("Adding NAT-T keepalive packet(" + slot + ")");
-        if (null != mKeepalivePackets.get(slot)) {
-            throw new IllegalArgumentException("NAT-T Keepalive slot " + slot + " is occupied");
-        }
-        if (sentKeepalivePacket.srcAddress.length != 4) {
-            throw new IllegalArgumentException("NAT-T keepalive is only supported on IPv4");
-        }
-        mKeepalivePackets.put(slot, new NattKeepaliveResponse(sentKeepalivePacket));
-        installNewProgramLocked();
-    }
-
-    /**
-     * Remove keepalive packet filter.
-     *
-     * @param slot The index used to access the filter.
-     */
-    public synchronized void removeKeepalivePacketFilter(int slot) {
-        log("Removing keepalive packet(" + slot + ")");
-        mKeepalivePackets.remove(slot);
-        installNewProgramLocked();
-    }
-
-    static public long counterValue(byte[] data, Counter counter)
-            throws ArrayIndexOutOfBoundsException {
-        // Follow the same wrap-around addressing scheme of the interpreter.
-        int offset = counter.offset();
-        if (offset < 0) {
-            offset = data.length + offset;
-        }
-
-        // Decode 32bit big-endian integer into a long so we can count up beyond 2^31.
-        long value = 0;
-        for (int i = 0; i < 4; i++) {
-            value = value << 8 | (data[offset] & 0xFF);
-            offset++;
-        }
-        return value;
-    }
-
-    public synchronized void dump(IndentingPrintWriter pw) {
-        pw.println("Capabilities: " + mApfCapabilities);
-        pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED"));
-        pw.println("Multicast: " + (mMulticastFilter ? "DROP" : "ALLOW"));
-        try {
-            pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress());
-        } catch (UnknownHostException|NullPointerException e) {}
-
-        if (mLastTimeInstalledProgram == 0) {
-            pw.println("No program installed.");
-            return;
-        }
-        pw.println("Program updates: " + mNumProgramUpdates);
-        pw.println(String.format(
-                "Last program length %d, installed %ds ago, lifetime %ds",
-                mLastInstalledProgram.length, currentTimeSeconds() - mLastTimeInstalledProgram,
-                mLastInstalledProgramMinLifetime));
-
-        pw.println("RA filters:");
-        pw.increaseIndent();
-        for (Ra ra: mRas) {
-            pw.println(ra);
-            pw.increaseIndent();
-            pw.println(String.format(
-                    "Seen: %d, last %ds ago", ra.seenCount, currentTimeSeconds() - ra.mLastSeen));
-            if (DBG) {
-                pw.println("Last match:");
-                pw.increaseIndent();
-                pw.println(ra.getLastMatchingPacket());
-                pw.decreaseIndent();
-            }
-            pw.decreaseIndent();
-        }
-        pw.decreaseIndent();
-
-        pw.println("TCP Keepalive filters:");
-        pw.increaseIndent();
-        for (int i = 0; i < mKeepalivePackets.size(); ++i) {
-            final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i);
-            if (keepalivePacket instanceof TcpKeepaliveAck) {
-                pw.print("Slot ");
-                pw.print(mKeepalivePackets.keyAt(i));
-                pw.print(": ");
-                pw.println(keepalivePacket);
-            }
-        }
-        pw.decreaseIndent();
-
-        pw.println("NAT-T Keepalive filters:");
-        pw.increaseIndent();
-        for (int i = 0; i < mKeepalivePackets.size(); ++i) {
-            final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i);
-            if (keepalivePacket instanceof NattKeepaliveResponse) {
-                pw.print("Slot ");
-                pw.print(mKeepalivePackets.keyAt(i));
-                pw.print(": ");
-                pw.println(keepalivePacket);
-            }
-        }
-        pw.decreaseIndent();
-
-        if (DBG) {
-            pw.println("Last program:");
-            pw.increaseIndent();
-            pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */));
-            pw.decreaseIndent();
-        }
-
-        pw.println("APF packet counters: ");
-        pw.increaseIndent();
-        if (!mApfCapabilities.hasDataAccess()) {
-            pw.println("APF counters not supported");
-        } else if (mDataSnapshot == null) {
-            pw.println("No last snapshot.");
-        } else {
-            try {
-                Counter[] counters = Counter.class.getEnumConstants();
-                for (Counter c : Arrays.asList(counters).subList(1, counters.length)) {
-                    long value = counterValue(mDataSnapshot, c);
-                    // Only print non-zero counters
-                    if (value != 0) {
-                        pw.println(c.toString() + ": " + value);
-                    }
-                }
-            } catch (ArrayIndexOutOfBoundsException e) {
-                pw.println("Uh-oh: " + e);
-            }
-            if (VDBG) {
-                pw.println("Raw data dump: ");
-                pw.println(HexDump.dumpHexString(mDataSnapshot));
-            }
-        }
-        pw.decreaseIndent();
-    }
-
-    // TODO: move to android.net.NetworkUtils
-    @VisibleForTesting
-    public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) {
-        return bytesToBEInt(addrBytes) | (int) (Integer.toUnsignedLong(-1) >>> prefixLength);
-    }
-
-    private static int uint8(byte b) {
-        return b & 0xff;
-    }
-
-    private static int getUint16(ByteBuffer buffer, int position) {
-        return buffer.getShort(position) & 0xffff;
-    }
-
-    private static long getUint32(ByteBuffer buffer, int position) {
-        return Integer.toUnsignedLong(buffer.getInt(position));
-    }
-
-    private static int getUint8(ByteBuffer buffer, int position) {
-        return uint8(buffer.get(position));
-    }
-
-    private static int bytesToBEInt(byte[] bytes) {
-        return (uint8(bytes[0]) << 24)
-                + (uint8(bytes[1]) << 16)
-                + (uint8(bytes[2]) << 8)
-                + (uint8(bytes[3]));
-    }
-
-    private static byte[] concatArrays(final byte[]... arr) {
-        int size = 0;
-        for (byte[] a : arr) {
-            size += a.length;
-        }
-        final byte[] result = new byte[size];
-        int offset = 0;
-        for (byte[] a : arr) {
-            System.arraycopy(a, 0, result, offset, a.length);
-            offset += a.length;
-        }
-        return result;
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java
deleted file mode 100644
index 44ce2db..0000000
--- a/packages/NetworkStack/src/android/net/apf/ApfGenerator.java
+++ /dev/null
@@ -1,937 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.apf;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * APF assembler/generator.  A tool for generating an APF program.
- *
- * Call add*() functions to add instructions to the program, then call
- * {@link generate} to get the APF bytecode for the program.
- *
- * @hide
- */
-public class ApfGenerator {
-    /**
-     * This exception is thrown when an attempt is made to generate an illegal instruction.
-     */
-    public static class IllegalInstructionException extends Exception {
-        IllegalInstructionException(String msg) {
-            super(msg);
-        }
-    }
-    private enum Opcodes {
-        LABEL(-1),
-        LDB(1),    // Load 1 byte from immediate offset, e.g. "ldb R0, [5]"
-        LDH(2),    // Load 2 bytes from immediate offset, e.g. "ldh R0, [5]"
-        LDW(3),    // Load 4 bytes from immediate offset, e.g. "ldw R0, [5]"
-        LDBX(4),   // Load 1 byte from immediate offset plus register, e.g. "ldbx R0, [5]R0"
-        LDHX(5),   // Load 2 byte from immediate offset plus register, e.g. "ldhx R0, [5]R0"
-        LDWX(6),   // Load 4 byte from immediate offset plus register, e.g. "ldwx R0, [5]R0"
-        ADD(7),    // Add, e.g. "add R0,5"
-        MUL(8),    // Multiply, e.g. "mul R0,5"
-        DIV(9),    // Divide, e.g. "div R0,5"
-        AND(10),   // And, e.g. "and R0,5"
-        OR(11),    // Or, e.g. "or R0,5"
-        SH(12),    // Left shift, e.g, "sh R0, 5" or "sh R0, -5" (shifts right)
-        LI(13),    // Load immediate, e.g. "li R0,5" (immediate encoded as signed value)
-        JMP(14),   // Jump, e.g. "jmp label"
-        JEQ(15),   // Compare equal and branch, e.g. "jeq R0,5,label"
-        JNE(16),   // Compare not equal and branch, e.g. "jne R0,5,label"
-        JGT(17),   // Compare greater than and branch, e.g. "jgt R0,5,label"
-        JLT(18),   // Compare less than and branch, e.g. "jlt R0,5,label"
-        JSET(19),  // Compare any bits set and branch, e.g. "jset R0,5,label"
-        JNEBS(20), // Compare not equal byte sequence, e.g. "jnebs R0,5,label,0x1122334455"
-        EXT(21),   // Followed by immediate indicating ExtendedOpcodes.
-        LDDW(22),  // Load 4 bytes from data memory address (register + immediate): "lddw R0, [5]R1"
-        STDW(23);  // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1"
-
-        final int value;
-
-        private Opcodes(int value) {
-            this.value = value;
-        }
-    }
-    // Extended opcodes. Primary opcode is Opcodes.EXT. ExtendedOpcodes are encoded in the immediate
-    // field.
-    private enum ExtendedOpcodes {
-        LDM(0),   // Load from memory, e.g. "ldm R0,5"
-        STM(16),  // Store to memory, e.g. "stm R0,5"
-        NOT(32),  // Not, e.g. "not R0"
-        NEG(33),  // Negate, e.g. "neg R0"
-        SWAP(34), // Swap, e.g. "swap R0,R1"
-        MOVE(35);  // Move, e.g. "move R0,R1"
-
-        final int value;
-
-        private ExtendedOpcodes(int value) {
-            this.value = value;
-        }
-    }
-    public enum Register {
-        R0(0),
-        R1(1);
-
-        final int value;
-
-        private Register(int value) {
-            this.value = value;
-        }
-    }
-    private class Instruction {
-        private final byte mOpcode;   // A "Opcode" value.
-        private final byte mRegister; // A "Register" value.
-        private boolean mHasImm;
-        private byte mImmSize;
-        private boolean mImmSigned;
-        private int mImm;
-        // When mOpcode is a jump:
-        private byte mTargetLabelSize;
-        private String mTargetLabel;
-        // When mOpcode == Opcodes.LABEL:
-        private String mLabel;
-        // When mOpcode == Opcodes.JNEBS:
-        private byte[] mCompareBytes;
-        // Offset in bytes from the beginning of this program. Set by {@link ApfGenerator#generate}.
-        int offset;
-
-        Instruction(Opcodes opcode, Register register) {
-            mOpcode = (byte)opcode.value;
-            mRegister = (byte)register.value;
-        }
-
-        Instruction(Opcodes opcode) {
-            this(opcode, Register.R0);
-        }
-
-        void setImm(int imm, boolean signed) {
-            mHasImm = true;
-            mImm = imm;
-            mImmSigned = signed;
-            mImmSize = calculateImmSize(imm, signed);
-        }
-
-        void setUnsignedImm(int imm) {
-            setImm(imm, false);
-        }
-
-        void setSignedImm(int imm) {
-            setImm(imm, true);
-        }
-
-        void setLabel(String label) throws IllegalInstructionException {
-            if (mLabels.containsKey(label)) {
-                throw new IllegalInstructionException("duplicate label " + label);
-            }
-            if (mOpcode != Opcodes.LABEL.value) {
-                throw new IllegalStateException("adding label to non-label instruction");
-            }
-            mLabel = label;
-            mLabels.put(label, this);
-        }
-
-        void setTargetLabel(String label) {
-            mTargetLabel = label;
-            mTargetLabelSize = 4; // May shrink later on in generate().
-        }
-
-        void setCompareBytes(byte[] bytes) {
-            if (mOpcode != Opcodes.JNEBS.value) {
-                throw new IllegalStateException("adding compare bytes to non-JNEBS instruction");
-            }
-            mCompareBytes = bytes;
-        }
-
-        /**
-         * @return size of instruction in bytes.
-         */
-        int size() {
-            if (mOpcode == Opcodes.LABEL.value) {
-                return 0;
-            }
-            int size = 1;
-            if (mHasImm) {
-                size += generatedImmSize();
-            }
-            if (mTargetLabel != null) {
-                size += generatedImmSize();
-            }
-            if (mCompareBytes != null) {
-                size += mCompareBytes.length;
-            }
-            return size;
-        }
-
-        /**
-         * Resize immediate value field so that it's only as big as required to
-         * contain the offset of the jump destination.
-         * @return {@code true} if shrunk.
-         */
-        boolean shrink() throws IllegalInstructionException {
-            if (mTargetLabel == null) {
-                return false;
-            }
-            int oldSize = size();
-            int oldTargetLabelSize = mTargetLabelSize;
-            mTargetLabelSize = calculateImmSize(calculateTargetLabelOffset(), false);
-            if (mTargetLabelSize > oldTargetLabelSize) {
-                throw new IllegalStateException("instruction grew");
-            }
-            return size() < oldSize;
-        }
-
-        /**
-         * Assemble value for instruction size field.
-         */
-        private byte generateImmSizeField() {
-            byte immSize = generatedImmSize();
-            // Encode size field to fit in 2 bits: 0->0, 1->1, 2->2, 3->4.
-            return immSize == 4 ? 3 : immSize;
-        }
-
-        /**
-         * Assemble first byte of generated instruction.
-         */
-        private byte generateInstructionByte() {
-            byte sizeField = generateImmSizeField();
-            return (byte)((mOpcode << 3) | (sizeField << 1) | mRegister);
-        }
-
-        /**
-         * Write {@code value} at offset {@code writingOffset} into {@code bytecode}.
-         * {@link generatedImmSize} bytes are written. {@code value} is truncated to
-         * {@code generatedImmSize} bytes. {@code value} is treated simply as a
-         * 32-bit value, so unsigned values should be zero extended and the truncation
-         * should simply throw away their zero-ed upper bits, and signed values should
-         * be sign extended and the truncation should simply throw away their signed
-         * upper bits.
-         */
-        private int writeValue(int value, byte[] bytecode, int writingOffset) {
-            for (int i = generatedImmSize() - 1; i >= 0; i--) {
-                bytecode[writingOffset++] = (byte)((value >> (i * 8)) & 255);
-            }
-            return writingOffset;
-        }
-
-        /**
-         * Generate bytecode for this instruction at offset {@link offset}.
-         */
-        void generate(byte[] bytecode) throws IllegalInstructionException {
-            if (mOpcode == Opcodes.LABEL.value) {
-                return;
-            }
-            int writingOffset = offset;
-            bytecode[writingOffset++] = generateInstructionByte();
-            if (mTargetLabel != null) {
-                writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset);
-            }
-            if (mHasImm) {
-                writingOffset = writeValue(mImm, bytecode, writingOffset);
-            }
-            if (mCompareBytes != null) {
-                System.arraycopy(mCompareBytes, 0, bytecode, writingOffset, mCompareBytes.length);
-                writingOffset += mCompareBytes.length;
-            }
-            if ((writingOffset - offset) != size()) {
-                throw new IllegalStateException("wrote " + (writingOffset - offset) +
-                        " but should have written " + size());
-            }
-        }
-
-        /**
-         * Calculate the size of either the immediate field or the target label field, if either is
-         * present. Most instructions have either an immediate or a target label field, but for the
-         * instructions that have both, the size of the target label field must be the same as the
-         * size of the immediate field, because there is only one length field in the instruction
-         * byte, hence why this function simply takes the maximum of the two sizes, so neither is
-         * truncated.
-         */
-        private byte generatedImmSize() {
-            return mImmSize > mTargetLabelSize ? mImmSize : mTargetLabelSize;
-        }
-
-        private int calculateTargetLabelOffset() throws IllegalInstructionException {
-            Instruction targetLabelInstruction;
-            if (mTargetLabel == DROP_LABEL) {
-                targetLabelInstruction = mDropLabel;
-            } else if (mTargetLabel == PASS_LABEL) {
-                targetLabelInstruction = mPassLabel;
-            } else {
-                targetLabelInstruction = mLabels.get(mTargetLabel);
-            }
-            if (targetLabelInstruction == null) {
-                throw new IllegalInstructionException("label not found: " + mTargetLabel);
-            }
-            // Calculate distance from end of this instruction to instruction.offset.
-            final int targetLabelOffset = targetLabelInstruction.offset - (offset + size());
-            if (targetLabelOffset < 0) {
-                throw new IllegalInstructionException("backward branches disallowed; label: " +
-                        mTargetLabel);
-            }
-            return targetLabelOffset;
-        }
-
-        private byte calculateImmSize(int imm, boolean signed) {
-            if (imm == 0) {
-                return 0;
-            }
-            if (signed && (imm >= -128 && imm <= 127) ||
-                    !signed && (imm >= 0 && imm <= 255)) {
-                return 1;
-            }
-            if (signed && (imm >= -32768 && imm <= 32767) ||
-                    !signed && (imm >= 0 && imm <= 65535)) {
-                return 2;
-            }
-            return 4;
-        }
-    }
-
-    /**
-     * Jump to this label to terminate the program and indicate the packet
-     * should be dropped.
-     */
-    public static final String DROP_LABEL = "__DROP__";
-
-    /**
-     * Jump to this label to terminate the program and indicate the packet
-     * should be passed to the AP.
-     */
-    public static final String PASS_LABEL = "__PASS__";
-
-    /**
-     * Number of memory slots available for access via APF stores to memory and loads from memory.
-     * The memory slots are numbered 0 to {@code MEMORY_SLOTS} - 1. This must be kept in sync with
-     * the APF interpreter.
-     */
-    public static final int MEMORY_SLOTS = 16;
-
-    /**
-     * Memory slot number that is prefilled with the IPv4 header length.
-     * Note that this memory slot may be overwritten by a program that
-     * executes stores to this memory slot. This must be kept in sync with
-     * the APF interpreter.
-     */
-    public static final int IPV4_HEADER_SIZE_MEMORY_SLOT = 13;
-
-    /**
-     * Memory slot number that is prefilled with the size of the packet being filtered in bytes.
-     * Note that this memory slot may be overwritten by a program that
-     * executes stores to this memory slot. This must be kept in sync with the APF interpreter.
-     */
-    public static final int PACKET_SIZE_MEMORY_SLOT = 14;
-
-    /**
-     * Memory slot number that is prefilled with the age of the filter in seconds. The age of the
-     * filter is the time since the filter was installed until now.
-     * Note that this memory slot may be overwritten by a program that
-     * executes stores to this memory slot. This must be kept in sync with the APF interpreter.
-     */
-    public static final int FILTER_AGE_MEMORY_SLOT = 15;
-
-    /**
-     * First memory slot containing prefilled values. Can be used in range comparisons to determine
-     * if memory slot index is within prefilled slots.
-     */
-    public static final int FIRST_PREFILLED_MEMORY_SLOT = IPV4_HEADER_SIZE_MEMORY_SLOT;
-
-    /**
-     * Last memory slot containing prefilled values. Can be used in range comparisons to determine
-     * if memory slot index is within prefilled slots.
-     */
-    public static final int LAST_PREFILLED_MEMORY_SLOT = FILTER_AGE_MEMORY_SLOT;
-
-    // This version number syncs up with APF_VERSION in hardware/google/apf/apf_interpreter.h
-    private static final int MIN_APF_VERSION = 2;
-
-    private final ArrayList<Instruction> mInstructions = new ArrayList<Instruction>();
-    private final HashMap<String, Instruction> mLabels = new HashMap<String, Instruction>();
-    private final Instruction mDropLabel = new Instruction(Opcodes.LABEL);
-    private final Instruction mPassLabel = new Instruction(Opcodes.LABEL);
-    private final int mVersion;
-    private boolean mGenerated;
-
-    /**
-     * Creates an ApfGenerator instance which is able to emit instructions for the specified
-     * {@code version} of the APF interpreter. Throws {@code IllegalInstructionException} if
-     * the requested version is unsupported.
-     */
-    ApfGenerator(int version) throws IllegalInstructionException {
-        mVersion = version;
-        requireApfVersion(MIN_APF_VERSION);
-    }
-
-    /**
-     * Returns true if the ApfGenerator supports the specified {@code version}, otherwise false.
-     */
-    public static boolean supportsVersion(int version) {
-        return version >= MIN_APF_VERSION;
-    }
-
-    private void requireApfVersion(int minimumVersion) throws IllegalInstructionException {
-        if (mVersion < minimumVersion) {
-            throw new IllegalInstructionException("Requires APF >= " + minimumVersion);
-        }
-    }
-
-    private void addInstruction(Instruction instruction) {
-        if (mGenerated) {
-            throw new IllegalStateException("Program already generated");
-        }
-        mInstructions.add(instruction);
-    }
-
-    /**
-     * Define a label at the current end of the program. Jumps can jump to this label. Labels are
-     * their own separate instructions, though with size 0. This facilitates having labels with
-     * no corresponding code to execute, for example a label at the end of a program. For example
-     * an {@link ApfGenerator} might be passed to a function that adds a filter like so:
-     * <pre>
-     *   load from packet
-     *   compare loaded data, jump if not equal to "next_filter"
-     *   load from packet
-     *   compare loaded data, jump if not equal to "next_filter"
-     *   jump to drop label
-     *   define "next_filter" here
-     * </pre>
-     * In this case "next_filter" may not have any generated code associated with it.
-     */
-    public ApfGenerator defineLabel(String name) throws IllegalInstructionException {
-        Instruction instruction = new Instruction(Opcodes.LABEL);
-        instruction.setLabel(name);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an unconditional jump instruction to the end of the program.
-     */
-    public ApfGenerator addJump(String target) {
-        Instruction instruction = new Instruction(Opcodes.JMP);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to load the byte at offset {@code offset}
-     * bytes from the beginning of the packet into {@code register}.
-     */
-    public ApfGenerator addLoad8(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDB, register);
-        instruction.setUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to load 16-bits at offset {@code offset}
-     * bytes from the beginning of the packet into {@code register}.
-     */
-    public ApfGenerator addLoad16(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDH, register);
-        instruction.setUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to load 32-bits at offset {@code offset}
-     * bytes from the beginning of the packet into {@code register}.
-     */
-    public ApfGenerator addLoad32(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDW, register);
-        instruction.setUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to load a byte from the packet into
-     * {@code register}. The offset of the loaded byte from the beginning of the packet is
-     * the sum of {@code offset} and the value in register R1.
-     */
-    public ApfGenerator addLoad8Indexed(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDBX, register);
-        instruction.setUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to load 16-bits from the packet into
-     * {@code register}. The offset of the loaded 16-bits from the beginning of the packet is
-     * the sum of {@code offset} and the value in register R1.
-     */
-    public ApfGenerator addLoad16Indexed(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDHX, register);
-        instruction.setUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to load 32-bits from the packet into
-     * {@code register}. The offset of the loaded 32-bits from the beginning of the packet is
-     * the sum of {@code offset} and the value in register R1.
-     */
-    public ApfGenerator addLoad32Indexed(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDWX, register);
-        instruction.setUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to add {@code value} to register R0.
-     */
-    public ApfGenerator addAdd(int value) {
-        Instruction instruction = new Instruction(Opcodes.ADD);
-        instruction.setSignedImm(value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to multiply register R0 by {@code value}.
-     */
-    public ApfGenerator addMul(int value) {
-        Instruction instruction = new Instruction(Opcodes.MUL);
-        instruction.setSignedImm(value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to divide register R0 by {@code value}.
-     */
-    public ApfGenerator addDiv(int value) {
-        Instruction instruction = new Instruction(Opcodes.DIV);
-        instruction.setSignedImm(value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to logically and register R0 with {@code value}.
-     */
-    public ApfGenerator addAnd(int value) {
-        Instruction instruction = new Instruction(Opcodes.AND);
-        instruction.setUnsignedImm(value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to logically or register R0 with {@code value}.
-     */
-    public ApfGenerator addOr(int value) {
-        Instruction instruction = new Instruction(Opcodes.OR);
-        instruction.setUnsignedImm(value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to shift left register R0 by {@code value} bits.
-     */
-    public ApfGenerator addLeftShift(int value) {
-        Instruction instruction = new Instruction(Opcodes.SH);
-        instruction.setSignedImm(value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to shift right register R0 by {@code value}
-     * bits.
-     */
-    public ApfGenerator addRightShift(int value) {
-        Instruction instruction = new Instruction(Opcodes.SH);
-        instruction.setSignedImm(-value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to add register R1 to register R0.
-     */
-    public ApfGenerator addAddR1() {
-        Instruction instruction = new Instruction(Opcodes.ADD, Register.R1);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to multiply register R0 by register R1.
-     */
-    public ApfGenerator addMulR1() {
-        Instruction instruction = new Instruction(Opcodes.MUL, Register.R1);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to divide register R0 by register R1.
-     */
-    public ApfGenerator addDivR1() {
-        Instruction instruction = new Instruction(Opcodes.DIV, Register.R1);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to logically and register R0 with register R1
-     * and store the result back into register R0.
-     */
-    public ApfGenerator addAndR1() {
-        Instruction instruction = new Instruction(Opcodes.AND, Register.R1);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to logically or register R0 with register R1
-     * and store the result back into register R0.
-     */
-    public ApfGenerator addOrR1() {
-        Instruction instruction = new Instruction(Opcodes.OR, Register.R1);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to shift register R0 left by the value in
-     * register R1.
-     */
-    public ApfGenerator addLeftShiftR1() {
-        Instruction instruction = new Instruction(Opcodes.SH, Register.R1);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to move {@code value} into {@code register}.
-     */
-    public ApfGenerator addLoadImmediate(Register register, int value) {
-        Instruction instruction = new Instruction(Opcodes.LI, register);
-        instruction.setSignedImm(value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if register R0's
-     * value equals {@code value}.
-     */
-    public ApfGenerator addJumpIfR0Equals(int value, String target) {
-        Instruction instruction = new Instruction(Opcodes.JEQ);
-        instruction.setUnsignedImm(value);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if register R0's
-     * value does not equal {@code value}.
-     */
-    public ApfGenerator addJumpIfR0NotEquals(int value, String target) {
-        Instruction instruction = new Instruction(Opcodes.JNE);
-        instruction.setUnsignedImm(value);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if register R0's
-     * value is greater than {@code value}.
-     */
-    public ApfGenerator addJumpIfR0GreaterThan(int value, String target) {
-        Instruction instruction = new Instruction(Opcodes.JGT);
-        instruction.setUnsignedImm(value);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if register R0's
-     * value is less than {@code value}.
-     */
-    public ApfGenerator addJumpIfR0LessThan(int value, String target) {
-        Instruction instruction = new Instruction(Opcodes.JLT);
-        instruction.setUnsignedImm(value);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if register R0's
-     * value has any bits set that are also set in {@code value}.
-     */
-    public ApfGenerator addJumpIfR0AnyBitsSet(int value, String target) {
-        Instruction instruction = new Instruction(Opcodes.JSET);
-        instruction.setUnsignedImm(value);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if register R0's
-     * value equals register R1's value.
-     */
-    public ApfGenerator addJumpIfR0EqualsR1(String target) {
-        Instruction instruction = new Instruction(Opcodes.JEQ, Register.R1);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if register R0's
-     * value does not equal register R1's value.
-     */
-    public ApfGenerator addJumpIfR0NotEqualsR1(String target) {
-        Instruction instruction = new Instruction(Opcodes.JNE, Register.R1);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if register R0's
-     * value is greater than register R1's value.
-     */
-    public ApfGenerator addJumpIfR0GreaterThanR1(String target) {
-        Instruction instruction = new Instruction(Opcodes.JGT, Register.R1);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if register R0's
-     * value is less than register R1's value.
-     */
-    public ApfGenerator addJumpIfR0LessThanR1(String target) {
-        Instruction instruction = new Instruction(Opcodes.JLT, Register.R1);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if register R0's
-     * value has any bits set that are also set in R1's value.
-     */
-    public ApfGenerator addJumpIfR0AnyBitsSetR1(String target) {
-        Instruction instruction = new Instruction(Opcodes.JSET, Register.R1);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to jump to {@code target} if the bytes of the
-     * packet at an offset specified by {@code register} match {@code bytes}.
-     */
-    public ApfGenerator addJumpIfBytesNotEqual(Register register, byte[] bytes, String target)
-            throws IllegalInstructionException {
-        if (register == Register.R1) {
-            throw new IllegalInstructionException("JNEBS fails with R1");
-        }
-        Instruction instruction = new Instruction(Opcodes.JNEBS, register);
-        instruction.setUnsignedImm(bytes.length);
-        instruction.setTargetLabel(target);
-        instruction.setCompareBytes(bytes);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to load memory slot {@code slot} into
-     * {@code register}.
-     */
-    public ApfGenerator addLoadFromMemory(Register register, int slot)
-            throws IllegalInstructionException {
-        if (slot < 0 || slot > (MEMORY_SLOTS - 1)) {
-            throw new IllegalInstructionException("illegal memory slot number: " + slot);
-        }
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.setUnsignedImm(ExtendedOpcodes.LDM.value + slot);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to store {@code register} into memory slot
-     * {@code slot}.
-     */
-    public ApfGenerator addStoreToMemory(Register register, int slot)
-            throws IllegalInstructionException {
-        if (slot < 0 || slot > (MEMORY_SLOTS - 1)) {
-            throw new IllegalInstructionException("illegal memory slot number: " + slot);
-        }
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.setUnsignedImm(ExtendedOpcodes.STM.value + slot);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to logically not {@code register}.
-     */
-    public ApfGenerator addNot(Register register) {
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.setUnsignedImm(ExtendedOpcodes.NOT.value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to negate {@code register}.
-     */
-    public ApfGenerator addNeg(Register register) {
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.setUnsignedImm(ExtendedOpcodes.NEG.value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to swap the values in register R0 and register R1.
-     */
-    public ApfGenerator addSwap() {
-        Instruction instruction = new Instruction(Opcodes.EXT);
-        instruction.setUnsignedImm(ExtendedOpcodes.SWAP.value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to move the value into
-     * {@code register} from the other register.
-     */
-    public ApfGenerator addMove(Register register) {
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.setUnsignedImm(ExtendedOpcodes.MOVE.value);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to load 32 bits from the data memory into
-     * {@code register}. The source address is computed by adding the signed immediate
-     * @{code offset} to the other register.
-     * Requires APF v3 or greater.
-     */
-    public ApfGenerator addLoadData(Register destinationRegister, int offset)
-            throws IllegalInstructionException {
-        requireApfVersion(3);
-        Instruction instruction = new Instruction(Opcodes.LDDW, destinationRegister);
-        instruction.setSignedImm(offset);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Add an instruction to the end of the program to store 32 bits from {@code register} into the
-     * data memory. The destination address is computed by adding the signed immediate
-     * @{code offset} to the other register.
-     * Requires APF v3 or greater.
-     */
-    public ApfGenerator addStoreData(Register sourceRegister, int offset)
-            throws IllegalInstructionException {
-        requireApfVersion(3);
-        Instruction instruction = new Instruction(Opcodes.STDW, sourceRegister);
-        instruction.setSignedImm(offset);
-        addInstruction(instruction);
-        return this;
-    }
-
-    /**
-     * Updates instruction offset fields using latest instruction sizes.
-     * @return current program length in bytes.
-     */
-    private int updateInstructionOffsets() {
-        int offset = 0;
-        for (Instruction instruction : mInstructions) {
-            instruction.offset = offset;
-            offset += instruction.size();
-        }
-        return offset;
-    }
-
-    /**
-     * Returns an overestimate of the size of the generated program. {@link #generate} may return
-     * a program that is smaller.
-     */
-    public int programLengthOverEstimate() {
-        return updateInstructionOffsets();
-    }
-
-    /**
-     * Generate the bytecode for the APF program.
-     * @return the bytecode.
-     * @throws IllegalStateException if a label is referenced but not defined.
-     */
-    public byte[] generate() throws IllegalInstructionException {
-        // Enforce that we can only generate once because we cannot unshrink instructions and
-        // PASS/DROP labels may move further away requiring unshrinking if we add further
-        // instructions.
-        if (mGenerated) {
-            throw new IllegalStateException("Can only generate() once!");
-        }
-        mGenerated = true;
-        int total_size;
-        boolean shrunk;
-        // Shrink the immediate value fields of instructions.
-        // As we shrink the instructions some branch offset
-        // fields may shrink also, thereby shrinking the
-        // instructions further. Loop until we've reached the
-        // minimum size. Rarely will this loop more than a few times.
-        // Limit iterations to avoid O(n^2) behavior.
-        int iterations_remaining = 10;
-        do {
-            total_size = updateInstructionOffsets();
-            // Update drop and pass label offsets.
-            mDropLabel.offset = total_size + 1;
-            mPassLabel.offset = total_size;
-            // Limit run-time in aberant circumstances.
-            if (iterations_remaining-- == 0) break;
-            // Attempt to shrink instructions.
-            shrunk = false;
-            for (Instruction instruction : mInstructions) {
-                if (instruction.shrink()) {
-                    shrunk = true;
-                }
-            }
-        } while (shrunk);
-        // Generate bytecode for instructions.
-        byte[] bytecode = new byte[total_size];
-        for (Instruction instruction : mInstructions) {
-            instruction.generate(bytecode);
-        }
-        return bytecode;
-    }
-}
-
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java
deleted file mode 100644
index b2eb4e2..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import java.net.Inet4Address;
-import java.nio.ByteBuffer;
-
-/**
- * This class implements the DHCP-ACK packet.
- */
-class DhcpAckPacket extends DhcpPacket {
-
-    /**
-     * The address of the server which sent this packet.
-     */
-    private final Inet4Address mSrcIp;
-
-    DhcpAckPacket(int transId, short secs, boolean broadcast, Inet4Address serverAddress,
-            Inet4Address relayIp, Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) {
-        super(transId, secs, clientIp, yourIp, serverAddress, relayIp, clientMac, broadcast);
-        mBroadcast = broadcast;
-        mSrcIp = serverAddress;
-    }
-
-    public String toString() {
-        String s = super.toString();
-        String dnsServers = " DNS servers: ";
-
-        for (Inet4Address dnsServer: mDnsServers) {
-            dnsServers += dnsServer.toString() + " ";
-        }
-
-        return s + " ACK: your new IP " + mYourIp +
-                ", netmask " + mSubnetMask +
-                ", gateways " + mGateways + dnsServers +
-                ", lease time " + mLeaseTime;
-    }
-
-    /**
-     * Fills in a packet with the requested ACK parameters.
-     */
-    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
-        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
-        Inet4Address destIp = mBroadcast ? INADDR_BROADCAST : mYourIp;
-        Inet4Address srcIp = mBroadcast ? INADDR_ANY : mSrcIp;
-
-        fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result,
-            DHCP_BOOTREPLY, mBroadcast);
-        result.flip();
-        return result;
-    }
-
-    /**
-     * Adds the optional parameters to the client-generated ACK packet.
-     */
-    void finishPacket(ByteBuffer buffer) {
-        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_ACK);
-        addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
-
-        addCommonServerTlvs(buffer);
-        addTlvEnd(buffer);
-    }
-
-    /**
-     * Un-boxes an Integer, returning 0 if a null reference is supplied.
-     */
-    private static final int getInt(Integer v) {
-        if (v == null) {
-            return 0;
-        } else {
-            return v.intValue();
-        }
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
deleted file mode 100644
index ca6c17a..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
+++ /dev/null
@@ -1,1070 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS;
-import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER;
-import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME;
-import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_MTU;
-import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_ROUTER;
-import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK;
-import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO;
-import static android.net.dhcp.DhcpPacket.INADDR_ANY;
-import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
-import static android.net.util.NetworkStackUtils.closeSocketQuietly;
-import static android.net.util.SocketUtils.makePacketSocketAddress;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_PACKET;
-import static android.system.OsConstants.ETH_P_IP;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOCK_RAW;
-import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_BROADCAST;
-import static android.system.OsConstants.SO_RCVBUF;
-import static android.system.OsConstants.SO_REUSEADDR;
-
-import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
-
-import android.content.Context;
-import android.net.DhcpResults;
-import android.net.InetAddresses;
-import android.net.TrafficStats;
-import android.net.ip.IpClient;
-import android.net.metrics.DhcpClientEvent;
-import android.net.metrics.DhcpErrorEvent;
-import android.net.metrics.IpConnectivityLog;
-import android.net.util.InterfaceParams;
-import android.net.util.NetworkStackUtils;
-import android.net.util.SocketUtils;
-import android.os.Message;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.util.HexDump;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.internal.util.TrafficStatsConstants;
-import com.android.internal.util.WakeupMessage;
-import com.android.networkstack.R;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Random;
-
-/**
- * A DHCPv4 client.
- *
- * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android
- * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine.
- *
- * TODO:
- *
- * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour).
- * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not
- *   do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a
- *   given SSID), it requests the last-leased IP address on the same interface, causing a delay if
- *   the server NAKs or a timeout if it doesn't.
- *
- * Known differences from current behaviour:
- *
- * - Does not request the "static routes" option.
- * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now.
- * - Requests the "broadcast" option, but does nothing with it.
- * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0).
- *
- * @hide
- */
-public class DhcpClient extends StateMachine {
-
-    private static final String TAG = "DhcpClient";
-    private static final boolean DBG = true;
-    private static final boolean STATE_DBG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final boolean MSG_DBG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final boolean PACKET_DBG = Log.isLoggable(TAG, Log.DEBUG);
-
-    // Metrics events: must be kept in sync with server-side aggregation code.
-    /** Represents transitions from DhcpInitState to DhcpBoundState */
-    private static final String EVENT_INITIAL_BOUND = "InitialBoundState";
-    /** Represents transitions from and to DhcpBoundState via DhcpRenewingState */
-    private static final String EVENT_RENEWING_BOUND = "RenewingBoundState";
-
-    // Timers and timeouts.
-    private static final int SECONDS = 1000;
-    private static final int FIRST_TIMEOUT_MS   =   2 * SECONDS;
-    private static final int MAX_TIMEOUT_MS     = 128 * SECONDS;
-
-    // This is not strictly needed, since the client is asynchronous and implements exponential
-    // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
-    // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
-    // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
-    private static final int DHCP_TIMEOUT_MS    =  36 * SECONDS;
-
-    // DhcpClient uses IpClient's handler.
-    private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE;
-
-    // Below constants are picked up by MessageUtils and exempt from ProGuard optimization.
-    /* Commands from controller to start/stop DHCP */
-    public static final int CMD_START_DHCP                  = PUBLIC_BASE + 1;
-    public static final int CMD_STOP_DHCP                   = PUBLIC_BASE + 2;
-
-    /* Notification from DHCP state machine prior to DHCP discovery/renewal */
-    public static final int CMD_PRE_DHCP_ACTION             = PUBLIC_BASE + 3;
-    /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
-     * success/failure */
-    public static final int CMD_POST_DHCP_ACTION            = PUBLIC_BASE + 4;
-    /* Notification from DHCP state machine before quitting */
-    public static final int CMD_ON_QUIT                     = PUBLIC_BASE + 5;
-
-    /* Command from controller to indicate DHCP discovery/renewal can continue
-     * after pre DHCP action is complete */
-    public static final int CMD_PRE_DHCP_ACTION_COMPLETE    = PUBLIC_BASE + 6;
-
-    /* Command and event notification to/from IpManager requesting the setting
-     * (or clearing) of an IPv4 LinkAddress.
-     */
-    public static final int CMD_CLEAR_LINKADDRESS           = PUBLIC_BASE + 7;
-    public static final int CMD_CONFIGURE_LINKADDRESS       = PUBLIC_BASE + 8;
-    public static final int EVENT_LINKADDRESS_CONFIGURED    = PUBLIC_BASE + 9;
-
-    /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
-    public static final int DHCP_SUCCESS = 1;
-    public static final int DHCP_FAILURE = 2;
-
-    // Internal messages.
-    private static final int PRIVATE_BASE         = IpClient.DHCPCLIENT_CMD_BASE + 100;
-    private static final int CMD_KICK             = PRIVATE_BASE + 1;
-    private static final int CMD_RECEIVED_PACKET  = PRIVATE_BASE + 2;
-    private static final int CMD_TIMEOUT          = PRIVATE_BASE + 3;
-    private static final int CMD_RENEW_DHCP       = PRIVATE_BASE + 4;
-    private static final int CMD_REBIND_DHCP      = PRIVATE_BASE + 5;
-    private static final int CMD_EXPIRE_DHCP      = PRIVATE_BASE + 6;
-
-    // For message logging.
-    private static final Class[] sMessageClasses = { DhcpClient.class };
-    private static final SparseArray<String> sMessageNames =
-            MessageUtils.findMessageNames(sMessageClasses);
-
-    // DHCP parameters that we request.
-    /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
-        DHCP_SUBNET_MASK,
-        DHCP_ROUTER,
-        DHCP_DNS_SERVER,
-        DHCP_DOMAIN_NAME,
-        DHCP_MTU,
-        DHCP_BROADCAST_ADDRESS,  // TODO: currently ignored.
-        DHCP_LEASE_TIME,
-        DHCP_RENEWAL_TIME,
-        DHCP_REBINDING_TIME,
-        DHCP_VENDOR_INFO,
-    };
-
-    // DHCP flag that means "yes, we support unicast."
-    private static final boolean DO_UNICAST   = false;
-
-    // System services / libraries we use.
-    private final Context mContext;
-    private final Random mRandom;
-    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-
-    // Sockets.
-    // - We use a packet socket to receive, because servers send us packets bound for IP addresses
-    //   which we have not yet configured, and the kernel protocol stack drops these.
-    // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
-    //   be off-link as well as on-link).
-    private FileDescriptor mPacketSock;
-    private FileDescriptor mUdpSock;
-    private ReceiveThread mReceiveThread;
-
-    // State variables.
-    private final StateMachine mController;
-    private final WakeupMessage mKickAlarm;
-    private final WakeupMessage mTimeoutAlarm;
-    private final WakeupMessage mRenewAlarm;
-    private final WakeupMessage mRebindAlarm;
-    private final WakeupMessage mExpiryAlarm;
-    private final String mIfaceName;
-
-    private boolean mRegisteredForPreDhcpNotification;
-    private InterfaceParams mIface;
-    // TODO: MacAddress-ify more of this class hierarchy.
-    private byte[] mHwAddr;
-    private SocketAddress mInterfaceBroadcastAddr;
-    private int mTransactionId;
-    private long mTransactionStartMillis;
-    private DhcpResults mDhcpLease;
-    private long mDhcpLeaseExpiry;
-    private DhcpResults mOffer;
-
-    // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState.
-    private long mLastInitEnterTime;
-    private long mLastBoundExitTime;
-
-    // States.
-    private State mStoppedState = new StoppedState();
-    private State mDhcpState = new DhcpState();
-    private State mDhcpInitState = new DhcpInitState();
-    private State mDhcpSelectingState = new DhcpSelectingState();
-    private State mDhcpRequestingState = new DhcpRequestingState();
-    private State mDhcpHaveLeaseState = new DhcpHaveLeaseState();
-    private State mConfiguringInterfaceState = new ConfiguringInterfaceState();
-    private State mDhcpBoundState = new DhcpBoundState();
-    private State mDhcpRenewingState = new DhcpRenewingState();
-    private State mDhcpRebindingState = new DhcpRebindingState();
-    private State mDhcpInitRebootState = new DhcpInitRebootState();
-    private State mDhcpRebootingState = new DhcpRebootingState();
-    private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
-    private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
-
-    private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
-        cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
-        return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
-    }
-
-    // TODO: Take an InterfaceParams instance instead of an interface name String.
-    private DhcpClient(Context context, StateMachine controller, String iface) {
-        super(TAG, controller.getHandler());
-
-        mContext = context;
-        mController = controller;
-        mIfaceName = iface;
-
-        addState(mStoppedState);
-        addState(mDhcpState);
-            addState(mDhcpInitState, mDhcpState);
-            addState(mWaitBeforeStartState, mDhcpState);
-            addState(mDhcpSelectingState, mDhcpState);
-            addState(mDhcpRequestingState, mDhcpState);
-            addState(mDhcpHaveLeaseState, mDhcpState);
-                addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
-                addState(mDhcpBoundState, mDhcpHaveLeaseState);
-                addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
-                addState(mDhcpRenewingState, mDhcpHaveLeaseState);
-                addState(mDhcpRebindingState, mDhcpHaveLeaseState);
-            addState(mDhcpInitRebootState, mDhcpState);
-            addState(mDhcpRebootingState, mDhcpState);
-
-        setInitialState(mStoppedState);
-
-        mRandom = new Random();
-
-        // Used to schedule packet retransmissions.
-        mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
-        // Used to time out PacketRetransmittingStates.
-        mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
-        // Used to schedule DHCP reacquisition.
-        mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
-        mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP);
-        mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP);
-    }
-
-    public void registerForPreDhcpNotification() {
-        mRegisteredForPreDhcpNotification = true;
-    }
-
-    public static DhcpClient makeDhcpClient(
-            Context context, StateMachine controller, InterfaceParams ifParams) {
-        DhcpClient client = new DhcpClient(context, controller, ifParams.name);
-        client.mIface = ifParams;
-        client.start();
-        return client;
-    }
-
-    private boolean initInterface() {
-        if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName);
-        if (mIface == null) {
-            Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName);
-            return false;
-        }
-
-        mHwAddr = mIface.macAddr.toByteArray();
-        mInterfaceBroadcastAddr = makePacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST);
-        return true;
-    }
-
-    private void startNewTransaction() {
-        mTransactionId = mRandom.nextInt();
-        mTransactionStartMillis = SystemClock.elapsedRealtime();
-    }
-
-    private boolean initSockets() {
-        return initPacketSocket() && initUdpSocket();
-    }
-
-    private boolean initPacketSocket() {
-        try {
-            mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
-            SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index);
-            Os.bind(mPacketSock, addr);
-            NetworkStackUtils.attachDhcpFilter(mPacketSock);
-        } catch(SocketException|ErrnoException e) {
-            Log.e(TAG, "Error creating packet socket", e);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean initUdpSocket() {
-        final int oldTag = TrafficStats.getAndSetThreadStatsTag(
-                TrafficStatsConstants.TAG_SYSTEM_DHCP);
-        try {
-            mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
-            SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName);
-            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
-            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
-            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
-            Os.bind(mUdpSock, IPV4_ADDR_ANY, DhcpPacket.DHCP_CLIENT);
-        } catch(SocketException|ErrnoException e) {
-            Log.e(TAG, "Error creating UDP socket", e);
-            return false;
-        } finally {
-            TrafficStats.setThreadStatsTag(oldTag);
-        }
-        return true;
-    }
-
-    private boolean connectUdpSock(Inet4Address to) {
-        try {
-            Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
-            return true;
-        } catch (SocketException|ErrnoException e) {
-            Log.e(TAG, "Error connecting UDP socket", e);
-            return false;
-        }
-    }
-
-    private void closeSockets() {
-        closeSocketQuietly(mUdpSock);
-        closeSocketQuietly(mPacketSock);
-    }
-
-    class ReceiveThread extends Thread {
-
-        private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
-        private volatile boolean mStopped = false;
-
-        public void halt() {
-            mStopped = true;
-            closeSockets();  // Interrupts the read() call the thread is blocked in.
-        }
-
-        @Override
-        public void run() {
-            if (DBG) Log.d(TAG, "Receive thread started");
-            while (!mStopped) {
-                int length = 0;  // Or compiler can't tell it's initialized if a parse error occurs.
-                try {
-                    length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
-                    DhcpPacket packet = null;
-                    packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
-                    if (DBG) Log.d(TAG, "Received packet: " + packet);
-                    sendMessage(CMD_RECEIVED_PACKET, packet);
-                } catch (IOException|ErrnoException e) {
-                    if (!mStopped) {
-                        Log.e(TAG, "Read error", e);
-                        logError(DhcpErrorEvent.RECEIVE_ERROR);
-                    }
-                } catch (DhcpPacket.ParseException e) {
-                    Log.e(TAG, "Can't parse packet: " + e.getMessage());
-                    if (PACKET_DBG) {
-                        Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
-                    }
-                    if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
-                        int snetTagId = 0x534e4554;
-                        String bugId = "31850211";
-                        int uid = -1;
-                        String data = DhcpPacket.ParseException.class.getName();
-                        EventLog.writeEvent(snetTagId, bugId, uid, data);
-                    }
-                    logError(e.errorCode);
-                }
-            }
-            if (DBG) Log.d(TAG, "Receive thread stopped");
-        }
-    }
-
-    private short getSecs() {
-        return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
-    }
-
-    private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) {
-        try {
-            if (encap == DhcpPacket.ENCAP_L2) {
-                if (DBG) Log.d(TAG, "Broadcasting " + description);
-                Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
-            } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) {
-                if (DBG) Log.d(TAG, "Broadcasting " + description);
-                // We only send L3-encapped broadcasts in DhcpRebindingState,
-                // where we have an IP address and an unconnected UDP socket.
-                //
-                // N.B.: We only need this codepath because DhcpRequestPacket
-                // hardcodes the source IP address to 0.0.0.0. We could reuse
-                // the packet socket if this ever changes.
-                Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
-            } else {
-                // It's safe to call getpeername here, because we only send unicast packets if we
-                // have an IP address, and we connect the UDP socket in DhcpBoundState#enter.
-                if (DBG) Log.d(TAG, String.format("Unicasting %s to %s",
-                        description, Os.getpeername(mUdpSock)));
-                Os.write(mUdpSock, buf);
-            }
-        } catch(ErrnoException|IOException e) {
-            Log.e(TAG, "Can't send packet: ", e);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean sendDiscoverPacket() {
-        ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
-                DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
-                DO_UNICAST, REQUESTED_PARAMS);
-        return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
-    }
-
-    private boolean sendRequestPacket(
-            Inet4Address clientAddress, Inet4Address requestedAddress,
-            Inet4Address serverAddress, Inet4Address to) {
-        // TODO: should we use the transaction ID from the server?
-        final int encap = INADDR_ANY.equals(clientAddress)
-                ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
-
-        ByteBuffer packet = DhcpPacket.buildRequestPacket(
-                encap, mTransactionId, getSecs(), clientAddress,
-                DO_UNICAST, mHwAddr, requestedAddress,
-                serverAddress, REQUESTED_PARAMS, null);
-        String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
-        String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
-                             " request=" + requestedAddress.getHostAddress() +
-                             " serverid=" + serverStr;
-        return transmitPacket(packet, description, encap, to);
-    }
-
-    private void scheduleLeaseTimers() {
-        if (mDhcpLeaseExpiry == 0) {
-            Log.d(TAG, "Infinite lease, no timer scheduling needed");
-            return;
-        }
-
-        final long now = SystemClock.elapsedRealtime();
-
-        // TODO: consider getting the renew and rebind timers from T1 and T2.
-        // See also:
-        //     https://tools.ietf.org/html/rfc2131#section-4.4.5
-        //     https://tools.ietf.org/html/rfc1533#section-9.9
-        //     https://tools.ietf.org/html/rfc1533#section-9.10
-        final long remainingDelay = mDhcpLeaseExpiry - now;
-        final long renewDelay = remainingDelay / 2;
-        final long rebindDelay = remainingDelay * 7 / 8;
-        mRenewAlarm.schedule(now + renewDelay);
-        mRebindAlarm.schedule(now + rebindDelay);
-        mExpiryAlarm.schedule(now + remainingDelay);
-        Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s");
-        Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s");
-        Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s");
-    }
-
-    private void notifySuccess() {
-        mController.sendMessage(
-                CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
-    }
-
-    private void notifyFailure() {
-        mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null);
-    }
-
-    private void acceptDhcpResults(DhcpResults results, String msg) {
-        mDhcpLease = results;
-        if (mDhcpLease.dnsServers.isEmpty()) {
-            // supplement customized dns servers
-            String[] dnsServersList =
-                    mContext.getResources().getStringArray(R.array.config_default_dns_servers);
-            for (final String dnsServer : dnsServersList) {
-                try {
-                    mDhcpLease.dnsServers.add(InetAddresses.parseNumericAddress(dnsServer));
-                } catch (IllegalArgumentException e) {
-                    Log.e(TAG, "Invalid default DNS server: " + dnsServer, e);
-                }
-            }
-        }
-        mOffer = null;
-        Log.d(TAG, msg + " lease: " + mDhcpLease);
-        notifySuccess();
-    }
-
-    private void clearDhcpState() {
-        mDhcpLease = null;
-        mDhcpLeaseExpiry = 0;
-        mOffer = null;
-    }
-
-    /**
-     * Quit the DhcpStateMachine.
-     *
-     * @hide
-     */
-    public void doQuit() {
-        Log.d(TAG, "doQuit");
-        quit();
-    }
-
-    @Override
-    protected void onQuitting() {
-        Log.d(TAG, "onQuitting");
-        mController.sendMessage(CMD_ON_QUIT);
-    }
-
-    abstract class LoggingState extends State {
-        private long mEnterTimeMs;
-
-        @Override
-        public void enter() {
-            if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
-            mEnterTimeMs = SystemClock.elapsedRealtime();
-        }
-
-        @Override
-        public void exit() {
-            long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs;
-            logState(getName(), (int) durationMs);
-        }
-
-        private String messageName(int what) {
-            return sMessageNames.get(what, Integer.toString(what));
-        }
-
-        private String messageToString(Message message) {
-            long now = SystemClock.uptimeMillis();
-            return new StringBuilder(" ")
-                    .append(message.getWhen() - now)
-                    .append(messageName(message.what))
-                    .append(" ").append(message.arg1)
-                    .append(" ").append(message.arg2)
-                    .append(" ").append(message.obj)
-                    .toString();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (MSG_DBG) {
-                Log.d(TAG, getName() + messageToString(message));
-            }
-            return NOT_HANDLED;
-        }
-
-        @Override
-        public String getName() {
-            // All DhcpClient's states are inner classes with a well defined name.
-            // Use getSimpleName() and avoid super's getName() creating new String instances.
-            return getClass().getSimpleName();
-        }
-    }
-
-    // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
-    // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
-    abstract class WaitBeforeOtherState extends LoggingState {
-        protected State mOtherState;
-
-        @Override
-        public void enter() {
-            super.enter();
-            mController.sendMessage(CMD_PRE_DHCP_ACTION);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            super.processMessage(message);
-            switch (message.what) {
-                case CMD_PRE_DHCP_ACTION_COMPLETE:
-                    transitionTo(mOtherState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-    }
-
-    class StoppedState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_START_DHCP:
-                    if (mRegisteredForPreDhcpNotification) {
-                        transitionTo(mWaitBeforeStartState);
-                    } else {
-                        transitionTo(mDhcpInitState);
-                    }
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-    }
-
-    class WaitBeforeStartState extends WaitBeforeOtherState {
-        public WaitBeforeStartState(State otherState) {
-            super();
-            mOtherState = otherState;
-        }
-    }
-
-    class WaitBeforeRenewalState extends WaitBeforeOtherState {
-        public WaitBeforeRenewalState(State otherState) {
-            super();
-            mOtherState = otherState;
-        }
-    }
-
-    class DhcpState extends State {
-        @Override
-        public void enter() {
-            clearDhcpState();
-            if (initInterface() && initSockets()) {
-                mReceiveThread = new ReceiveThread();
-                mReceiveThread.start();
-            } else {
-                notifyFailure();
-                transitionTo(mStoppedState);
-            }
-        }
-
-        @Override
-        public void exit() {
-            if (mReceiveThread != null) {
-                mReceiveThread.halt();  // Also closes sockets.
-                mReceiveThread = null;
-            }
-            clearDhcpState();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            super.processMessage(message);
-            switch (message.what) {
-                case CMD_STOP_DHCP:
-                    transitionTo(mStoppedState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-    }
-
-    public boolean isValidPacket(DhcpPacket packet) {
-        // TODO: check checksum.
-        int xid = packet.getTransactionId();
-        if (xid != mTransactionId) {
-            Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
-            return false;
-        }
-        if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
-            Log.d(TAG, "MAC addr mismatch: got " +
-                    HexDump.toHexString(packet.getClientMac()) + ", expected " +
-                    HexDump.toHexString(packet.getClientMac()));
-            return false;
-        }
-        return true;
-    }
-
-    public void setDhcpLeaseExpiry(DhcpPacket packet) {
-        long leaseTimeMillis = packet.getLeaseTimeMillis();
-        mDhcpLeaseExpiry =
-                (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
-    }
-
-    /**
-     * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
-     * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
-     * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
-     * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
-     * state.
-     *
-     * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
-     * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
-     * sent by the receive thread. They may also set mTimeout and implement timeout.
-     */
-    abstract class PacketRetransmittingState extends LoggingState {
-
-        private int mTimer;
-        protected int mTimeout = 0;
-
-        @Override
-        public void enter() {
-            super.enter();
-            initTimer();
-            maybeInitTimeout();
-            sendMessage(CMD_KICK);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            super.processMessage(message);
-            switch (message.what) {
-                case CMD_KICK:
-                    sendPacket();
-                    scheduleKick();
-                    return HANDLED;
-                case CMD_RECEIVED_PACKET:
-                    receivePacket((DhcpPacket) message.obj);
-                    return HANDLED;
-                case CMD_TIMEOUT:
-                    timeout();
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-
-        @Override
-        public void exit() {
-            super.exit();
-            mKickAlarm.cancel();
-            mTimeoutAlarm.cancel();
-        }
-
-        abstract protected boolean sendPacket();
-        abstract protected void receivePacket(DhcpPacket packet);
-        protected void timeout() {}
-
-        protected void initTimer() {
-            mTimer = FIRST_TIMEOUT_MS;
-        }
-
-        protected int jitterTimer(int baseTimer) {
-            int maxJitter = baseTimer / 10;
-            int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
-            return baseTimer + jitter;
-        }
-
-        protected void scheduleKick() {
-            long now = SystemClock.elapsedRealtime();
-            long timeout = jitterTimer(mTimer);
-            long alarmTime = now + timeout;
-            mKickAlarm.schedule(alarmTime);
-            mTimer *= 2;
-            if (mTimer > MAX_TIMEOUT_MS) {
-                mTimer = MAX_TIMEOUT_MS;
-            }
-        }
-
-        protected void maybeInitTimeout() {
-            if (mTimeout > 0) {
-                long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
-                mTimeoutAlarm.schedule(alarmTime);
-            }
-        }
-    }
-
-    class DhcpInitState extends PacketRetransmittingState {
-        public DhcpInitState() {
-            super();
-        }
-
-        @Override
-        public void enter() {
-            super.enter();
-            startNewTransaction();
-            mLastInitEnterTime = SystemClock.elapsedRealtime();
-        }
-
-        protected boolean sendPacket() {
-            return sendDiscoverPacket();
-        }
-
-        protected void receivePacket(DhcpPacket packet) {
-            if (!isValidPacket(packet)) return;
-            if (!(packet instanceof DhcpOfferPacket)) return;
-            mOffer = packet.toDhcpResults();
-            if (mOffer != null) {
-                Log.d(TAG, "Got pending lease: " + mOffer);
-                transitionTo(mDhcpRequestingState);
-            }
-        }
-    }
-
-    // Not implemented. We request the first offer we receive.
-    class DhcpSelectingState extends LoggingState {
-    }
-
-    class DhcpRequestingState extends PacketRetransmittingState {
-        public DhcpRequestingState() {
-            mTimeout = DHCP_TIMEOUT_MS / 2;
-        }
-
-        protected boolean sendPacket() {
-            return sendRequestPacket(
-                    INADDR_ANY,                                    // ciaddr
-                    (Inet4Address) mOffer.ipAddress.getAddress(),  // DHCP_REQUESTED_IP
-                    (Inet4Address) mOffer.serverAddress,           // DHCP_SERVER_IDENTIFIER
-                    INADDR_BROADCAST);                             // packet destination address
-        }
-
-        protected void receivePacket(DhcpPacket packet) {
-            if (!isValidPacket(packet)) return;
-            if ((packet instanceof DhcpAckPacket)) {
-                DhcpResults results = packet.toDhcpResults();
-                if (results != null) {
-                    setDhcpLeaseExpiry(packet);
-                    acceptDhcpResults(results, "Confirmed");
-                    transitionTo(mConfiguringInterfaceState);
-                }
-            } else if (packet instanceof DhcpNakPacket) {
-                // TODO: Wait a while before returning into INIT state.
-                Log.d(TAG, "Received NAK, returning to INIT");
-                mOffer = null;
-                transitionTo(mDhcpInitState);
-            }
-        }
-
-        @Override
-        protected void timeout() {
-            // After sending REQUESTs unsuccessfully for a while, go back to init.
-            transitionTo(mDhcpInitState);
-        }
-    }
-
-    class DhcpHaveLeaseState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_EXPIRE_DHCP:
-                    Log.d(TAG, "Lease expired!");
-                    notifyFailure();
-                    transitionTo(mDhcpInitState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-
-        @Override
-        public void exit() {
-            // Clear any extant alarms.
-            mRenewAlarm.cancel();
-            mRebindAlarm.cancel();
-            mExpiryAlarm.cancel();
-            clearDhcpState();
-            // Tell IpManager to clear the IPv4 address. There is no need to
-            // wait for confirmation since any subsequent packets are sent from
-            // INADDR_ANY anyway (DISCOVER, REQUEST).
-            mController.sendMessage(CMD_CLEAR_LINKADDRESS);
-        }
-    }
-
-    class ConfiguringInterfaceState extends LoggingState {
-        @Override
-        public void enter() {
-            super.enter();
-            mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            super.processMessage(message);
-            switch (message.what) {
-                case EVENT_LINKADDRESS_CONFIGURED:
-                    transitionTo(mDhcpBoundState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-    }
-
-    class DhcpBoundState extends LoggingState {
-        @Override
-        public void enter() {
-            super.enter();
-            if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) {
-                // There's likely no point in going into DhcpInitState here, we'll probably
-                // just repeat the transaction, get the same IP address as before, and fail.
-                //
-                // NOTE: It is observed that connectUdpSock() basically never fails, due to
-                // SO_BINDTODEVICE. Examining the local socket address shows it will happily
-                // return an IPv4 address from another interface, or even return "0.0.0.0".
-                //
-                // TODO: Consider deleting this check, following testing on several kernels.
-                notifyFailure();
-                transitionTo(mStoppedState);
-            }
-
-            scheduleLeaseTimers();
-            logTimeToBoundState();
-        }
-
-        @Override
-        public void exit() {
-            super.exit();
-            mLastBoundExitTime = SystemClock.elapsedRealtime();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            super.processMessage(message);
-            switch (message.what) {
-                case CMD_RENEW_DHCP:
-                    if (mRegisteredForPreDhcpNotification) {
-                        transitionTo(mWaitBeforeRenewalState);
-                    } else {
-                        transitionTo(mDhcpRenewingState);
-                    }
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-
-        private void logTimeToBoundState() {
-            long now = SystemClock.elapsedRealtime();
-            if (mLastBoundExitTime > mLastInitEnterTime) {
-                logState(EVENT_RENEWING_BOUND, (int) (now - mLastBoundExitTime));
-            } else {
-                logState(EVENT_INITIAL_BOUND, (int) (now - mLastInitEnterTime));
-            }
-        }
-    }
-
-    abstract class DhcpReacquiringState extends PacketRetransmittingState {
-        protected String mLeaseMsg;
-
-        @Override
-        public void enter() {
-            super.enter();
-            startNewTransaction();
-        }
-
-        abstract protected Inet4Address packetDestination();
-
-        protected boolean sendPacket() {
-            return sendRequestPacket(
-                    (Inet4Address) mDhcpLease.ipAddress.getAddress(),  // ciaddr
-                    INADDR_ANY,                                        // DHCP_REQUESTED_IP
-                    null,                                              // DHCP_SERVER_IDENTIFIER
-                    packetDestination());                              // packet destination address
-        }
-
-        protected void receivePacket(DhcpPacket packet) {
-            if (!isValidPacket(packet)) return;
-            if ((packet instanceof DhcpAckPacket)) {
-                final DhcpResults results = packet.toDhcpResults();
-                if (results != null) {
-                    if (!mDhcpLease.ipAddress.equals(results.ipAddress)) {
-                        Log.d(TAG, "Renewed lease not for our current IP address!");
-                        notifyFailure();
-                        transitionTo(mDhcpInitState);
-                    }
-                    setDhcpLeaseExpiry(packet);
-                    // Updating our notion of DhcpResults here only causes the
-                    // DNS servers and routes to be updated in LinkProperties
-                    // in IpManager and by any overridden relevant handlers of
-                    // the registered IpManager.Callback.  IP address changes
-                    // are not supported here.
-                    acceptDhcpResults(results, mLeaseMsg);
-                    transitionTo(mDhcpBoundState);
-                }
-            } else if (packet instanceof DhcpNakPacket) {
-                Log.d(TAG, "Received NAK, returning to INIT");
-                notifyFailure();
-                transitionTo(mDhcpInitState);
-            }
-        }
-    }
-
-    class DhcpRenewingState extends DhcpReacquiringState {
-        public DhcpRenewingState() {
-            mLeaseMsg = "Renewed";
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (super.processMessage(message) == HANDLED) {
-                return HANDLED;
-            }
-
-            switch (message.what) {
-                case CMD_REBIND_DHCP:
-                    transitionTo(mDhcpRebindingState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-
-        @Override
-        protected Inet4Address packetDestination() {
-            // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
-            // http://b/25343517 . Try to make things work anyway by using broadcast renews.
-            return (mDhcpLease.serverAddress != null) ?
-                    mDhcpLease.serverAddress : INADDR_BROADCAST;
-        }
-    }
-
-    class DhcpRebindingState extends DhcpReacquiringState {
-        public DhcpRebindingState() {
-            mLeaseMsg = "Rebound";
-        }
-
-        @Override
-        public void enter() {
-            super.enter();
-
-            // We need to broadcast and possibly reconnect the socket to a
-            // completely different server.
-            closeSocketQuietly(mUdpSock);
-            if (!initUdpSocket()) {
-                Log.e(TAG, "Failed to recreate UDP socket");
-                transitionTo(mDhcpInitState);
-            }
-        }
-
-        @Override
-        protected Inet4Address packetDestination() {
-            return INADDR_BROADCAST;
-        }
-    }
-
-    class DhcpInitRebootState extends LoggingState {
-    }
-
-    class DhcpRebootingState extends LoggingState {
-    }
-
-    private void logError(int errorCode) {
-        mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
-    }
-
-    private void logState(String name, int durationMs) {
-        final DhcpClientEvent event = new DhcpClientEvent.Builder()
-                .setMsg(name)
-                .setDurationMs(durationMs)
-                .build();
-        mMetricsLog.log(mIfaceName, event);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java
deleted file mode 100644
index 7ecdea7..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import java.net.Inet4Address;
-import java.nio.ByteBuffer;
-
-/**
- * This class implements the DHCP-DECLINE packet.
- */
-class DhcpDeclinePacket extends DhcpPacket {
-    /**
-     * Generates a DECLINE packet with the specified parameters.
-     */
-    DhcpDeclinePacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp,
-                      Inet4Address nextIp, Inet4Address relayIp,
-                      byte[] clientMac) {
-        super(transId, secs, clientIp, yourIp, nextIp, relayIp, clientMac, false);
-    }
-
-    public String toString() {
-        String s = super.toString();
-        return s + " DECLINE";
-    }
-
-    /**
-     * Fills in a packet with the requested DECLINE attributes.
-     */
-    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
-        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
-
-        fillInPacket(encap, mClientIp, mYourIp, destUdp, srcUdp, result,
-            DHCP_BOOTREQUEST, false);
-        result.flip();
-        return result;
-    }
-
-    /**
-     * Adds optional parameters to the DECLINE packet.
-     */
-    void finishPacket(ByteBuffer buffer) {
-        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DECLINE);
-        addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
-        // RFC 2131 says we MUST NOT include our common client TLVs or the parameter request list.
-        addTlvEnd(buffer);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java
deleted file mode 100644
index 11f2b61..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import java.net.Inet4Address;
-import java.nio.ByteBuffer;
-
-/**
- * This class implements the DHCP-DISCOVER packet.
- */
-class DhcpDiscoverPacket extends DhcpPacket {
-    /**
-     * The IP address of the client which sent this packet.
-     */
-    final Inet4Address mSrcIp;
-
-    /**
-     * Generates a DISCOVER packet with the specified parameters.
-     */
-    DhcpDiscoverPacket(int transId, short secs, Inet4Address relayIp, byte[] clientMac,
-            boolean broadcast, Inet4Address srcIp) {
-        super(transId, secs, INADDR_ANY, INADDR_ANY, INADDR_ANY, relayIp, clientMac, broadcast);
-        mSrcIp = srcIp;
-    }
-
-    public String toString() {
-        String s = super.toString();
-        return s + " DISCOVER " +
-                (mBroadcast ? "broadcast " : "unicast ");
-    }
-
-    /**
-     * Fills in a packet with the requested DISCOVER parameters.
-     */
-    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
-        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
-        fillInPacket(encap, INADDR_BROADCAST, mSrcIp, destUdp, srcUdp, result, DHCP_BOOTREQUEST,
-                mBroadcast);
-        result.flip();
-        return result;
-    }
-
-    /**
-     * Adds optional parameters to a DISCOVER packet.
-     */
-    void finishPacket(ByteBuffer buffer) {
-        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DISCOVER);
-        addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
-        addCommonClientTlvs(buffer);
-        addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
-        addTlvEnd(buffer);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java
deleted file mode 100644
index 7a83466..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import java.net.Inet4Address;
-import java.nio.ByteBuffer;
-
-/**
- * This class implements the (unused) DHCP-INFORM packet.
- */
-class DhcpInformPacket extends DhcpPacket {
-    /**
-     * Generates an INFORM packet with the specified parameters.
-     */
-    DhcpInformPacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp,
-                     Inet4Address nextIp, Inet4Address relayIp,
-                     byte[] clientMac) {
-        super(transId, secs, clientIp, yourIp, nextIp, relayIp, clientMac, false);
-    }
-
-    public String toString() {
-        String s = super.toString();
-        return s + " INFORM";
-    }
-
-    /**
-     * Builds an INFORM packet.
-     */
-    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
-        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
-
-        fillInPacket(encap, mClientIp, mYourIp, destUdp, srcUdp, result,
-            DHCP_BOOTREQUEST, false);
-        result.flip();
-        return result;
-    }
-
-    /**
-     * Adds additional parameters to the INFORM packet.
-     */
-    void finishPacket(ByteBuffer buffer) {
-        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_INFORM);
-        addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
-        addCommonClientTlvs(buffer);
-        addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
-        addTlvEnd(buffer);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpLease.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLease.java
deleted file mode 100644
index 6849cfa..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpLease.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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.dhcp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.MacAddress;
-import android.os.SystemClock;
-import android.text.TextUtils;
-
-import com.android.internal.util.HexDump;
-
-import java.net.Inet4Address;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * An IPv4 address assignment done through DHCPv4.
- * @hide
- */
-public class DhcpLease {
-    public static final long EXPIRATION_NEVER = Long.MAX_VALUE;
-    public static final String HOSTNAME_NONE = null;
-
-    @Nullable
-    private final byte[] mClientId;
-    @NonNull
-    private final MacAddress mHwAddr;
-    @NonNull
-    private final Inet4Address mNetAddr;
-    /**
-     * Expiration time for the lease, to compare with {@link SystemClock#elapsedRealtime()}.
-     */
-    private final long mExpTime;
-    @Nullable
-    private final String mHostname;
-
-    public DhcpLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
-            @NonNull Inet4Address netAddr, long expTime, @Nullable String hostname) {
-        mClientId = (clientId == null ? null : Arrays.copyOf(clientId, clientId.length));
-        mHwAddr = hwAddr;
-        mNetAddr = netAddr;
-        mExpTime = expTime;
-        mHostname = hostname;
-    }
-
-    /**
-     * Get the clientId associated with this lease, if any.
-     *
-     * <p>If the lease is not associated to a clientId, this returns null.
-     */
-    @Nullable
-    public byte[] getClientId() {
-        if (mClientId == null) {
-            return null;
-        }
-        return Arrays.copyOf(mClientId, mClientId.length);
-    }
-
-    @NonNull
-    public MacAddress getHwAddr() {
-        return mHwAddr;
-    }
-
-    @Nullable
-    public String getHostname() {
-        return mHostname;
-    }
-
-    @NonNull
-    public Inet4Address getNetAddr() {
-        return mNetAddr;
-    }
-
-    public long getExpTime() {
-        return mExpTime;
-    }
-
-    /**
-     * Push back the expiration time of this lease. If the provided time is sooner than the original
-     * expiration time, the lease time will not be updated.
-     *
-     * <p>The lease hostname is updated with the provided one if set.
-     * @return A {@link DhcpLease} with expiration time set to max(expTime, currentExpTime)
-     */
-    public DhcpLease renewedLease(long expTime, @Nullable String hostname) {
-        return new DhcpLease(mClientId, mHwAddr, mNetAddr, Math.max(expTime, mExpTime),
-                (hostname == null ? mHostname : hostname));
-    }
-
-    /**
-     * Determine whether this lease matches a client with the specified parameters.
-     * @param clientId clientId of the client if any, or null otherwise.
-     * @param hwAddr Hardware address of the client.
-     */
-    public boolean matchesClient(@Nullable byte[] clientId, @NonNull MacAddress hwAddr) {
-        if (mClientId != null) {
-            return Arrays.equals(mClientId, clientId);
-        } else {
-            return clientId == null && mHwAddr.equals(hwAddr);
-        }
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof DhcpLease)) {
-            return false;
-        }
-        final DhcpLease other = (DhcpLease) obj;
-        return Arrays.equals(mClientId, other.mClientId)
-                && mHwAddr.equals(other.mHwAddr)
-                && mNetAddr.equals(other.mNetAddr)
-                && mExpTime == other.mExpTime
-                && TextUtils.equals(mHostname, other.mHostname);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mClientId, mHwAddr, mNetAddr, mHostname, mExpTime);
-    }
-
-    static String clientIdToString(byte[] bytes) {
-        if (bytes == null) {
-            return "null";
-        }
-        return HexDump.toHexString(bytes);
-    }
-
-    static String inet4AddrToString(@Nullable Inet4Address addr) {
-        return (addr == null) ? "null" : addr.getHostAddress();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("clientId: %s, hwAddr: %s, netAddr: %s, expTime: %d, hostname: %s",
-                clientIdToString(mClientId), mHwAddr.toString(), inet4AddrToString(mNetAddr),
-                mExpTime, mHostname);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
deleted file mode 100644
index 0a15cd7..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
+++ /dev/null
@@ -1,546 +0,0 @@
-/*
- * 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.dhcp;
-
-import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER;
-import static android.net.dhcp.DhcpLease.inet4AddrToString;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
-
-import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
-import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_BITS;
-
-import static java.lang.Math.min;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.IpPrefix;
-import android.net.MacAddress;
-import android.net.dhcp.DhcpServer.Clock;
-import android.net.util.SharedLog;
-import android.util.ArrayMap;
-
-import java.net.Inet4Address;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.function.Function;
-
-/**
- * A repository managing IPv4 address assignments through DHCPv4.
- *
- * <p>This class is not thread-safe. All public methods should be called on a common thread or
- * use some synchronization mechanism.
- *
- * <p>Methods are optimized for a small number of allocated leases, assuming that most of the time
- * only 2~10 addresses will be allocated, which is the common case. Managing a large number of
- * addresses is supported but will be slower: some operations have complexity in O(num_leases).
- * @hide
- */
-class DhcpLeaseRepository {
-    public static final byte[] CLIENTID_UNSPEC = null;
-    public static final Inet4Address INETADDR_UNSPEC = null;
-
-    @NonNull
-    private final SharedLog mLog;
-    @NonNull
-    private final Clock mClock;
-
-    @NonNull
-    private IpPrefix mPrefix;
-    @NonNull
-    private Set<Inet4Address> mReservedAddrs;
-    private int mSubnetAddr;
-    private int mSubnetMask;
-    private int mNumAddresses;
-    private long mLeaseTimeMs;
-
-    /**
-     * Next timestamp when committed or declined leases should be checked for expired ones. This
-     * will always be lower than or equal to the time for the first lease to expire: it's OK not to
-     * update this when removing entries, but it must always be updated when adding/updating.
-     */
-    private long mNextExpirationCheck = EXPIRATION_NEVER;
-
-    static class DhcpLeaseException extends Exception {
-        DhcpLeaseException(String message) {
-            super(message);
-        }
-    }
-
-    static class OutOfAddressesException extends DhcpLeaseException {
-        OutOfAddressesException(String message) {
-            super(message);
-        }
-    }
-
-    static class InvalidAddressException extends DhcpLeaseException {
-        InvalidAddressException(String message) {
-            super(message);
-        }
-    }
-
-    static class InvalidSubnetException extends DhcpLeaseException {
-        InvalidSubnetException(String message) {
-            super(message);
-        }
-    }
-
-    /**
-     * Leases by IP address
-     */
-    private final ArrayMap<Inet4Address, DhcpLease> mCommittedLeases = new ArrayMap<>();
-
-    /**
-     * Map address -> expiration timestamp in ms. Addresses are guaranteed to be valid as defined
-     * by {@link #isValidAddress(Inet4Address)}, but are not necessarily otherwise available for
-     * assignment.
-     */
-    private final LinkedHashMap<Inet4Address, Long> mDeclinedAddrs = new LinkedHashMap<>();
-
-    DhcpLeaseRepository(@NonNull IpPrefix prefix, @NonNull Set<Inet4Address> reservedAddrs,
-            long leaseTimeMs, @NonNull SharedLog log, @NonNull Clock clock) {
-        updateParams(prefix, reservedAddrs, leaseTimeMs);
-        mLog = log;
-        mClock = clock;
-    }
-
-    public void updateParams(@NonNull IpPrefix prefix, @NonNull Set<Inet4Address> reservedAddrs,
-            long leaseTimeMs) {
-        mPrefix = prefix;
-        mReservedAddrs = Collections.unmodifiableSet(new HashSet<>(reservedAddrs));
-        mSubnetMask = prefixLengthToV4NetmaskIntHTH(prefix.getPrefixLength());
-        mSubnetAddr = inet4AddressToIntHTH((Inet4Address) prefix.getAddress()) & mSubnetMask;
-        mNumAddresses = 1 << (IPV4_ADDR_BITS - prefix.getPrefixLength());
-        mLeaseTimeMs = leaseTimeMs;
-
-        cleanMap(mCommittedLeases);
-        cleanMap(mDeclinedAddrs);
-    }
-
-    /**
-     * From a map keyed by {@link Inet4Address}, remove entries where the key is invalid (as
-     * specified by {@link #isValidAddress(Inet4Address)}), or is a reserved address.
-     */
-    private <T> void cleanMap(Map<Inet4Address, T> map) {
-        final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator();
-        while (it.hasNext()) {
-            final Inet4Address addr = it.next().getKey();
-            if (!isValidAddress(addr) || mReservedAddrs.contains(addr)) {
-                it.remove();
-            }
-        }
-    }
-
-    /**
-     * Get a DHCP offer, to reply to a DHCPDISCOVER. Follows RFC2131 #4.3.1.
-     *
-     * @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC}
-     * @param relayAddr Internet address of the relay (giaddr), can be {@link Inet4Address#ANY}
-     * @param reqAddr Requested address by the client (option 50), or {@link #INETADDR_UNSPEC}
-     * @param hostname Client-provided hostname, or {@link DhcpLease#HOSTNAME_NONE}
-     * @throws OutOfAddressesException The server does not have any available address
-     * @throws InvalidSubnetException The lease was requested from an unsupported subnet
-     */
-    @NonNull
-    public DhcpLease getOffer(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
-            @NonNull Inet4Address relayAddr, @Nullable Inet4Address reqAddr,
-            @Nullable String hostname) throws OutOfAddressesException, InvalidSubnetException {
-        final long currentTime = mClock.elapsedRealtime();
-        final long expTime = currentTime + mLeaseTimeMs;
-
-        removeExpiredLeases(currentTime);
-        checkValidRelayAddr(relayAddr);
-
-        final DhcpLease currentLease = findByClient(clientId, hwAddr);
-        final DhcpLease newLease;
-        if (currentLease != null) {
-            newLease = currentLease.renewedLease(expTime, hostname);
-            mLog.log("Offering extended lease " + newLease);
-            // Do not update lease time in the map: the offer is not committed yet.
-        } else if (reqAddr != null && isValidAddress(reqAddr) && isAvailable(reqAddr)) {
-            newLease = new DhcpLease(clientId, hwAddr, reqAddr, expTime, hostname);
-            mLog.log("Offering requested lease " + newLease);
-        } else {
-            newLease = makeNewOffer(clientId, hwAddr, expTime, hostname);
-            mLog.log("Offering new generated lease " + newLease);
-        }
-        return newLease;
-    }
-
-    private void checkValidRelayAddr(@Nullable Inet4Address relayAddr)
-            throws InvalidSubnetException {
-        // As per #4.3.1, addresses are assigned based on the relay address if present. This
-        // implementation only assigns addresses if the relayAddr is inside our configured subnet.
-        // This also applies when the client requested a specific address for consistency between
-        // requests, and with older behavior.
-        if (isIpAddrOutsidePrefix(mPrefix, relayAddr)) {
-            throw new InvalidSubnetException("Lease requested by relay from outside of subnet");
-        }
-    }
-
-    private static boolean isIpAddrOutsidePrefix(@NonNull IpPrefix prefix,
-            @Nullable Inet4Address addr) {
-        return addr != null && !addr.equals(IPV4_ADDR_ANY) && !prefix.contains(addr);
-    }
-
-    @Nullable
-    private DhcpLease findByClient(@Nullable byte[] clientId, @NonNull MacAddress hwAddr) {
-        for (DhcpLease lease : mCommittedLeases.values()) {
-            if (lease.matchesClient(clientId, hwAddr)) {
-                return lease;
-            }
-        }
-
-        // Note this differs from dnsmasq behavior, which would match by hwAddr if clientId was
-        // given but no lease keyed on clientId matched. This would prevent one interface from
-        // obtaining multiple leases with different clientId.
-        return null;
-    }
-
-    /**
-     * Make a lease conformant to a client DHCPREQUEST or renew the client's existing lease,
-     * commit it to the repository and return it.
-     *
-     * <p>This method always succeeds and commits the lease if it does not throw, and has no side
-     * effects if it throws.
-     *
-     * @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC}
-     * @param reqAddr Requested address by the client (option 50), or {@link #INETADDR_UNSPEC}
-     * @param sidSet Whether the server identifier was set in the request
-     * @return The newly created or renewed lease
-     * @throws InvalidAddressException The client provided an address that conflicts with its
-     *                                 current configuration, or other committed/reserved leases.
-     */
-    @NonNull
-    public DhcpLease requestLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
-            @NonNull Inet4Address clientAddr, @NonNull Inet4Address relayAddr,
-            @Nullable Inet4Address reqAddr, boolean sidSet, @Nullable String hostname)
-            throws InvalidAddressException, InvalidSubnetException {
-        final long currentTime = mClock.elapsedRealtime();
-        removeExpiredLeases(currentTime);
-        checkValidRelayAddr(relayAddr);
-        final DhcpLease assignedLease = findByClient(clientId, hwAddr);
-
-        final Inet4Address leaseAddr = reqAddr != null ? reqAddr : clientAddr;
-        if (assignedLease != null) {
-            if (sidSet && reqAddr != null) {
-                // Client in SELECTING state; remove any current lease before creating a new one.
-                mCommittedLeases.remove(assignedLease.getNetAddr());
-            } else if (!assignedLease.getNetAddr().equals(leaseAddr)) {
-                // reqAddr null (RENEWING/REBINDING): client renewing its own lease for clientAddr.
-                // reqAddr set with sid not set (INIT-REBOOT): client verifying configuration.
-                // In both cases, throw if clientAddr or reqAddr does not match the known lease.
-                throw new InvalidAddressException("Incorrect address for client in "
-                        + (reqAddr != null ? "INIT-REBOOT" : "RENEWING/REBINDING"));
-            }
-        }
-
-        // In the init-reboot case, RFC2131 #4.3.2 says that the server must not reply if
-        // assignedLease == null, but dnsmasq will let the client use the requested address if
-        // available, when configured with --dhcp-authoritative. This is preferable to avoid issues
-        // if the server lost the lease DB: the client would not get a reply because the server
-        // does not know their lease.
-        // Similarly in RENEWING/REBINDING state, create a lease when possible if the
-        // client-provided lease is unknown.
-        final DhcpLease lease =
-                checkClientAndMakeLease(clientId, hwAddr, leaseAddr, hostname, currentTime);
-        mLog.logf("DHCPREQUEST assignedLease %s, reqAddr=%s, sidSet=%s: created/renewed lease %s",
-                assignedLease, inet4AddrToString(reqAddr), sidSet, lease);
-        return lease;
-    }
-
-    /**
-     * Check that the client can request the specified address, make or renew the lease if yes, and
-     * commit it.
-     *
-     * <p>This method always succeeds and returns the lease if it does not throw, and has no
-     * side-effect if it throws.
-     *
-     * @return The newly created or renewed, committed lease
-     * @throws InvalidAddressException The client provided an address that conflicts with its
-     *                                 current configuration, or other committed/reserved leases.
-     */
-    private DhcpLease checkClientAndMakeLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
-            @NonNull Inet4Address addr, @Nullable String hostname, long currentTime)
-            throws InvalidAddressException {
-        final long expTime = currentTime + mLeaseTimeMs;
-        final DhcpLease currentLease = mCommittedLeases.getOrDefault(addr, null);
-        if (currentLease != null && !currentLease.matchesClient(clientId, hwAddr)) {
-            throw new InvalidAddressException("Address in use");
-        }
-
-        final DhcpLease lease;
-        if (currentLease == null) {
-            if (isValidAddress(addr) && !mReservedAddrs.contains(addr)) {
-                lease = new DhcpLease(clientId, hwAddr, addr, expTime, hostname);
-            } else {
-                throw new InvalidAddressException("Lease not found and address unavailable");
-            }
-        } else {
-            lease = currentLease.renewedLease(expTime, hostname);
-        }
-        commitLease(lease);
-        return lease;
-    }
-
-    private void commitLease(@NonNull DhcpLease lease) {
-        mCommittedLeases.put(lease.getNetAddr(), lease);
-        maybeUpdateEarliestExpiration(lease.getExpTime());
-    }
-
-    /**
-     * Delete a committed lease from the repository.
-     *
-     * @return true if a lease matching parameters was found.
-     */
-    public boolean releaseLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
-            @NonNull Inet4Address addr) {
-        final DhcpLease currentLease = mCommittedLeases.getOrDefault(addr, null);
-        if (currentLease == null) {
-            mLog.w("Could not release unknown lease for " + inet4AddrToString(addr));
-            return false;
-        }
-        if (currentLease.matchesClient(clientId, hwAddr)) {
-            mCommittedLeases.remove(addr);
-            mLog.log("Released lease " + currentLease);
-            return true;
-        }
-        mLog.w(String.format("Not releasing lease %s: does not match client (cid %s, hwAddr %s)",
-                currentLease, DhcpLease.clientIdToString(clientId), hwAddr));
-        return false;
-    }
-
-    public void markLeaseDeclined(@NonNull Inet4Address addr) {
-        if (mDeclinedAddrs.containsKey(addr) || !isValidAddress(addr)) {
-            mLog.logf("Not marking %s as declined: already declined or not assignable",
-                    inet4AddrToString(addr));
-            return;
-        }
-        final long expTime = mClock.elapsedRealtime() + mLeaseTimeMs;
-        mDeclinedAddrs.put(addr, expTime);
-        mLog.logf("Marked %s as declined expiring %d", inet4AddrToString(addr), expTime);
-        maybeUpdateEarliestExpiration(expTime);
-    }
-
-    /**
-     * Get the list of currently valid committed leases in the repository.
-     */
-    @NonNull
-    public List<DhcpLease> getCommittedLeases() {
-        removeExpiredLeases(mClock.elapsedRealtime());
-        return new ArrayList<>(mCommittedLeases.values());
-    }
-
-    /**
-     * Get the set of addresses that have been marked as declined in the repository.
-     */
-    @NonNull
-    public Set<Inet4Address> getDeclinedAddresses() {
-        removeExpiredLeases(mClock.elapsedRealtime());
-        return new HashSet<>(mDeclinedAddrs.keySet());
-    }
-
-    /**
-     * Given the expiration time of a new committed lease or declined address, update
-     * {@link #mNextExpirationCheck} so it stays lower than or equal to the time for the first lease
-     * to expire.
-     */
-    private void maybeUpdateEarliestExpiration(long expTime) {
-        if (expTime < mNextExpirationCheck) {
-            mNextExpirationCheck = expTime;
-        }
-    }
-
-    /**
-     * Remove expired entries from a map keyed by {@link Inet4Address}.
-     *
-     * @param tag Type of lease in the map, for logging
-     * @param getExpTime Functor returning the expiration time for an object in the map.
-     *                   Must not return null.
-     * @return The lowest expiration time among entries remaining in the map
-     */
-    private <T> long removeExpired(long currentTime, @NonNull Map<Inet4Address, T> map,
-            @NonNull String tag, @NonNull Function<T, Long> getExpTime) {
-        final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator();
-        long firstExpiration = EXPIRATION_NEVER;
-        while (it.hasNext()) {
-            final Entry<Inet4Address, T> lease = it.next();
-            final long expTime = getExpTime.apply(lease.getValue());
-            if (expTime <= currentTime) {
-                mLog.logf("Removing expired %s lease for %s (expTime=%s, currentTime=%s)",
-                        tag, lease.getKey(), expTime, currentTime);
-                it.remove();
-            } else {
-                firstExpiration = min(firstExpiration, expTime);
-            }
-        }
-        return firstExpiration;
-    }
-
-    /**
-     * Go through committed and declined leases and remove the expired ones.
-     */
-    private void removeExpiredLeases(long currentTime) {
-        if (currentTime < mNextExpirationCheck) {
-            return;
-        }
-
-        final long commExp = removeExpired(
-                currentTime, mCommittedLeases, "committed", DhcpLease::getExpTime);
-        final long declExp = removeExpired(
-                currentTime, mDeclinedAddrs, "declined", Function.identity());
-
-        mNextExpirationCheck = min(commExp, declExp);
-    }
-
-    private boolean isAvailable(@NonNull Inet4Address addr) {
-        return !mReservedAddrs.contains(addr) && !mCommittedLeases.containsKey(addr);
-    }
-
-    /**
-     * Get the 0-based index of an address in the subnet.
-     *
-     * <p>Given ordering of addresses 5.6.7.8 < 5.6.7.9 < 5.6.8.0, the index on a subnet is defined
-     * so that the first address is 0, the second 1, etc. For example on a /16, 192.168.0.0 -> 0,
-     * 192.168.0.1 -> 1, 192.168.1.0 -> 256
-     *
-     */
-    private int getAddrIndex(int addr) {
-        return addr & ~mSubnetMask;
-    }
-
-    private int getAddrByIndex(int index) {
-        return mSubnetAddr | index;
-    }
-
-    /**
-     * Get a valid address starting from the supplied one.
-     *
-     * <p>This only checks that the address is numerically valid for assignment, not whether it is
-     * already in use. The return value is always inside the configured prefix, even if the supplied
-     * address is not.
-     *
-     * <p>If the provided address is valid, it is returned as-is. Otherwise, the next valid
-     * address (with the ordering in {@link #getAddrIndex(int)}) is returned.
-     */
-    private int getValidAddress(int addr) {
-        final int lastByteMask = 0xff;
-        int addrIndex = getAddrIndex(addr); // 0-based index of the address in the subnet
-
-        // Some OSes do not handle addresses in .255 or .0 correctly: avoid those.
-        final int lastByte = getAddrByIndex(addrIndex) & lastByteMask;
-        if (lastByte == lastByteMask) {
-            // Avoid .255 address, and .0 address that follows
-            addrIndex = (addrIndex + 2) % mNumAddresses;
-        } else if (lastByte == 0) {
-            // Avoid .0 address
-            addrIndex = (addrIndex + 1) % mNumAddresses;
-        }
-
-        // Do not use first or last address of range
-        if (addrIndex == 0 || addrIndex == mNumAddresses - 1) {
-            // Always valid and not end of range since prefixLength is at most 30 in serving params
-            addrIndex = 1;
-        }
-        return getAddrByIndex(addrIndex);
-    }
-
-    /**
-     * Returns whether the address is in the configured subnet and part of the assignable range.
-     */
-    private boolean isValidAddress(Inet4Address addr) {
-        final int intAddr = inet4AddressToIntHTH(addr);
-        return getValidAddress(intAddr) == intAddr;
-    }
-
-    private int getNextAddress(int addr) {
-        final int addrIndex = getAddrIndex(addr);
-        final int nextAddress = getAddrByIndex((addrIndex + 1) % mNumAddresses);
-        return getValidAddress(nextAddress);
-    }
-
-    /**
-     * Calculate a first candidate address for a client by hashing the hardware address.
-     *
-     * <p>This will be a valid address as checked by {@link #getValidAddress(int)}, but may be
-     * in use.
-     *
-     * @return An IPv4 address encoded as 32-bit int
-     */
-    private int getFirstClientAddress(MacAddress hwAddr) {
-        // This follows dnsmasq behavior. Advantages are: clients will often get the same
-        // offers for different DISCOVER even if the lease was not yet accepted or has expired,
-        // and address generation will generally not need to loop through many allocated addresses
-        // until it finds a free one.
-        int hash = 0;
-        for (byte b : hwAddr.toByteArray()) {
-            hash += b + (b << 8) + (b << 16);
-        }
-        // This implementation will not always result in the same IPs as dnsmasq would give out in
-        // Android <= P, because it includes invalid and reserved addresses in mNumAddresses while
-        // the configured ranges for dnsmasq did not.
-        final int addrIndex = hash % mNumAddresses;
-        return getValidAddress(getAddrByIndex(addrIndex));
-    }
-
-    /**
-     * Create a lease that can be offered to respond to a client DISCOVER.
-     *
-     * <p>This method always succeeds and returns the lease if it does not throw. If no non-declined
-     * address is available, it will try to offer the oldest declined address if valid.
-     *
-     * @throws OutOfAddressesException The server has no address left to offer
-     */
-    private DhcpLease makeNewOffer(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
-            long expTime, @Nullable String hostname) throws OutOfAddressesException {
-        int intAddr = getFirstClientAddress(hwAddr);
-        // Loop until a free address is found, or there are no more addresses.
-        // There is slightly less than this many usable addresses, but some extra looping is OK
-        for (int i = 0; i < mNumAddresses; i++) {
-            final Inet4Address addr = intToInet4AddressHTH(intAddr);
-            if (isAvailable(addr) && !mDeclinedAddrs.containsKey(addr)) {
-                return new DhcpLease(clientId, hwAddr, addr, expTime, hostname);
-            }
-            intAddr = getNextAddress(intAddr);
-        }
-
-        // Try freeing DECLINEd addresses if out of addresses.
-        final Iterator<Inet4Address> it = mDeclinedAddrs.keySet().iterator();
-        while (it.hasNext()) {
-            final Inet4Address addr = it.next();
-            it.remove();
-            mLog.logf("Out of addresses in address pool: dropped declined addr %s",
-                    inet4AddrToString(addr));
-            // isValidAddress() is always verified for entries in mDeclinedAddrs.
-            // However declined addresses may have been requested (typically by the machine that was
-            // already using the address) after being declined.
-            if (isAvailable(addr)) {
-                return new DhcpLease(clientId, hwAddr, addr, expTime, hostname);
-            }
-        }
-
-        throw new OutOfAddressesException("No address available for offer");
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java
deleted file mode 100644
index 1da0b73..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import java.net.Inet4Address;
-import java.nio.ByteBuffer;
-
-/**
- * This class implements the DHCP-NAK packet.
- */
-class DhcpNakPacket extends DhcpPacket {
-    /**
-     * Generates a NAK packet with the specified parameters.
-     */
-    DhcpNakPacket(int transId, short secs, Inet4Address relayIp, byte[] clientMac,
-            boolean broadcast) {
-        super(transId, secs, INADDR_ANY /* clientIp */, INADDR_ANY /* yourIp */,
-                INADDR_ANY /* nextIp */, relayIp, clientMac, broadcast);
-    }
-
-    public String toString() {
-        String s = super.toString();
-        return s + " NAK, reason " + (mMessage == null ? "(none)" : mMessage);
-    }
-
-    /**
-     * Fills in a packet with the requested NAK attributes.
-     */
-    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
-        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
-        // Constructor does not set values for layers <= 3: use empty values
-        Inet4Address destIp = INADDR_ANY;
-        Inet4Address srcIp = INADDR_ANY;
-
-        fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, DHCP_BOOTREPLY, mBroadcast);
-        result.flip();
-        return result;
-    }
-
-    /**
-     * Adds the optional parameters to the client-generated NAK packet.
-     */
-    void finishPacket(ByteBuffer buffer) {
-        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_NAK);
-        addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
-        addTlv(buffer, DHCP_MESSAGE, mMessage);
-        addTlvEnd(buffer);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java
deleted file mode 100644
index 0eba77e..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import java.net.Inet4Address;
-import java.nio.ByteBuffer;
-
-/**
- * This class implements the DHCP-OFFER packet.
- */
-class DhcpOfferPacket extends DhcpPacket {
-    /**
-     * The IP address of the server which sent this packet.
-     */
-    private final Inet4Address mSrcIp;
-
-    /**
-     * Generates a OFFER packet with the specified parameters.
-     */
-    DhcpOfferPacket(int transId, short secs, boolean broadcast, Inet4Address serverAddress,
-            Inet4Address relayIp, Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) {
-        super(transId, secs, clientIp, yourIp, serverAddress, relayIp, clientMac, broadcast);
-        mSrcIp = serverAddress;
-    }
-
-    public String toString() {
-        String s = super.toString();
-        String dnsServers = ", DNS servers: ";
-
-        if (mDnsServers != null) {
-            for (Inet4Address dnsServer: mDnsServers) {
-                dnsServers += dnsServer + " ";
-            }
-        }
-
-        return s + " OFFER, ip " + mYourIp + ", mask " + mSubnetMask +
-                dnsServers + ", gateways " + mGateways +
-                " lease time " + mLeaseTime + ", domain " + mDomainName;
-    }
-
-    /**
-     * Fills in a packet with the specified OFFER attributes.
-     */
-    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
-        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
-        Inet4Address destIp = mBroadcast ? INADDR_BROADCAST : mYourIp;
-        Inet4Address srcIp = mBroadcast ? INADDR_ANY : mSrcIp;
-
-        fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result,
-            DHCP_BOOTREPLY, mBroadcast);
-        result.flip();
-        return result;
-    }
-
-    /**
-     * Adds the optional parameters to the server-generated OFFER packet.
-     */
-    void finishPacket(ByteBuffer buffer) {
-        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_OFFER);
-        addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
-
-        addCommonServerTlvs(buffer);
-        addTlvEnd(buffer);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
deleted file mode 100644
index a15d423..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
+++ /dev/null
@@ -1,1397 +0,0 @@
-package android.net.dhcp;
-
-import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL;
-import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
-
-import android.annotation.Nullable;
-import android.net.DhcpResults;
-import android.net.LinkAddress;
-import android.net.metrics.DhcpErrorEvent;
-import android.net.shared.Inet4AddressUtils;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.system.OsConstants;
-import android.text.TextUtils;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.UnsupportedEncodingException;
-import java.net.Inet4Address;
-import java.net.UnknownHostException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.ShortBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Defines basic data and operations needed to build and use packets for the
- * DHCP protocol.  Subclasses create the specific packets used at each
- * stage of the negotiation.
- *
- * @hide
- */
-public abstract class DhcpPacket {
-    protected static final String TAG = "DhcpPacket";
-
-    // TODO: use NetworkStackConstants.IPV4_MIN_MTU once this class is moved to the network stack.
-    private static final int IPV4_MIN_MTU = 68;
-
-    // dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the
-    // CPU for anything shorter than 5 minutes. For sanity's sake, this must be higher than the
-    // DHCP client timeout.
-    public static final int MINIMUM_LEASE = 60;
-    public static final int INFINITE_LEASE = (int) 0xffffffff;
-
-    public static final Inet4Address INADDR_ANY = IPV4_ADDR_ANY;
-    public static final Inet4Address INADDR_BROADCAST = IPV4_ADDR_ALL;
-    public static final byte[] ETHER_BROADCAST = new byte[] {
-            (byte) 0xff, (byte) 0xff, (byte) 0xff,
-            (byte) 0xff, (byte) 0xff, (byte) 0xff,
-    };
-
-    /**
-     * Packet encapsulations.
-     */
-    public static final int ENCAP_L2 = 0;    // EthernetII header included
-    public static final int ENCAP_L3 = 1;    // IP/UDP header included
-    public static final int ENCAP_BOOTP = 2; // BOOTP contents only
-
-    /**
-     * Minimum length of a DHCP packet, excluding options, in the above encapsulations.
-     */
-    public static final int MIN_PACKET_LENGTH_BOOTP = 236;  // See diagram in RFC 2131, section 2.
-    public static final int MIN_PACKET_LENGTH_L3 = MIN_PACKET_LENGTH_BOOTP + 20 + 8;
-    public static final int MIN_PACKET_LENGTH_L2 = MIN_PACKET_LENGTH_L3 + 14;
-
-    public static final int HWADDR_LEN = 16;
-    public static final int MAX_OPTION_LEN = 255;
-
-    /**
-     * The minimum and maximum MTU that we are prepared to use. We set the minimum to the minimum
-     * IPv6 MTU because the IPv6 stack enters unusual codepaths when the link MTU drops below 1280,
-     * and does not recover if the MTU is brought above 1280 again. We set the maximum to 1500
-     * because in general it is risky to assume that the hardware is able to send/receive packets
-     * larger than 1500 bytes even if the network supports it.
-     */
-    private static final int MIN_MTU = 1280;
-    private static final int MAX_MTU = 1500;
-
-    /**
-     * IP layer definitions.
-     */
-    private static final byte IP_TYPE_UDP = (byte) 0x11;
-
-    /**
-     * IP: Version 4, Header Length 20 bytes
-     */
-    private static final byte IP_VERSION_HEADER_LEN = (byte) 0x45;
-
-    /**
-     * IP: Flags 0, Fragment Offset 0, Don't Fragment
-     */
-    private static final short IP_FLAGS_OFFSET = (short) 0x4000;
-
-    /**
-     * IP: TOS
-     */
-    private static final byte IP_TOS_LOWDELAY = (byte) 0x10;
-
-    /**
-     * IP: TTL -- use default 64 from RFC1340
-     */
-    private static final byte IP_TTL = (byte) 0x40;
-
-    /**
-     * The client DHCP port.
-     */
-    static final short DHCP_CLIENT = (short) 68;
-
-    /**
-     * The server DHCP port.
-     */
-    static final short DHCP_SERVER = (short) 67;
-
-    /**
-     * The message op code indicating a request from a client.
-     */
-    protected static final byte DHCP_BOOTREQUEST = (byte) 1;
-
-    /**
-     * The message op code indicating a response from the server.
-     */
-    protected static final byte DHCP_BOOTREPLY = (byte) 2;
-
-    /**
-     * The code type used to identify an Ethernet MAC address in the
-     * Client-ID field.
-     */
-    protected static final byte CLIENT_ID_ETHER = (byte) 1;
-
-    /**
-     * The maximum length of a packet that can be constructed.
-     */
-    protected static final int MAX_LENGTH = 1500;
-
-    /**
-     * The magic cookie that identifies this as a DHCP packet instead of BOOTP.
-     */
-    private static final int DHCP_MAGIC_COOKIE = 0x63825363;
-
-    /**
-     * DHCP Optional Type: DHCP Subnet Mask
-     */
-    protected static final byte DHCP_SUBNET_MASK = 1;
-    protected Inet4Address mSubnetMask;
-
-    /**
-     * DHCP Optional Type: DHCP Router
-     */
-    protected static final byte DHCP_ROUTER = 3;
-    protected List <Inet4Address> mGateways;
-
-    /**
-     * DHCP Optional Type: DHCP DNS Server
-     */
-    protected static final byte DHCP_DNS_SERVER = 6;
-    protected List<Inet4Address> mDnsServers;
-
-    /**
-     * DHCP Optional Type: DHCP Host Name
-     */
-    protected static final byte DHCP_HOST_NAME = 12;
-    protected String mHostName;
-
-    /**
-     * DHCP Optional Type: DHCP DOMAIN NAME
-     */
-    protected static final byte DHCP_DOMAIN_NAME = 15;
-    protected String mDomainName;
-
-    /**
-     * DHCP Optional Type: DHCP Interface MTU
-     */
-    protected static final byte DHCP_MTU = 26;
-    protected Short mMtu;
-
-    /**
-     * DHCP Optional Type: DHCP BROADCAST ADDRESS
-     */
-    protected static final byte DHCP_BROADCAST_ADDRESS = 28;
-    protected Inet4Address mBroadcastAddress;
-
-    /**
-     * DHCP Optional Type: Vendor specific information
-     */
-    protected static final byte DHCP_VENDOR_INFO = 43;
-    protected String mVendorInfo;
-
-    /**
-     * Value of the vendor specific option used to indicate that the network is metered
-     */
-    public static final String VENDOR_INFO_ANDROID_METERED = "ANDROID_METERED";
-
-    /**
-     * DHCP Optional Type: Option overload option
-     */
-    protected static final byte DHCP_OPTION_OVERLOAD = 52;
-
-    /**
-     * Possible values of the option overload option.
-     */
-    private static final byte OPTION_OVERLOAD_FILE = 1;
-    private static final byte OPTION_OVERLOAD_SNAME = 2;
-    private static final byte OPTION_OVERLOAD_BOTH = 3;
-
-    /**
-     * DHCP Optional Type: DHCP Requested IP Address
-     */
-    protected static final byte DHCP_REQUESTED_IP = 50;
-    protected Inet4Address mRequestedIp;
-
-    /**
-     * DHCP Optional Type: DHCP Lease Time
-     */
-    protected static final byte DHCP_LEASE_TIME = 51;
-    protected Integer mLeaseTime;
-
-    /**
-     * DHCP Optional Type: DHCP Message Type
-     */
-    protected static final byte DHCP_MESSAGE_TYPE = 53;
-    // the actual type values
-    protected static final byte DHCP_MESSAGE_TYPE_DISCOVER = 1;
-    protected static final byte DHCP_MESSAGE_TYPE_OFFER = 2;
-    protected static final byte DHCP_MESSAGE_TYPE_REQUEST = 3;
-    protected static final byte DHCP_MESSAGE_TYPE_DECLINE = 4;
-    protected static final byte DHCP_MESSAGE_TYPE_ACK = 5;
-    protected static final byte DHCP_MESSAGE_TYPE_NAK = 6;
-    protected static final byte DHCP_MESSAGE_TYPE_RELEASE = 7;
-    protected static final byte DHCP_MESSAGE_TYPE_INFORM = 8;
-
-    /**
-     * DHCP Optional Type: DHCP Server Identifier
-     */
-    protected static final byte DHCP_SERVER_IDENTIFIER = 54;
-    protected Inet4Address mServerIdentifier;
-
-    /**
-     * DHCP Optional Type: DHCP Parameter List
-     */
-    protected static final byte DHCP_PARAMETER_LIST = 55;
-    protected byte[] mRequestedParams;
-
-    /**
-     * DHCP Optional Type: DHCP MESSAGE
-     */
-    protected static final byte DHCP_MESSAGE = 56;
-    protected String mMessage;
-
-    /**
-     * DHCP Optional Type: Maximum DHCP Message Size
-     */
-    protected static final byte DHCP_MAX_MESSAGE_SIZE = 57;
-    protected Short mMaxMessageSize;
-
-    /**
-     * DHCP Optional Type: DHCP Renewal Time Value
-     */
-    protected static final byte DHCP_RENEWAL_TIME = 58;
-    protected Integer mT1;
-
-    /**
-     * DHCP Optional Type: Rebinding Time Value
-     */
-    protected static final byte DHCP_REBINDING_TIME = 59;
-    protected Integer mT2;
-
-    /**
-     * DHCP Optional Type: Vendor Class Identifier
-     */
-    protected static final byte DHCP_VENDOR_CLASS_ID = 60;
-    protected String mVendorId;
-
-    /**
-     * DHCP Optional Type: DHCP Client Identifier
-     */
-    protected static final byte DHCP_CLIENT_IDENTIFIER = 61;
-    protected byte[] mClientId;
-
-    /**
-     * DHCP zero-length option code: pad
-     */
-    protected static final byte DHCP_OPTION_PAD = 0x00;
-
-    /**
-     * DHCP zero-length option code: end of options
-     */
-    protected static final byte DHCP_OPTION_END = (byte) 0xff;
-
-    /**
-     * The transaction identifier used in this particular DHCP negotiation
-     */
-    protected final int mTransId;
-
-    /**
-     * The seconds field in the BOOTP header. Per RFC, should be nonzero in client requests only.
-     */
-    protected final short mSecs;
-
-    /**
-     * The IP address of the client host.  This address is typically
-     * proposed by the client (from an earlier DHCP negotiation) or
-     * supplied by the server.
-     */
-    protected final Inet4Address mClientIp;
-    protected final Inet4Address mYourIp;
-    private final Inet4Address mNextIp;
-    protected final Inet4Address mRelayIp;
-
-    /**
-     * Does the client request a broadcast response?
-     */
-    protected boolean mBroadcast;
-
-    /**
-     * The six-octet MAC of the client.
-     */
-    protected final byte[] mClientMac;
-
-    /**
-     * The server host name from server.
-     */
-    protected String mServerHostName;
-
-    /**
-     * Asks the packet object to create a ByteBuffer serialization of
-     * the packet for transmission.
-     */
-    public abstract ByteBuffer buildPacket(int encap, short destUdp,
-        short srcUdp);
-
-    /**
-     * Allows the concrete class to fill in packet-type-specific details,
-     * typically optional parameters at the end of the packet.
-     */
-    abstract void finishPacket(ByteBuffer buffer);
-
-    // Set in unit tests, to ensure that the test does not break when run on different devices and
-    // on different releases.
-    static String testOverrideVendorId = null;
-    static String testOverrideHostname = null;
-
-    protected DhcpPacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp,
-                         Inet4Address nextIp, Inet4Address relayIp,
-                         byte[] clientMac, boolean broadcast) {
-        mTransId = transId;
-        mSecs = secs;
-        mClientIp = clientIp;
-        mYourIp = yourIp;
-        mNextIp = nextIp;
-        mRelayIp = relayIp;
-        mClientMac = clientMac;
-        mBroadcast = broadcast;
-    }
-
-    /**
-     * Returns the transaction ID.
-     */
-    public int getTransactionId() {
-        return mTransId;
-    }
-
-    /**
-     * Returns the client MAC.
-     */
-    public byte[] getClientMac() {
-        return mClientMac;
-    }
-
-    // TODO: refactor DhcpClient to set clientId when constructing packets and remove
-    // hasExplicitClientId logic
-    /**
-     * Returns whether a client ID was set in the options for this packet.
-     */
-    public boolean hasExplicitClientId() {
-        return mClientId != null;
-    }
-
-    /**
-     * Convenience method to return the client ID if it was set explicitly, or null otherwise.
-     */
-    @Nullable
-    public byte[] getExplicitClientIdOrNull() {
-        return hasExplicitClientId() ? getClientId() : null;
-    }
-
-    /**
-     * Returns the client ID. If not set explicitly, this follows RFC 2132 and creates a client ID
-     * based on the hardware address.
-     */
-    public byte[] getClientId() {
-        final byte[] clientId;
-        if (hasExplicitClientId()) {
-            clientId = Arrays.copyOf(mClientId, mClientId.length);
-        } else {
-            clientId = new byte[mClientMac.length + 1];
-            clientId[0] = CLIENT_ID_ETHER;
-            System.arraycopy(mClientMac, 0, clientId, 1, mClientMac.length);
-        }
-        return clientId;
-    }
-
-    /**
-     * Returns whether a parameter is included in the parameter request list option of this packet.
-     *
-     * <p>If there is no parameter request list option in the packet, false is returned.
-     *
-     * @param paramId ID of the parameter, such as {@link #DHCP_MTU} or {@link #DHCP_HOST_NAME}.
-     */
-    public boolean hasRequestedParam(byte paramId) {
-        if (mRequestedParams == null) {
-            return false;
-        }
-
-        for (byte reqParam : mRequestedParams) {
-            if (reqParam == paramId) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Creates a new L3 packet (including IP header) containing the
-     * DHCP udp packet.  This method relies upon the delegated method
-     * finishPacket() to insert the per-packet contents.
-     */
-    protected void fillInPacket(int encap, Inet4Address destIp,
-        Inet4Address srcIp, short destUdp, short srcUdp, ByteBuffer buf,
-        byte requestCode, boolean broadcast) {
-        byte[] destIpArray = destIp.getAddress();
-        byte[] srcIpArray = srcIp.getAddress();
-        int ipHeaderOffset = 0;
-        int ipLengthOffset = 0;
-        int ipChecksumOffset = 0;
-        int endIpHeader = 0;
-        int udpHeaderOffset = 0;
-        int udpLengthOffset = 0;
-        int udpChecksumOffset = 0;
-
-        buf.clear();
-        buf.order(ByteOrder.BIG_ENDIAN);
-
-        if (encap == ENCAP_L2) {
-            buf.put(ETHER_BROADCAST);
-            buf.put(mClientMac);
-            buf.putShort((short) OsConstants.ETH_P_IP);
-        }
-
-        // if a full IP packet needs to be generated, put the IP & UDP
-        // headers in place, and pre-populate with artificial values
-        // needed to seed the IP checksum.
-        if (encap <= ENCAP_L3) {
-            ipHeaderOffset = buf.position();
-            buf.put(IP_VERSION_HEADER_LEN);
-            buf.put(IP_TOS_LOWDELAY);    // tos: IPTOS_LOWDELAY
-            ipLengthOffset = buf.position();
-            buf.putShort((short)0);  // length
-            buf.putShort((short)0);  // id
-            buf.putShort(IP_FLAGS_OFFSET); // ip offset: don't fragment
-            buf.put(IP_TTL);    // TTL: use default 64 from RFC1340
-            buf.put(IP_TYPE_UDP);
-            ipChecksumOffset = buf.position();
-            buf.putShort((short) 0); // checksum
-
-            buf.put(srcIpArray);
-            buf.put(destIpArray);
-            endIpHeader = buf.position();
-
-            // UDP header
-            udpHeaderOffset = buf.position();
-            buf.putShort(srcUdp);
-            buf.putShort(destUdp);
-            udpLengthOffset = buf.position();
-            buf.putShort((short) 0); // length
-            udpChecksumOffset = buf.position();
-            buf.putShort((short) 0); // UDP checksum -- initially zero
-        }
-
-        // DHCP payload
-        buf.put(requestCode);
-        buf.put((byte) 1); // Hardware Type: Ethernet
-        buf.put((byte) mClientMac.length); // Hardware Address Length
-        buf.put((byte) 0); // Hop Count
-        buf.putInt(mTransId);  // Transaction ID
-        buf.putShort(mSecs); // Elapsed Seconds
-
-        if (broadcast) {
-            buf.putShort((short) 0x8000); // Flags
-        } else {
-            buf.putShort((short) 0x0000); // Flags
-        }
-
-        buf.put(mClientIp.getAddress());
-        buf.put(mYourIp.getAddress());
-        buf.put(mNextIp.getAddress());
-        buf.put(mRelayIp.getAddress());
-        buf.put(mClientMac);
-        buf.position(buf.position() +
-                     (HWADDR_LEN - mClientMac.length) // pad addr to 16 bytes
-                     + 64     // empty server host name (64 bytes)
-                     + 128);  // empty boot file name (128 bytes)
-        buf.putInt(DHCP_MAGIC_COOKIE); // magic number
-        finishPacket(buf);
-
-        // round up to an even number of octets
-        if ((buf.position() & 1) == 1) {
-            buf.put((byte) 0);
-        }
-
-        // If an IP packet is being built, the IP & UDP checksums must be
-        // computed.
-        if (encap <= ENCAP_L3) {
-            // fix UDP header: insert length
-            short udpLen = (short)(buf.position() - udpHeaderOffset);
-            buf.putShort(udpLengthOffset, udpLen);
-            // fix UDP header: checksum
-            // checksum for UDP at udpChecksumOffset
-            int udpSeed = 0;
-
-            // apply IPv4 pseudo-header.  Read IP address src and destination
-            // values from the IP header and accumulate checksum.
-            udpSeed += intAbs(buf.getShort(ipChecksumOffset + 2));
-            udpSeed += intAbs(buf.getShort(ipChecksumOffset + 4));
-            udpSeed += intAbs(buf.getShort(ipChecksumOffset + 6));
-            udpSeed += intAbs(buf.getShort(ipChecksumOffset + 8));
-
-            // accumulate extra data for the pseudo-header
-            udpSeed += IP_TYPE_UDP;
-            udpSeed += udpLen;
-            // and compute UDP checksum
-            buf.putShort(udpChecksumOffset, (short) checksum(buf, udpSeed,
-                                                             udpHeaderOffset,
-                                                             buf.position()));
-            // fix IP header: insert length
-            buf.putShort(ipLengthOffset, (short)(buf.position() - ipHeaderOffset));
-            // fixup IP-header checksum
-            buf.putShort(ipChecksumOffset,
-                         (short) checksum(buf, 0, ipHeaderOffset, endIpHeader));
-        }
-    }
-
-    /**
-     * Converts a signed short value to an unsigned int value.  Needed
-     * because Java does not have unsigned types.
-     */
-    private static int intAbs(short v) {
-        return v & 0xFFFF;
-    }
-
-    /**
-     * Performs an IP checksum (used in IP header and across UDP
-     * payload) on the specified portion of a ByteBuffer.  The seed
-     * allows the checksum to commence with a specified value.
-     */
-    private int checksum(ByteBuffer buf, int seed, int start, int end) {
-        int sum = seed;
-        int bufPosition = buf.position();
-
-        // set position of original ByteBuffer, so that the ShortBuffer
-        // will be correctly initialized
-        buf.position(start);
-        ShortBuffer shortBuf = buf.asShortBuffer();
-
-        // re-set ByteBuffer position
-        buf.position(bufPosition);
-
-        short[] shortArray = new short[(end - start) / 2];
-        shortBuf.get(shortArray);
-
-        for (short s : shortArray) {
-            sum += intAbs(s);
-        }
-
-        start += shortArray.length * 2;
-
-        // see if a singleton byte remains
-        if (end != start) {
-            short b = buf.get(start);
-
-            // make it unsigned
-            if (b < 0) {
-                b += 256;
-            }
-
-            sum += b * 256;
-        }
-
-        sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF);
-        sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF);
-        int negated = ~sum;
-        return intAbs((short) negated);
-    }
-
-    /**
-     * Adds an optional parameter containing a single byte value.
-     */
-    protected static void addTlv(ByteBuffer buf, byte type, byte value) {
-        buf.put(type);
-        buf.put((byte) 1);
-        buf.put(value);
-    }
-
-    /**
-     * Adds an optional parameter containing an array of bytes.
-     *
-     * <p>This method is a no-op if the payload argument is null.
-     */
-    protected static void addTlv(ByteBuffer buf, byte type, @Nullable byte[] payload) {
-        if (payload != null) {
-            if (payload.length > MAX_OPTION_LEN) {
-                throw new IllegalArgumentException("DHCP option too long: "
-                        + payload.length + " vs. " + MAX_OPTION_LEN);
-            }
-            buf.put(type);
-            buf.put((byte) payload.length);
-            buf.put(payload);
-        }
-    }
-
-    /**
-     * Adds an optional parameter containing an IP address.
-     *
-     * <p>This method is a no-op if the address argument is null.
-     */
-    protected static void addTlv(ByteBuffer buf, byte type, @Nullable Inet4Address addr) {
-        if (addr != null) {
-            addTlv(buf, type, addr.getAddress());
-        }
-    }
-
-    /**
-     * Adds an optional parameter containing a list of IP addresses.
-     *
-     * <p>This method is a no-op if the addresses argument is null or empty.
-     */
-    protected static void addTlv(ByteBuffer buf, byte type, @Nullable List<Inet4Address> addrs) {
-        if (addrs == null || addrs.size() == 0) return;
-
-        int optionLen = 4 * addrs.size();
-        if (optionLen > MAX_OPTION_LEN) {
-            throw new IllegalArgumentException("DHCP option too long: "
-                    + optionLen + " vs. " + MAX_OPTION_LEN);
-        }
-
-        buf.put(type);
-        buf.put((byte)(optionLen));
-
-        for (Inet4Address addr : addrs) {
-            buf.put(addr.getAddress());
-        }
-    }
-
-    /**
-     * Adds an optional parameter containing a short integer.
-     *
-     * <p>This method is a no-op if the value argument is null.
-     */
-    protected static void addTlv(ByteBuffer buf, byte type, @Nullable Short value) {
-        if (value != null) {
-            buf.put(type);
-            buf.put((byte) 2);
-            buf.putShort(value.shortValue());
-        }
-    }
-
-    /**
-     * Adds an optional parameter containing a simple integer.
-     *
-     * <p>This method is a no-op if the value argument is null.
-     */
-    protected static void addTlv(ByteBuffer buf, byte type, @Nullable Integer value) {
-        if (value != null) {
-            buf.put(type);
-            buf.put((byte) 4);
-            buf.putInt(value.intValue());
-        }
-    }
-
-    /**
-     * Adds an optional parameter containing an ASCII string.
-     *
-     * <p>This method is a no-op if the string argument is null.
-     */
-    protected static void addTlv(ByteBuffer buf, byte type, @Nullable String str) {
-        if (str != null) {
-            try {
-                addTlv(buf, type, str.getBytes("US-ASCII"));
-            } catch (UnsupportedEncodingException e) {
-                throw new IllegalArgumentException("String is not US-ASCII: " + str);
-            }
-        }
-    }
-
-    /**
-     * Adds the special end-of-optional-parameters indicator.
-     */
-    protected static void addTlvEnd(ByteBuffer buf) {
-        buf.put((byte) 0xFF);
-    }
-
-    private String getVendorId() {
-        if (testOverrideVendorId != null) return testOverrideVendorId;
-        return "android-dhcp-" + Build.VERSION.RELEASE;
-    }
-
-    private String getHostname() {
-        if (testOverrideHostname != null) return testOverrideHostname;
-        return SystemProperties.get("net.hostname");
-    }
-
-    /**
-     * Adds common client TLVs.
-     *
-     * TODO: Does this belong here? The alternative would be to modify all the buildXyzPacket
-     * methods to take them.
-     */
-    protected void addCommonClientTlvs(ByteBuffer buf) {
-        addTlv(buf, DHCP_MAX_MESSAGE_SIZE, (short) MAX_LENGTH);
-        addTlv(buf, DHCP_VENDOR_CLASS_ID, getVendorId());
-        final String hn = getHostname();
-        if (!TextUtils.isEmpty(hn)) addTlv(buf, DHCP_HOST_NAME, hn);
-    }
-
-    protected void addCommonServerTlvs(ByteBuffer buf) {
-        addTlv(buf, DHCP_LEASE_TIME, mLeaseTime);
-        if (mLeaseTime != null && mLeaseTime != INFINITE_LEASE) {
-            // The client should renew at 1/2 the lease-expiry interval
-            addTlv(buf, DHCP_RENEWAL_TIME, (int) (Integer.toUnsignedLong(mLeaseTime) / 2));
-            // Default rebinding time is set as below by RFC2131
-            addTlv(buf, DHCP_REBINDING_TIME,
-                    (int) (Integer.toUnsignedLong(mLeaseTime) * 875L / 1000L));
-        }
-        addTlv(buf, DHCP_SUBNET_MASK, mSubnetMask);
-        addTlv(buf, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
-        addTlv(buf, DHCP_ROUTER, mGateways);
-        addTlv(buf, DHCP_DNS_SERVER, mDnsServers);
-        addTlv(buf, DHCP_DOMAIN_NAME, mDomainName);
-        addTlv(buf, DHCP_HOST_NAME, mHostName);
-        addTlv(buf, DHCP_VENDOR_INFO, mVendorInfo);
-        if (mMtu != null && Short.toUnsignedInt(mMtu) >= IPV4_MIN_MTU) {
-            addTlv(buf, DHCP_MTU, mMtu);
-        }
-    }
-
-    /**
-     * Converts a MAC from an array of octets to an ASCII string.
-     */
-    public static String macToString(byte[] mac) {
-        String macAddr = "";
-
-        for (int i = 0; i < mac.length; i++) {
-            String hexString = "0" + Integer.toHexString(mac[i]);
-
-            // substring operation grabs the last 2 digits: this
-            // allows signed bytes to be converted correctly.
-            macAddr += hexString.substring(hexString.length() - 2);
-
-            if (i != (mac.length - 1)) {
-                macAddr += ":";
-            }
-        }
-
-        return macAddr;
-    }
-
-    public String toString() {
-        String macAddr = macToString(mClientMac);
-
-        return macAddr;
-    }
-
-    /**
-     * Reads a four-octet value from a ByteBuffer and construct
-     * an IPv4 address from that value.
-     */
-    private static Inet4Address readIpAddress(ByteBuffer packet) {
-        Inet4Address result = null;
-        byte[] ipAddr = new byte[4];
-        packet.get(ipAddr);
-
-        try {
-            result = (Inet4Address) Inet4Address.getByAddress(ipAddr);
-        } catch (UnknownHostException ex) {
-            // ipAddr is numeric, so this should not be
-            // triggered.  However, if it is, just nullify
-            result = null;
-        }
-
-        return result;
-    }
-
-    /**
-     * Reads a string of specified length from the buffer.
-     */
-    private static String readAsciiString(ByteBuffer buf, int byteCount, boolean nullOk) {
-        byte[] bytes = new byte[byteCount];
-        buf.get(bytes);
-        int length = bytes.length;
-        if (!nullOk) {
-            // Stop at the first null byte. This is because some DHCP options (e.g., the domain
-            // name) are passed to netd via FrameworkListener, which refuses arguments containing
-            // null bytes. We don't do this by default because vendorInfo is an opaque string which
-            // could in theory contain null bytes.
-            for (length = 0; length < bytes.length; length++) {
-                if (bytes[length] == 0) {
-                    break;
-                }
-            }
-        }
-        return new String(bytes, 0, length, StandardCharsets.US_ASCII);
-    }
-
-    private static boolean isPacketToOrFromClient(short udpSrcPort, short udpDstPort) {
-        return (udpSrcPort == DHCP_CLIENT) || (udpDstPort == DHCP_CLIENT);
-    }
-
-    private static boolean isPacketServerToServer(short udpSrcPort, short udpDstPort) {
-        return (udpSrcPort == DHCP_SERVER) && (udpDstPort == DHCP_SERVER);
-    }
-
-    public static class ParseException extends Exception {
-        public final int errorCode;
-        public ParseException(int errorCode, String msg, Object... args) {
-            super(String.format(msg, args));
-            this.errorCode = errorCode;
-        }
-    }
-
-    /**
-     * Creates a concrete DhcpPacket from the supplied ByteBuffer.  The
-     * buffer may have an L2 encapsulation (which is the full EthernetII
-     * format starting with the source-address MAC) or an L3 encapsulation
-     * (which starts with the IP header).
-     * <br>
-     * A subset of the optional parameters are parsed and are stored
-     * in object fields.
-     */
-    @VisibleForTesting
-    static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) throws ParseException
-    {
-        // bootp parameters
-        int transactionId;
-        short secs;
-        Inet4Address clientIp;
-        Inet4Address yourIp;
-        Inet4Address nextIp;
-        Inet4Address relayIp;
-        byte[] clientMac;
-        byte[] clientId = null;
-        List<Inet4Address> dnsServers = new ArrayList<>();
-        List<Inet4Address> gateways = new ArrayList<>();  // aka router
-        Inet4Address serverIdentifier = null;
-        Inet4Address netMask = null;
-        String message = null;
-        String vendorId = null;
-        String vendorInfo = null;
-        byte[] expectedParams = null;
-        String hostName = null;
-        String domainName = null;
-        Inet4Address ipSrc = null;
-        Inet4Address ipDst = null;
-        Inet4Address bcAddr = null;
-        Inet4Address requestedIp = null;
-        String serverHostName;
-        byte optionOverload = 0;
-
-        // The following are all unsigned integers. Internally we store them as signed integers of
-        // the same length because that way we're guaranteed that they can't be out of the range of
-        // the unsigned field in the packet. Callers wanting to pass in an unsigned value will need
-        // to cast it.
-        Short mtu = null;
-        Short maxMessageSize = null;
-        Integer leaseTime = null;
-        Integer T1 = null;
-        Integer T2 = null;
-
-        // dhcp options
-        byte dhcpType = (byte) 0xFF;
-
-        packet.order(ByteOrder.BIG_ENDIAN);
-
-        // check to see if we need to parse L2, IP, and UDP encaps
-        if (pktType == ENCAP_L2) {
-            if (packet.remaining() < MIN_PACKET_LENGTH_L2) {
-                throw new ParseException(DhcpErrorEvent.L2_TOO_SHORT,
-                        "L2 packet too short, %d < %d", packet.remaining(), MIN_PACKET_LENGTH_L2);
-            }
-
-            byte[] l2dst = new byte[6];
-            byte[] l2src = new byte[6];
-
-            packet.get(l2dst);
-            packet.get(l2src);
-
-            short l2type = packet.getShort();
-
-            if (l2type != OsConstants.ETH_P_IP) {
-                throw new ParseException(DhcpErrorEvent.L2_WRONG_ETH_TYPE,
-                        "Unexpected L2 type 0x%04x, expected 0x%04x", l2type, OsConstants.ETH_P_IP);
-            }
-        }
-
-        if (pktType <= ENCAP_L3) {
-            if (packet.remaining() < MIN_PACKET_LENGTH_L3) {
-                throw new ParseException(DhcpErrorEvent.L3_TOO_SHORT,
-                        "L3 packet too short, %d < %d", packet.remaining(), MIN_PACKET_LENGTH_L3);
-            }
-
-            byte ipTypeAndLength = packet.get();
-            int ipVersion = (ipTypeAndLength & 0xf0) >> 4;
-            if (ipVersion != 4) {
-                throw new ParseException(
-                        DhcpErrorEvent.L3_NOT_IPV4, "Invalid IP version %d", ipVersion);
-            }
-
-            // System.out.println("ipType is " + ipType);
-            byte ipDiffServicesField = packet.get();
-            short ipTotalLength = packet.getShort();
-            short ipIdentification = packet.getShort();
-            byte ipFlags = packet.get();
-            byte ipFragOffset = packet.get();
-            byte ipTTL = packet.get();
-            byte ipProto = packet.get();
-            short ipChksm = packet.getShort();
-
-            ipSrc = readIpAddress(packet);
-            ipDst = readIpAddress(packet);
-
-            if (ipProto != IP_TYPE_UDP) {
-                throw new ParseException(
-                        DhcpErrorEvent.L4_NOT_UDP, "Protocol not UDP: %d", ipProto);
-            }
-
-            // Skip options. This cannot cause us to read beyond the end of the buffer because the
-            // IPv4 header cannot be more than (0x0f * 4) = 60 bytes long, and that is less than
-            // MIN_PACKET_LENGTH_L3.
-            int optionWords = ((ipTypeAndLength & 0x0f) - 5);
-            for (int i = 0; i < optionWords; i++) {
-                packet.getInt();
-            }
-
-            // assume UDP
-            short udpSrcPort = packet.getShort();
-            short udpDstPort = packet.getShort();
-            short udpLen = packet.getShort();
-            short udpChkSum = packet.getShort();
-
-            // Only accept packets to or from the well-known client port (expressly permitting
-            // packets from ports other than the well-known server port; http://b/24687559), and
-            // server-to-server packets, e.g. for relays.
-            if (!isPacketToOrFromClient(udpSrcPort, udpDstPort) &&
-                !isPacketServerToServer(udpSrcPort, udpDstPort)) {
-                // This should almost never happen because we use SO_ATTACH_FILTER on the packet
-                // socket to drop packets that don't have the right source ports. However, it's
-                // possible that a packet arrives between when the socket is bound and when the
-                // filter is set. http://b/26696823 .
-                throw new ParseException(DhcpErrorEvent.L4_WRONG_PORT,
-                        "Unexpected UDP ports %d->%d", udpSrcPort, udpDstPort);
-            }
-        }
-
-        // We need to check the length even for ENCAP_L3 because the IPv4 header is variable-length.
-        if (pktType > ENCAP_BOOTP || packet.remaining() < MIN_PACKET_LENGTH_BOOTP) {
-            throw new ParseException(DhcpErrorEvent.BOOTP_TOO_SHORT,
-                        "Invalid type or BOOTP packet too short, %d < %d",
-                        packet.remaining(), MIN_PACKET_LENGTH_BOOTP);
-        }
-
-        byte type = packet.get();
-        byte hwType = packet.get();
-        int addrLen = packet.get() & 0xff;
-        byte hops = packet.get();
-        transactionId = packet.getInt();
-        secs = packet.getShort();
-        short bootpFlags = packet.getShort();
-        boolean broadcast = (bootpFlags & 0x8000) != 0;
-        byte[] ipv4addr = new byte[4];
-
-        try {
-            packet.get(ipv4addr);
-            clientIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
-            packet.get(ipv4addr);
-            yourIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
-            packet.get(ipv4addr);
-            nextIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
-            packet.get(ipv4addr);
-            relayIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
-        } catch (UnknownHostException ex) {
-            throw new ParseException(DhcpErrorEvent.L3_INVALID_IP,
-                    "Invalid IPv4 address: %s", Arrays.toString(ipv4addr));
-        }
-
-        // Some DHCP servers have been known to announce invalid client hardware address values such
-        // as 0xff. The legacy DHCP client accepted these becuause it does not check the length at
-        // all but only checks that the interface MAC address matches the first bytes of the address
-        // in the packets. We're a bit stricter: if the length is obviously invalid (i.e., bigger
-        // than the size of the field), we fudge it to 6 (Ethernet). http://b/23725795
-        // TODO: evaluate whether to make this test more liberal.
-        if (addrLen > HWADDR_LEN) {
-            addrLen = ETHER_BROADCAST.length;
-        }
-
-        clientMac = new byte[addrLen];
-        packet.get(clientMac);
-
-        // skip over address padding (16 octets allocated)
-        packet.position(packet.position() + (16 - addrLen));
-        serverHostName = readAsciiString(packet, 64, false);
-        packet.position(packet.position() + 128);
-
-        // Ensure this is a DHCP packet with a magic cookie, and not BOOTP. http://b/31850211
-        if (packet.remaining() < 4) {
-            throw new ParseException(DhcpErrorEvent.DHCP_NO_COOKIE, "not a DHCP message");
-        }
-
-        int dhcpMagicCookie = packet.getInt();
-        if (dhcpMagicCookie != DHCP_MAGIC_COOKIE) {
-            throw new ParseException(DhcpErrorEvent.DHCP_BAD_MAGIC_COOKIE,
-                    "Bad magic cookie 0x%08x, should be 0x%08x",
-                    dhcpMagicCookie, DHCP_MAGIC_COOKIE);
-        }
-
-        // parse options
-        boolean notFinishedOptions = true;
-
-        while ((packet.position() < packet.limit()) && notFinishedOptions) {
-            final byte optionType = packet.get(); // cannot underflow because position < limit
-            try {
-                if (optionType == DHCP_OPTION_END) {
-                    notFinishedOptions = false;
-                } else if (optionType == DHCP_OPTION_PAD) {
-                    // The pad option doesn't have a length field. Nothing to do.
-                } else {
-                    int optionLen = packet.get() & 0xFF;
-                    int expectedLen = 0;
-
-                    switch(optionType) {
-                        case DHCP_SUBNET_MASK:
-                            netMask = readIpAddress(packet);
-                            expectedLen = 4;
-                            break;
-                        case DHCP_ROUTER:
-                            for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) {
-                                gateways.add(readIpAddress(packet));
-                            }
-                            break;
-                        case DHCP_DNS_SERVER:
-                            for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) {
-                                dnsServers.add(readIpAddress(packet));
-                            }
-                            break;
-                        case DHCP_HOST_NAME:
-                            expectedLen = optionLen;
-                            hostName = readAsciiString(packet, optionLen, false);
-                            break;
-                        case DHCP_MTU:
-                            expectedLen = 2;
-                            mtu = packet.getShort();
-                            break;
-                        case DHCP_DOMAIN_NAME:
-                            expectedLen = optionLen;
-                            domainName = readAsciiString(packet, optionLen, false);
-                            break;
-                        case DHCP_BROADCAST_ADDRESS:
-                            bcAddr = readIpAddress(packet);
-                            expectedLen = 4;
-                            break;
-                        case DHCP_REQUESTED_IP:
-                            requestedIp = readIpAddress(packet);
-                            expectedLen = 4;
-                            break;
-                        case DHCP_LEASE_TIME:
-                            leaseTime = Integer.valueOf(packet.getInt());
-                            expectedLen = 4;
-                            break;
-                        case DHCP_MESSAGE_TYPE:
-                            dhcpType = packet.get();
-                            expectedLen = 1;
-                            break;
-                        case DHCP_SERVER_IDENTIFIER:
-                            serverIdentifier = readIpAddress(packet);
-                            expectedLen = 4;
-                            break;
-                        case DHCP_PARAMETER_LIST:
-                            expectedParams = new byte[optionLen];
-                            packet.get(expectedParams);
-                            expectedLen = optionLen;
-                            break;
-                        case DHCP_MESSAGE:
-                            expectedLen = optionLen;
-                            message = readAsciiString(packet, optionLen, false);
-                            break;
-                        case DHCP_MAX_MESSAGE_SIZE:
-                            expectedLen = 2;
-                            maxMessageSize = Short.valueOf(packet.getShort());
-                            break;
-                        case DHCP_RENEWAL_TIME:
-                            expectedLen = 4;
-                            T1 = Integer.valueOf(packet.getInt());
-                            break;
-                        case DHCP_REBINDING_TIME:
-                            expectedLen = 4;
-                            T2 = Integer.valueOf(packet.getInt());
-                            break;
-                        case DHCP_VENDOR_CLASS_ID:
-                            expectedLen = optionLen;
-                            // Embedded nulls are safe as this does not get passed to netd.
-                            vendorId = readAsciiString(packet, optionLen, true);
-                            break;
-                        case DHCP_CLIENT_IDENTIFIER: { // Client identifier
-                            byte[] id = new byte[optionLen];
-                            packet.get(id);
-                            expectedLen = optionLen;
-                        } break;
-                        case DHCP_VENDOR_INFO:
-                            expectedLen = optionLen;
-                            // Embedded nulls are safe as this does not get passed to netd.
-                            vendorInfo = readAsciiString(packet, optionLen, true);
-                            break;
-                        case DHCP_OPTION_OVERLOAD:
-                            expectedLen = 1;
-                            optionOverload = packet.get();
-                            optionOverload &= OPTION_OVERLOAD_BOTH;
-                            break;
-                        default:
-                            // ignore any other parameters
-                            for (int i = 0; i < optionLen; i++) {
-                                expectedLen++;
-                                byte throwaway = packet.get();
-                            }
-                    }
-
-                    if (expectedLen != optionLen) {
-                        final int errorCode = DhcpErrorEvent.errorCodeWithOption(
-                                DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH, optionType);
-                        throw new ParseException(errorCode,
-                                "Invalid length %d for option %d, expected %d",
-                                optionLen, optionType, expectedLen);
-                    }
-                }
-            } catch (BufferUnderflowException e) {
-                final int errorCode = DhcpErrorEvent.errorCodeWithOption(
-                        DhcpErrorEvent.BUFFER_UNDERFLOW, optionType);
-                throw new ParseException(errorCode, "BufferUnderflowException");
-            }
-        }
-
-        DhcpPacket newPacket;
-
-        switch(dhcpType) {
-            case (byte) 0xFF:
-                throw new ParseException(DhcpErrorEvent.DHCP_NO_MSG_TYPE,
-                        "No DHCP message type option");
-            case DHCP_MESSAGE_TYPE_DISCOVER:
-                newPacket = new DhcpDiscoverPacket(transactionId, secs, relayIp, clientMac,
-                        broadcast, ipSrc);
-                break;
-            case DHCP_MESSAGE_TYPE_OFFER:
-                newPacket = new DhcpOfferPacket(
-                    transactionId, secs, broadcast, ipSrc, relayIp, clientIp, yourIp, clientMac);
-                break;
-            case DHCP_MESSAGE_TYPE_REQUEST:
-                newPacket = new DhcpRequestPacket(
-                    transactionId, secs, clientIp, relayIp, clientMac, broadcast);
-                break;
-            case DHCP_MESSAGE_TYPE_DECLINE:
-                newPacket = new DhcpDeclinePacket(
-                    transactionId, secs, clientIp, yourIp, nextIp, relayIp,
-                    clientMac);
-                break;
-            case DHCP_MESSAGE_TYPE_ACK:
-                newPacket = new DhcpAckPacket(
-                    transactionId, secs, broadcast, ipSrc, relayIp, clientIp, yourIp, clientMac);
-                break;
-            case DHCP_MESSAGE_TYPE_NAK:
-                newPacket = new DhcpNakPacket(
-                        transactionId, secs, relayIp, clientMac, broadcast);
-                break;
-            case DHCP_MESSAGE_TYPE_RELEASE:
-                if (serverIdentifier == null) {
-                    throw new ParseException(DhcpErrorEvent.MISC_ERROR,
-                            "DHCPRELEASE without server identifier");
-                }
-                newPacket = new DhcpReleasePacket(
-                        transactionId, serverIdentifier, clientIp, relayIp, clientMac);
-                break;
-            case DHCP_MESSAGE_TYPE_INFORM:
-                newPacket = new DhcpInformPacket(
-                    transactionId, secs, clientIp, yourIp, nextIp, relayIp,
-                    clientMac);
-                break;
-            default:
-                throw new ParseException(DhcpErrorEvent.DHCP_UNKNOWN_MSG_TYPE,
-                        "Unimplemented DHCP type %d", dhcpType);
-        }
-
-        newPacket.mBroadcastAddress = bcAddr;
-        newPacket.mClientId = clientId;
-        newPacket.mDnsServers = dnsServers;
-        newPacket.mDomainName = domainName;
-        newPacket.mGateways = gateways;
-        newPacket.mHostName = hostName;
-        newPacket.mLeaseTime = leaseTime;
-        newPacket.mMessage = message;
-        newPacket.mMtu = mtu;
-        newPacket.mRequestedIp = requestedIp;
-        newPacket.mRequestedParams = expectedParams;
-        newPacket.mServerIdentifier = serverIdentifier;
-        newPacket.mSubnetMask = netMask;
-        newPacket.mMaxMessageSize = maxMessageSize;
-        newPacket.mT1 = T1;
-        newPacket.mT2 = T2;
-        newPacket.mVendorId = vendorId;
-        newPacket.mVendorInfo = vendorInfo;
-        if ((optionOverload & OPTION_OVERLOAD_SNAME) == 0) {
-            newPacket.mServerHostName = serverHostName;
-        } else {
-            newPacket.mServerHostName = "";
-        }
-        return newPacket;
-    }
-
-    /**
-     * Parse a packet from an array of bytes, stopping at the given length.
-     */
-    public static DhcpPacket decodeFullPacket(byte[] packet, int length, int pktType)
-            throws ParseException {
-        ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN);
-        try {
-            return decodeFullPacket(buffer, pktType);
-        } catch (ParseException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new ParseException(DhcpErrorEvent.PARSING_ERROR, e.getMessage());
-        }
-    }
-
-    /**
-     *  Construct a DhcpResults object from a DHCP reply packet.
-     */
-    public DhcpResults toDhcpResults() {
-        Inet4Address ipAddress = mYourIp;
-        if (ipAddress.equals(IPV4_ADDR_ANY)) {
-            ipAddress = mClientIp;
-            if (ipAddress.equals(IPV4_ADDR_ANY)) {
-                return null;
-            }
-        }
-
-        int prefixLength;
-        if (mSubnetMask != null) {
-            try {
-                prefixLength = Inet4AddressUtils.netmaskToPrefixLength(mSubnetMask);
-            } catch (IllegalArgumentException e) {
-                // Non-contiguous netmask.
-                return null;
-            }
-        } else {
-            prefixLength = Inet4AddressUtils.getImplicitNetmask(ipAddress);
-        }
-
-        DhcpResults results = new DhcpResults();
-        try {
-            results.ipAddress = new LinkAddress(ipAddress, prefixLength);
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
-
-        if (mGateways.size() > 0) {
-            results.gateway = mGateways.get(0);
-        }
-
-        results.dnsServers.addAll(mDnsServers);
-        results.domains = mDomainName;
-        results.serverAddress = mServerIdentifier;
-        results.vendorInfo = mVendorInfo;
-        results.leaseDuration = (mLeaseTime != null) ? mLeaseTime : INFINITE_LEASE;
-        results.mtu = (mMtu != null && MIN_MTU <= mMtu && mMtu <= MAX_MTU) ? mMtu : 0;
-        results.serverHostName = mServerHostName;
-
-        return results;
-    }
-
-    /**
-     * Returns the parsed lease time, in milliseconds, or 0 for infinite.
-     */
-    public long getLeaseTimeMillis() {
-        // dhcpcd treats the lack of a lease time option as an infinite lease.
-        if (mLeaseTime == null || mLeaseTime == INFINITE_LEASE) {
-            return 0;
-        } else if (0 <= mLeaseTime && mLeaseTime < MINIMUM_LEASE) {
-            return MINIMUM_LEASE * 1000;
-        } else {
-            return (mLeaseTime & 0xffffffffL) * 1000;
-        }
-    }
-
-    /**
-     * Builds a DHCP-DISCOVER packet from the required specified
-     * parameters.
-     */
-    public static ByteBuffer buildDiscoverPacket(int encap, int transactionId,
-        short secs, byte[] clientMac, boolean broadcast, byte[] expectedParams) {
-        DhcpPacket pkt = new DhcpDiscoverPacket(transactionId, secs, INADDR_ANY /* relayIp */,
-                clientMac, broadcast, INADDR_ANY /* srcIp */);
-        pkt.mRequestedParams = expectedParams;
-        return pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT);
-    }
-
-    /**
-     * Builds a DHCP-OFFER packet from the required specified
-     * parameters.
-     */
-    public static ByteBuffer buildOfferPacket(int encap, int transactionId,
-        boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp,
-        Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask,
-        Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
-        Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
-        short mtu) {
-        DhcpPacket pkt = new DhcpOfferPacket(
-                transactionId, (short) 0, broadcast, serverIpAddr, relayIp,
-                INADDR_ANY /* clientIp */, yourIp, mac);
-        pkt.mGateways = gateways;
-        pkt.mDnsServers = dnsServers;
-        pkt.mLeaseTime = timeout;
-        pkt.mDomainName = domainName;
-        pkt.mHostName = hostname;
-        pkt.mServerIdentifier = dhcpServerIdentifier;
-        pkt.mSubnetMask = netMask;
-        pkt.mBroadcastAddress = bcAddr;
-        pkt.mMtu = mtu;
-        if (metered) {
-            pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
-        }
-        return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
-    }
-
-    /**
-     * Builds a DHCP-ACK packet from the required specified parameters.
-     */
-    public static ByteBuffer buildAckPacket(int encap, int transactionId,
-        boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp, Inet4Address yourIp,
-        Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask,
-        Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
-        Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
-        short mtu) {
-        DhcpPacket pkt = new DhcpAckPacket(
-                transactionId, (short) 0, broadcast, serverIpAddr, relayIp, requestClientIp, yourIp,
-                mac);
-        pkt.mGateways = gateways;
-        pkt.mDnsServers = dnsServers;
-        pkt.mLeaseTime = timeout;
-        pkt.mDomainName = domainName;
-        pkt.mHostName = hostname;
-        pkt.mSubnetMask = netMask;
-        pkt.mServerIdentifier = dhcpServerIdentifier;
-        pkt.mBroadcastAddress = bcAddr;
-        pkt.mMtu = mtu;
-        if (metered) {
-            pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
-        }
-        return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
-    }
-
-    /**
-     * Builds a DHCP-NAK packet from the required specified parameters.
-     */
-    public static ByteBuffer buildNakPacket(int encap, int transactionId, Inet4Address serverIpAddr,
-            Inet4Address relayIp, byte[] mac, boolean broadcast, String message) {
-        DhcpPacket pkt = new DhcpNakPacket(
-                transactionId, (short) 0, relayIp, mac, broadcast);
-        pkt.mMessage = message;
-        pkt.mServerIdentifier = serverIpAddr;
-        return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
-    }
-
-    /**
-     * Builds a DHCP-REQUEST packet from the required specified parameters.
-     */
-    public static ByteBuffer buildRequestPacket(int encap,
-        int transactionId, short secs, Inet4Address clientIp, boolean broadcast,
-        byte[] clientMac, Inet4Address requestedIpAddress,
-        Inet4Address serverIdentifier, byte[] requestedParams, String hostName) {
-        DhcpPacket pkt = new DhcpRequestPacket(transactionId, secs, clientIp,
-                INADDR_ANY /* relayIp */, clientMac, broadcast);
-        pkt.mRequestedIp = requestedIpAddress;
-        pkt.mServerIdentifier = serverIdentifier;
-        pkt.mHostName = hostName;
-        pkt.mRequestedParams = requestedParams;
-        ByteBuffer result = pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT);
-        return result;
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java
deleted file mode 100644
index 97d26c7..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.dhcp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.util.FdEventsReader;
-import android.os.Handler;
-import android.system.Os;
-
-import java.io.FileDescriptor;
-import java.net.Inet4Address;
-import java.net.InetSocketAddress;
-
-/**
- * A {@link FdEventsReader} to receive and parse {@link DhcpPacket}.
- * @hide
- */
-abstract class DhcpPacketListener extends FdEventsReader<DhcpPacketListener.Payload> {
-    static final class Payload {
-        protected final byte[] mBytes = new byte[DhcpPacket.MAX_LENGTH];
-        protected Inet4Address mSrcAddr;
-        protected int mSrcPort;
-    }
-
-    DhcpPacketListener(@NonNull Handler handler) {
-        super(handler, new Payload());
-    }
-
-    @Override
-    protected int recvBufSize(@NonNull Payload buffer) {
-        return buffer.mBytes.length;
-    }
-
-    @Override
-    protected final void handlePacket(@NonNull Payload recvbuf, int length) {
-        if (recvbuf.mSrcAddr == null) {
-            return;
-        }
-
-        try {
-            final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf.mBytes, length,
-                    DhcpPacket.ENCAP_BOOTP);
-            onReceive(packet, recvbuf.mSrcAddr, recvbuf.mSrcPort);
-        } catch (DhcpPacket.ParseException e) {
-            logParseError(recvbuf.mBytes, length, e);
-        }
-    }
-
-    @Override
-    protected int readPacket(@NonNull FileDescriptor fd, @NonNull Payload packetBuffer)
-            throws Exception {
-        final InetSocketAddress addr = new InetSocketAddress(0);
-        final int read = Os.recvfrom(
-                fd, packetBuffer.mBytes, 0, packetBuffer.mBytes.length, 0 /* flags */, addr);
-
-        // Buffers with null srcAddr will be dropped in handlePacket()
-        packetBuffer.mSrcAddr = inet4AddrOrNull(addr);
-        packetBuffer.mSrcPort = addr.getPort();
-        return read;
-    }
-
-    @Nullable
-    private static Inet4Address inet4AddrOrNull(@NonNull InetSocketAddress addr) {
-        return addr.getAddress() instanceof Inet4Address
-                ? (Inet4Address) addr.getAddress()
-                : null;
-    }
-
-    protected abstract void onReceive(@NonNull DhcpPacket packet, @NonNull Inet4Address srcAddr,
-            int srcPort);
-    protected abstract void logParseError(@NonNull byte[] packet, int length,
-            @NonNull DhcpPacket.ParseException e);
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java
deleted file mode 100644
index 3958303..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.dhcp;
-
-import java.net.Inet4Address;
-import java.nio.ByteBuffer;
-
-/**
- * Implements DHCP-RELEASE
- */
-class DhcpReleasePacket extends DhcpPacket {
-
-    final Inet4Address mClientAddr;
-
-    /**
-     * Generates a RELEASE packet with the specified parameters.
-     */
-    public DhcpReleasePacket(int transId, Inet4Address serverId, Inet4Address clientAddr,
-            Inet4Address relayIp, byte[] clientMac) {
-        super(transId, (short)0, clientAddr, INADDR_ANY /* yourIp */, INADDR_ANY /* nextIp */,
-                relayIp, clientMac, false /* broadcast */);
-        mServerIdentifier = serverId;
-        mClientAddr = clientAddr;
-    }
-
-
-    @Override
-    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
-        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
-        fillInPacket(encap, mServerIdentifier /* destIp */, mClientIp /* srcIp */, destUdp, srcUdp,
-                result, DHCP_BOOTREPLY, mBroadcast);
-        result.flip();
-        return result;
-    }
-
-    @Override
-    void finishPacket(ByteBuffer buffer) {
-        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_RELEASE);
-        addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
-        addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
-        addCommonClientTlvs(buffer);
-        addTlvEnd(buffer);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java
deleted file mode 100644
index 231d0457..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import android.util.Log;
-
-import java.net.Inet4Address;
-import java.nio.ByteBuffer;
-
-/**
- * This class implements the DHCP-REQUEST packet.
- */
-class DhcpRequestPacket extends DhcpPacket {
-    /**
-     * Generates a REQUEST packet with the specified parameters.
-     */
-    DhcpRequestPacket(int transId, short secs, Inet4Address clientIp, Inet4Address relayIp,
-            byte[] clientMac, boolean broadcast) {
-        super(transId, secs, clientIp, INADDR_ANY, INADDR_ANY, relayIp, clientMac, broadcast);
-    }
-
-    public String toString() {
-        String s = super.toString();
-        return s + " REQUEST, desired IP " + mRequestedIp + " from host '"
-            + mHostName + "', param list length "
-            + (mRequestedParams == null ? 0 : mRequestedParams.length);
-    }
-
-    /**
-     * Fills in a packet with the requested REQUEST attributes.
-     */
-    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
-        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
-
-        fillInPacket(encap, INADDR_BROADCAST, INADDR_ANY, destUdp, srcUdp,
-            result, DHCP_BOOTREQUEST, mBroadcast);
-        result.flip();
-        return result;
-    }
-
-    /**
-     * Adds the optional parameters to the client-generated REQUEST packet.
-     */
-    void finishPacket(ByteBuffer buffer) {
-        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST);
-        addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
-        if (!INADDR_ANY.equals(mRequestedIp)) {
-            addTlv(buffer, DHCP_REQUESTED_IP, mRequestedIp);
-        }
-        if (!INADDR_ANY.equals(mServerIdentifier)) {
-            addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
-        }
-        addCommonClientTlvs(buffer);
-        addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
-        addTlvEnd(buffer);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
deleted file mode 100644
index b8ab94c..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
+++ /dev/null
@@ -1,655 +0,0 @@
-/*
- * 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.dhcp;
-
-import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
-import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
-import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
-import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
-import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
-import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
-import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOCK_NONBLOCK;
-import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_BROADCAST;
-import static android.system.OsConstants.SO_REUSEADDR;
-
-import static com.android.internal.util.TrafficStatsConstants.TAG_SYSTEM_DHCP_SERVER;
-import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
-import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL;
-import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
-import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
-
-import static java.lang.Integer.toUnsignedLong;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.INetworkStackStatusCallback;
-import android.net.MacAddress;
-import android.net.TrafficStats;
-import android.net.util.NetworkStackUtils;
-import android.net.util.SharedLog;
-import android.net.util.SocketUtils;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.TextUtils;
-import android.util.Pair;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.HexDump;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
-/**
- * A DHCPv4 server.
- *
- * <p>This server listens for and responds to packets on a single interface. It considers itself
- * authoritative for all leases on the subnet, which means that DHCP requests for unknown leases of
- * unknown hosts receive a reply instead of being ignored.
- *
- * <p>The server is single-threaded (including send/receive operations): all internal operations are
- * done on the provided {@link Looper}. Public methods are thread-safe and will schedule operations
- * on the looper asynchronously.
- * @hide
- */
-public class DhcpServer extends IDhcpServer.Stub {
-    private static final String REPO_TAG = "Repository";
-
-    // Lease time to transmit to client instead of a negative time in case a lease expired before
-    // the server could send it (if the server process is suspended for example).
-    private static final int EXPIRED_FALLBACK_LEASE_TIME_SECS = 120;
-
-    private static final int CMD_START_DHCP_SERVER = 1;
-    private static final int CMD_STOP_DHCP_SERVER = 2;
-    private static final int CMD_UPDATE_PARAMS = 3;
-
-    @NonNull
-    private final HandlerThread mHandlerThread;
-    @NonNull
-    private final String mIfName;
-    @NonNull
-    private final DhcpLeaseRepository mLeaseRepo;
-    @NonNull
-    private final SharedLog mLog;
-    @NonNull
-    private final Dependencies mDeps;
-    @NonNull
-    private final Clock mClock;
-
-    @Nullable
-    private volatile ServerHandler mHandler;
-
-    // Accessed only on the handler thread
-    @Nullable
-    private DhcpPacketListener mPacketListener;
-    @Nullable
-    private FileDescriptor mSocket;
-    @NonNull
-    private DhcpServingParams mServingParams;
-
-    /**
-     * Clock to be used by DhcpServer to track time for lease expiration.
-     *
-     * <p>The clock should track time as may be measured by clients obtaining a lease. It does not
-     * need to be monotonous across restarts of the server as long as leases are cleared when the
-     * server is stopped.
-     */
-    public static class Clock {
-        /**
-         * @see SystemClock#elapsedRealtime()
-         */
-        public long elapsedRealtime() {
-            return SystemClock.elapsedRealtime();
-        }
-    }
-
-    /**
-     * Dependencies for the DhcpServer. Useful to be mocked in tests.
-     */
-    public interface Dependencies {
-        /**
-         * Send a packet to the specified datagram socket.
-         *
-         * @param fd File descriptor of the socket.
-         * @param buffer Data to be sent.
-         * @param dst Destination address of the packet.
-         */
-        void sendPacket(@NonNull FileDescriptor fd, @NonNull ByteBuffer buffer,
-                @NonNull InetAddress dst) throws ErrnoException, IOException;
-
-        /**
-         * Create a DhcpLeaseRepository for the server.
-         * @param servingParams Parameters used to serve DHCP requests.
-         * @param log Log to be used by the repository.
-         * @param clock Clock that the repository must use to track time.
-         */
-        DhcpLeaseRepository makeLeaseRepository(@NonNull DhcpServingParams servingParams,
-                @NonNull SharedLog log, @NonNull Clock clock);
-
-        /**
-         * Create a packet listener that will send packets to be processed.
-         */
-        DhcpPacketListener makePacketListener();
-
-        /**
-         * Create a clock that the server will use to track time.
-         */
-        Clock makeClock();
-
-        /**
-         * Add an entry to the ARP cache table.
-         * @param fd Datagram socket file descriptor that must use the new entry.
-         */
-        void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr,
-                @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException;
-
-        /**
-         * Verify that the caller is allowed to call public methods on DhcpServer.
-         * @throws SecurityException The caller is not allowed to call public methods on DhcpServer.
-         */
-        void checkCaller() throws SecurityException;
-    }
-
-    private class DependenciesImpl implements Dependencies {
-        @Override
-        public void sendPacket(@NonNull FileDescriptor fd, @NonNull ByteBuffer buffer,
-                @NonNull InetAddress dst) throws ErrnoException, IOException {
-            Os.sendto(fd, buffer, 0, dst, DhcpPacket.DHCP_CLIENT);
-        }
-
-        @Override
-        public DhcpLeaseRepository makeLeaseRepository(@NonNull DhcpServingParams servingParams,
-                @NonNull SharedLog log, @NonNull Clock clock) {
-            return new DhcpLeaseRepository(
-                    DhcpServingParams.makeIpPrefix(servingParams.serverAddr),
-                    servingParams.excludedAddrs,
-                    servingParams.dhcpLeaseTimeSecs * 1000, log.forSubComponent(REPO_TAG), clock);
-        }
-
-        @Override
-        public DhcpPacketListener makePacketListener() {
-            return new PacketListener();
-        }
-
-        @Override
-        public Clock makeClock() {
-            return new Clock();
-        }
-
-        @Override
-        public void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr,
-                @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException {
-            NetworkStackUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd);
-        }
-
-        @Override
-        public void checkCaller() {
-            checkNetworkStackCallingPermission();
-        }
-    }
-
-    private static class MalformedPacketException extends Exception {
-        MalformedPacketException(String message, Throwable t) {
-            super(message, t);
-        }
-    }
-
-    public DhcpServer(@NonNull String ifName,
-            @NonNull DhcpServingParams params, @NonNull SharedLog log) {
-        this(new HandlerThread(DhcpServer.class.getSimpleName() + "." + ifName),
-                ifName, params, log, null);
-    }
-
-    @VisibleForTesting
-    DhcpServer(@NonNull HandlerThread handlerThread, @NonNull String ifName,
-            @NonNull DhcpServingParams params, @NonNull SharedLog log,
-            @Nullable Dependencies deps) {
-        if (deps == null) {
-            deps = new DependenciesImpl();
-        }
-        mHandlerThread = handlerThread;
-        mIfName = ifName;
-        mServingParams = params;
-        mLog = log;
-        mDeps = deps;
-        mClock = deps.makeClock();
-        mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock);
-    }
-
-    /**
-     * Start listening for and responding to packets.
-     *
-     * <p>It is not legal to call this method more than once; in particular the server cannot be
-     * restarted after being stopped.
-     */
-    @Override
-    public void start(@Nullable INetworkStackStatusCallback cb) {
-        mDeps.checkCaller();
-        mHandlerThread.start();
-        mHandler = new ServerHandler(mHandlerThread.getLooper());
-        sendMessage(CMD_START_DHCP_SERVER, cb);
-    }
-
-    /**
-     * Update serving parameters. All subsequently received requests will be handled with the new
-     * parameters, and current leases that are incompatible with the new parameters are dropped.
-     */
-    @Override
-    public void updateParams(@Nullable DhcpServingParamsParcel params,
-            @Nullable INetworkStackStatusCallback cb) throws RemoteException {
-        mDeps.checkCaller();
-        final DhcpServingParams parsedParams;
-        try {
-            // throws InvalidParameterException with null params
-            parsedParams = DhcpServingParams.fromParcelableObject(params);
-        } catch (DhcpServingParams.InvalidParameterException e) {
-            mLog.e("Invalid parameters sent to DhcpServer", e);
-            if (cb != null) {
-                cb.onStatusAvailable(STATUS_INVALID_ARGUMENT);
-            }
-            return;
-        }
-        sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb));
-    }
-
-    /**
-     * Stop listening for packets.
-     *
-     * <p>As the server is stopped asynchronously, some packets may still be processed shortly after
-     * calling this method.
-     */
-    @Override
-    public void stop(@Nullable INetworkStackStatusCallback cb) {
-        mDeps.checkCaller();
-        sendMessage(CMD_STOP_DHCP_SERVER, cb);
-    }
-
-    private void sendMessage(int what, @Nullable Object obj) {
-        if (mHandler == null) {
-            mLog.e("Attempting to send a command to stopped DhcpServer: " + what);
-            return;
-        }
-        mHandler.sendMessage(mHandler.obtainMessage(what, obj));
-    }
-
-    private class ServerHandler extends Handler {
-        ServerHandler(@NonNull Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(@NonNull Message msg) {
-            final INetworkStackStatusCallback cb;
-            switch (msg.what) {
-                case CMD_UPDATE_PARAMS:
-                    final Pair<DhcpServingParams, INetworkStackStatusCallback> pair =
-                            (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj;
-                    final DhcpServingParams params = pair.first;
-                    mServingParams = params;
-                    mLeaseRepo.updateParams(
-                            DhcpServingParams.makeIpPrefix(mServingParams.serverAddr),
-                            params.excludedAddrs,
-                            params.dhcpLeaseTimeSecs);
-
-                    cb = pair.second;
-                    break;
-                case CMD_START_DHCP_SERVER:
-                    mPacketListener = mDeps.makePacketListener();
-                    mPacketListener.start();
-                    cb = (INetworkStackStatusCallback) msg.obj;
-                    break;
-                case CMD_STOP_DHCP_SERVER:
-                    if (mPacketListener != null) {
-                        mPacketListener.stop();
-                        mPacketListener = null;
-                    }
-                    mHandlerThread.quitSafely();
-                    cb = (INetworkStackStatusCallback) msg.obj;
-                    break;
-                default:
-                    return;
-            }
-            if (cb != null) {
-                try {
-                    cb.onStatusAvailable(STATUS_SUCCESS);
-                } catch (RemoteException e) {
-                    mLog.e("Could not send status back to caller", e);
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting
-    void processPacket(@NonNull DhcpPacket packet, int srcPort) {
-        final String packetType = packet.getClass().getSimpleName();
-        if (srcPort != DHCP_CLIENT) {
-            mLog.logf("Ignored packet of type %s sent from client port %d", packetType, srcPort);
-            return;
-        }
-
-        mLog.log("Received packet of type " + packetType);
-        final Inet4Address sid = packet.mServerIdentifier;
-        if (sid != null && !sid.equals(mServingParams.serverAddr.getAddress())) {
-            mLog.log("Packet ignored due to wrong server identifier: " + sid);
-            return;
-        }
-
-        try {
-            if (packet instanceof DhcpDiscoverPacket) {
-                processDiscover((DhcpDiscoverPacket) packet);
-            } else if (packet instanceof DhcpRequestPacket) {
-                processRequest((DhcpRequestPacket) packet);
-            } else if (packet instanceof DhcpReleasePacket) {
-                processRelease((DhcpReleasePacket) packet);
-            } else {
-                mLog.e("Unknown packet type: " + packet.getClass().getSimpleName());
-            }
-        } catch (MalformedPacketException e) {
-            // Not an internal error: only logging exception message, not stacktrace
-            mLog.e("Ignored malformed packet: " + e.getMessage());
-        }
-    }
-
-    private void logIgnoredPacketInvalidSubnet(DhcpLeaseRepository.InvalidSubnetException e) {
-        // Not an internal error: only logging exception message, not stacktrace
-        mLog.e("Ignored packet from invalid subnet: " + e.getMessage());
-    }
-
-    private void processDiscover(@NonNull DhcpDiscoverPacket packet)
-            throws MalformedPacketException {
-        final DhcpLease lease;
-        final MacAddress clientMac = getMacAddr(packet);
-        try {
-            lease = mLeaseRepo.getOffer(packet.getExplicitClientIdOrNull(), clientMac,
-                    packet.mRelayIp, packet.mRequestedIp, packet.mHostName);
-        } catch (DhcpLeaseRepository.OutOfAddressesException e) {
-            transmitNak(packet, "Out of addresses to offer");
-            return;
-        } catch (DhcpLeaseRepository.InvalidSubnetException e) {
-            logIgnoredPacketInvalidSubnet(e);
-            return;
-        }
-
-        transmitOffer(packet, lease, clientMac);
-    }
-
-    private void processRequest(@NonNull DhcpRequestPacket packet) throws MalformedPacketException {
-        // If set, packet SID matches with this server's ID as checked in processPacket().
-        final boolean sidSet = packet.mServerIdentifier != null;
-        final DhcpLease lease;
-        final MacAddress clientMac = getMacAddr(packet);
-        try {
-            lease = mLeaseRepo.requestLease(packet.getExplicitClientIdOrNull(), clientMac,
-                    packet.mClientIp, packet.mRelayIp, packet.mRequestedIp, sidSet,
-                    packet.mHostName);
-        } catch (DhcpLeaseRepository.InvalidAddressException e) {
-            transmitNak(packet, "Invalid requested address");
-            return;
-        } catch (DhcpLeaseRepository.InvalidSubnetException e) {
-            logIgnoredPacketInvalidSubnet(e);
-            return;
-        }
-
-        transmitAck(packet, lease, clientMac);
-    }
-
-    private void processRelease(@NonNull DhcpReleasePacket packet)
-            throws MalformedPacketException {
-        final byte[] clientId = packet.getExplicitClientIdOrNull();
-        final MacAddress macAddr = getMacAddr(packet);
-        // Don't care about success (there is no ACK/NAK); logging is already done in the repository
-        mLeaseRepo.releaseLease(clientId, macAddr, packet.mClientIp);
-    }
-
-    private Inet4Address getAckOrOfferDst(@NonNull DhcpPacket request, @NonNull DhcpLease lease,
-            boolean broadcastFlag) {
-        // Unless relayed or broadcast, send to client IP if already configured on the client, or to
-        // the lease address if the client has no configured address
-        if (!isEmpty(request.mRelayIp)) {
-            return request.mRelayIp;
-        } else if (broadcastFlag) {
-            return IPV4_ADDR_ALL;
-        } else if (!isEmpty(request.mClientIp)) {
-            return request.mClientIp;
-        } else {
-            return lease.getNetAddr();
-        }
-    }
-
-    /**
-     * Determine whether the broadcast flag should be set in the BOOTP packet flags. This does not
-     * apply to NAK responses, which should always have it set.
-     */
-    private static boolean getBroadcastFlag(@NonNull DhcpPacket request, @NonNull DhcpLease lease) {
-        // No broadcast flag if the client already has a configured IP to unicast to. RFC2131 #4.1
-        // has some contradictions regarding broadcast behavior if a client already has an IP
-        // configured and sends a request with both ciaddr (renew/rebind) and the broadcast flag
-        // set. Sending a unicast response to ciaddr matches previous behavior and is more
-        // efficient.
-        // If the client has no configured IP, broadcast if requested by the client or if the lease
-        // address cannot be used to send a unicast reply either.
-        return isEmpty(request.mClientIp) && (request.mBroadcast || isEmpty(lease.getNetAddr()));
-    }
-
-    /**
-     * Get the hostname from a lease if non-empty and requested in the incoming request.
-     * @param request The incoming request.
-     * @return The hostname, or null if not requested or empty.
-     */
-    @Nullable
-    private static String getHostnameIfRequested(@NonNull DhcpPacket request,
-            @NonNull DhcpLease lease) {
-        return request.hasRequestedParam(DHCP_HOST_NAME) && !TextUtils.isEmpty(lease.getHostname())
-                ? lease.getHostname()
-                : null;
-    }
-
-    private boolean transmitOffer(@NonNull DhcpPacket request, @NonNull DhcpLease lease,
-            @NonNull MacAddress clientMac) {
-        final boolean broadcastFlag = getBroadcastFlag(request, lease);
-        final int timeout = getLeaseTimeout(lease);
-        final Inet4Address prefixMask =
-                getPrefixMaskAsInet4Address(mServingParams.serverAddr.getPrefixLength());
-        final Inet4Address broadcastAddr = getBroadcastAddress(
-                mServingParams.getServerInet4Addr(), mServingParams.serverAddr.getPrefixLength());
-        final String hostname = getHostnameIfRequested(request, lease);
-        final ByteBuffer offerPacket = DhcpPacket.buildOfferPacket(
-                ENCAP_BOOTP, request.mTransId, broadcastFlag, mServingParams.getServerInet4Addr(),
-                request.mRelayIp, lease.getNetAddr(), request.mClientMac, timeout, prefixMask,
-                broadcastAddr, new ArrayList<>(mServingParams.defaultRouters),
-                new ArrayList<>(mServingParams.dnsServers),
-                mServingParams.getServerInet4Addr(), null /* domainName */, hostname,
-                mServingParams.metered, (short) mServingParams.linkMtu);
-
-        return transmitOfferOrAckPacket(offerPacket, request, lease, clientMac, broadcastFlag);
-    }
-
-    private boolean transmitAck(@NonNull DhcpPacket request, @NonNull DhcpLease lease,
-            @NonNull MacAddress clientMac) {
-        // TODO: replace DhcpPacket's build methods with real builders and use common code with
-        // transmitOffer above
-        final boolean broadcastFlag = getBroadcastFlag(request, lease);
-        final int timeout = getLeaseTimeout(lease);
-        final String hostname = getHostnameIfRequested(request, lease);
-        final ByteBuffer ackPacket = DhcpPacket.buildAckPacket(ENCAP_BOOTP, request.mTransId,
-                broadcastFlag, mServingParams.getServerInet4Addr(), request.mRelayIp,
-                lease.getNetAddr(), request.mClientIp, request.mClientMac, timeout,
-                mServingParams.getPrefixMaskAsAddress(), mServingParams.getBroadcastAddress(),
-                new ArrayList<>(mServingParams.defaultRouters),
-                new ArrayList<>(mServingParams.dnsServers),
-                mServingParams.getServerInet4Addr(), null /* domainName */, hostname,
-                mServingParams.metered, (short) mServingParams.linkMtu);
-
-        return transmitOfferOrAckPacket(ackPacket, request, lease, clientMac, broadcastFlag);
-    }
-
-    private boolean transmitNak(DhcpPacket request, String message) {
-        mLog.w("Transmitting NAK: " + message);
-        // Always set broadcast flag for NAK: client may not have a correct IP
-        final ByteBuffer nakPacket = DhcpPacket.buildNakPacket(
-                ENCAP_BOOTP, request.mTransId, mServingParams.getServerInet4Addr(),
-                request.mRelayIp, request.mClientMac, true /* broadcast */, message);
-
-        final Inet4Address dst = isEmpty(request.mRelayIp)
-                ? IPV4_ADDR_ALL
-                : request.mRelayIp;
-        return transmitPacket(nakPacket, DhcpNakPacket.class.getSimpleName(), dst);
-    }
-
-    private boolean transmitOfferOrAckPacket(@NonNull ByteBuffer buf, @NonNull DhcpPacket request,
-            @NonNull DhcpLease lease, @NonNull MacAddress clientMac, boolean broadcastFlag) {
-        mLog.logf("Transmitting %s with lease %s", request.getClass().getSimpleName(), lease);
-        // Client may not yet respond to ARP for the lease address, which may be the destination
-        // address. Add an entry to the ARP cache to save future ARP probes and make sure the
-        // packet reaches its destination.
-        if (!addArpEntry(clientMac, lease.getNetAddr())) {
-            // Logging for error already done
-            return false;
-        }
-        final Inet4Address dst = getAckOrOfferDst(request, lease, broadcastFlag);
-        return transmitPacket(buf, request.getClass().getSimpleName(), dst);
-    }
-
-    private boolean transmitPacket(@NonNull ByteBuffer buf, @NonNull String packetTypeTag,
-            @NonNull Inet4Address dst) {
-        try {
-            mDeps.sendPacket(mSocket, buf, dst);
-        } catch (ErrnoException | IOException e) {
-            mLog.e("Can't send packet " + packetTypeTag, e);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean addArpEntry(@NonNull MacAddress macAddr, @NonNull Inet4Address inetAddr) {
-        try {
-            mDeps.addArpEntry(inetAddr, macAddr, mIfName, mSocket);
-            return true;
-        } catch (IOException e) {
-            mLog.e("Error adding client to ARP table", e);
-            return false;
-        }
-    }
-
-    /**
-     * Get the remaining lease time in seconds, starting from {@link Clock#elapsedRealtime()}.
-     *
-     * <p>This is an unsigned 32-bit integer, so it cannot be read as a standard (signed) Java int.
-     * The return value is only intended to be used to populate the lease time field in a DHCP
-     * response, considering that lease time is an unsigned 32-bit integer field in DHCP packets.
-     *
-     * <p>Lease expiration times are tracked internally with millisecond precision: this method
-     * returns a rounded down value.
-     */
-    private int getLeaseTimeout(@NonNull DhcpLease lease) {
-        final long remainingTimeSecs = (lease.getExpTime() - mClock.elapsedRealtime()) / 1000;
-        if (remainingTimeSecs < 0) {
-            mLog.e("Processing expired lease " + lease);
-            return EXPIRED_FALLBACK_LEASE_TIME_SECS;
-        }
-
-        if (remainingTimeSecs >= toUnsignedLong(INFINITE_LEASE)) {
-            return INFINITE_LEASE;
-        }
-
-        return (int) remainingTimeSecs;
-    }
-
-    /**
-     * Get the client MAC address from a packet.
-     *
-     * @throws MalformedPacketException The address in the packet uses an unsupported format.
-     */
-    @NonNull
-    private MacAddress getMacAddr(@NonNull DhcpPacket packet) throws MalformedPacketException {
-        try {
-            return MacAddress.fromBytes(packet.getClientMac());
-        } catch (IllegalArgumentException e) {
-            final String message = "Invalid MAC address in packet: "
-                    + HexDump.dumpHexString(packet.getClientMac());
-            throw new MalformedPacketException(message, e);
-        }
-    }
-
-    private static boolean isEmpty(@Nullable Inet4Address address) {
-        return address == null || IPV4_ADDR_ANY.equals(address);
-    }
-
-    private class PacketListener extends DhcpPacketListener {
-        PacketListener() {
-            super(mHandler);
-        }
-
-        @Override
-        protected void onReceive(@NonNull DhcpPacket packet, @NonNull Inet4Address srcAddr,
-                int srcPort) {
-            processPacket(packet, srcPort);
-        }
-
-        @Override
-        protected void logError(@NonNull String msg, Exception e) {
-            mLog.e("Error receiving packet: " + msg, e);
-        }
-
-        @Override
-        protected void logParseError(@NonNull byte[] packet, int length,
-                @NonNull DhcpPacket.ParseException e) {
-            mLog.e("Error parsing packet", e);
-        }
-
-        @Override
-        protected FileDescriptor createFd() {
-            // TODO: have and use an API to set a socket tag without going through the thread tag
-            final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_DHCP_SERVER);
-            try {
-                mSocket = Os.socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
-                SocketUtils.bindSocketToInterface(mSocket, mIfName);
-                Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1);
-                Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1);
-                Os.bind(mSocket, IPV4_ADDR_ANY, DHCP_SERVER);
-
-                return mSocket;
-            } catch (IOException | ErrnoException e) {
-                mLog.e("Error creating UDP socket", e);
-                DhcpServer.this.stop(null);
-                return null;
-            } finally {
-                TrafficStats.setThreadStatsTag(oldTag);
-            }
-        }
-    }
-
-    @Override
-    public int getInterfaceVersion() {
-        return this.VERSION;
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
deleted file mode 100644
index 230b693..0000000
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * 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.dhcp;
-
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-
-import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
-import static com.android.server.util.NetworkStackConstants.IPV4_MAX_MTU;
-import static com.android.server.util.NetworkStackConstants.IPV4_MIN_MTU;
-
-import static java.lang.Integer.toUnsignedLong;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.shared.Inet4AddressUtils;
-import android.util.ArraySet;
-
-import java.net.Inet4Address;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Parameters used by the DhcpServer to serve requests.
- *
- * <p>Instances are immutable. Use {@link DhcpServingParams.Builder} to instantiate.
- * @hide
- */
-public class DhcpServingParams {
-    public static final int MTU_UNSET = 0;
-    public static final int MIN_PREFIX_LENGTH = 16;
-    public static final int MAX_PREFIX_LENGTH = 30;
-
-    /** Server inet address and prefix to serve */
-    @NonNull
-    public final LinkAddress serverAddr;
-
-    /**
-     * Default routers to be advertised to DHCP clients. May be empty.
-     * This set is provided by {@link DhcpServingParams.Builder} and is immutable.
-     */
-    @NonNull
-    public final Set<Inet4Address> defaultRouters;
-
-    /**
-     * DNS servers to be advertised to DHCP clients. May be empty.
-     * This set is provided by {@link DhcpServingParams.Builder} and is immutable.
-     */
-    @NonNull
-    public final Set<Inet4Address> dnsServers;
-
-    /**
-     * Excluded addresses that the DHCP server is not allowed to assign to clients.
-     * This set is provided by {@link DhcpServingParams.Builder} and is immutable.
-     */
-    @NonNull
-    public final Set<Inet4Address> excludedAddrs;
-
-    // DHCP uses uint32. Use long for clearer code, and check range when building.
-    public final long dhcpLeaseTimeSecs;
-    public final int linkMtu;
-
-    /**
-     * Indicates whether the DHCP server should send the ANDROID_METERED vendor-specific option.
-     */
-    public final boolean metered;
-
-    /**
-     * Checked exception thrown when some parameters used to build {@link DhcpServingParams} are
-     * missing or invalid.
-     */
-    public static class InvalidParameterException extends Exception {
-        public InvalidParameterException(String message) {
-            super(message);
-        }
-    }
-
-    private DhcpServingParams(@NonNull LinkAddress serverAddr,
-            @NonNull Set<Inet4Address> defaultRouters,
-            @NonNull Set<Inet4Address> dnsServers, @NonNull Set<Inet4Address> excludedAddrs,
-            long dhcpLeaseTimeSecs, int linkMtu, boolean metered) {
-        this.serverAddr = serverAddr;
-        this.defaultRouters = defaultRouters;
-        this.dnsServers = dnsServers;
-        this.excludedAddrs = excludedAddrs;
-        this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
-        this.linkMtu = linkMtu;
-        this.metered = metered;
-    }
-
-    /**
-     * Create parameters from a stable AIDL-compatible parcel.
-     * @throws InvalidParameterException The parameters parcelable is null or invalid.
-     */
-    public static DhcpServingParams fromParcelableObject(@Nullable DhcpServingParamsParcel parcel)
-            throws InvalidParameterException {
-        if (parcel == null) {
-            throw new InvalidParameterException("Null serving parameters");
-        }
-        final LinkAddress serverAddr = new LinkAddress(
-                intToInet4AddressHTH(parcel.serverAddr),
-                parcel.serverAddrPrefixLength);
-        return new Builder()
-                .setServerAddr(serverAddr)
-                .setDefaultRouters(toInet4AddressSet(parcel.defaultRouters))
-                .setDnsServers(toInet4AddressSet(parcel.dnsServers))
-                .setExcludedAddrs(toInet4AddressSet(parcel.excludedAddrs))
-                .setDhcpLeaseTimeSecs(parcel.dhcpLeaseTimeSecs)
-                .setLinkMtu(parcel.linkMtu)
-                .setMetered(parcel.metered)
-                .build();
-    }
-
-    private static Set<Inet4Address> toInet4AddressSet(@Nullable int[] addrs) {
-        if (addrs == null) {
-            return new HashSet<>(0);
-        }
-
-        final HashSet<Inet4Address> res = new HashSet<>();
-        for (int addr : addrs) {
-            res.add(intToInet4AddressHTH(addr));
-        }
-        return res;
-    }
-
-    @NonNull
-    public Inet4Address getServerInet4Addr() {
-        return (Inet4Address) serverAddr.getAddress();
-    }
-
-    /**
-     * Get the served prefix mask as an IPv4 address.
-     *
-     * <p>For example, if the served prefix is 192.168.42.0/24, this will return 255.255.255.0.
-     */
-    @NonNull
-    public Inet4Address getPrefixMaskAsAddress() {
-        return getPrefixMaskAsInet4Address(serverAddr.getPrefixLength());
-    }
-
-    /**
-     * Get the server broadcast address.
-     *
-     * <p>For example, if the server {@link LinkAddress} is 192.168.42.1/24, this will return
-     * 192.168.42.255.
-     */
-    @NonNull
-    public Inet4Address getBroadcastAddress() {
-        return Inet4AddressUtils.getBroadcastAddress(
-                getServerInet4Addr(), serverAddr.getPrefixLength());
-    }
-
-    /**
-     * Utility class to create new instances of {@link DhcpServingParams} while checking validity
-     * of the parameters.
-     */
-    public static class Builder {
-        private LinkAddress mServerAddr;
-        private Set<Inet4Address> mDefaultRouters;
-        private Set<Inet4Address> mDnsServers;
-        private Set<Inet4Address> mExcludedAddrs;
-        private long mDhcpLeaseTimeSecs;
-        private int mLinkMtu = MTU_UNSET;
-        private boolean mMetered;
-
-        /**
-         * Set the server address and served prefix for the DHCP server.
-         *
-         * <p>This parameter is required.
-         */
-        public Builder setServerAddr(@NonNull LinkAddress serverAddr) {
-            this.mServerAddr = serverAddr;
-            return this;
-        }
-
-        /**
-         * Set the default routers to be advertised to DHCP clients.
-         *
-         * <p>Each router must be inside the served prefix. This may be an empty set, but it must
-         * always be set explicitly before building the {@link DhcpServingParams}.
-         */
-        public Builder setDefaultRouters(@NonNull Set<Inet4Address> defaultRouters) {
-            this.mDefaultRouters = defaultRouters;
-            return this;
-        }
-
-        /**
-         * Set the default routers to be advertised to DHCP clients.
-         *
-         * <p>Each router must be inside the served prefix. This may be an empty list of routers,
-         * but it must always be set explicitly before building the {@link DhcpServingParams}.
-         */
-        public Builder setDefaultRouters(@NonNull Inet4Address... defaultRouters) {
-            return setDefaultRouters(makeArraySet(defaultRouters));
-        }
-
-        /**
-         * Convenience method to build the parameters with no default router.
-         *
-         * <p>Equivalent to calling {@link #setDefaultRouters(Inet4Address...)} with no address.
-         */
-        public Builder withNoDefaultRouter() {
-            return setDefaultRouters();
-        }
-
-        /**
-         * Set the DNS servers to be advertised to DHCP clients.
-         *
-         * <p>This may be an empty set, but it must always be set explicitly before building the
-         * {@link DhcpServingParams}.
-         */
-        public Builder setDnsServers(@NonNull Set<Inet4Address> dnsServers) {
-            this.mDnsServers = dnsServers;
-            return this;
-        }
-
-        /**
-         * Set the DNS servers to be advertised to DHCP clients.
-         *
-         * <p>This may be an empty list of servers, but it must always be set explicitly before
-         * building the {@link DhcpServingParams}.
-         */
-        public Builder setDnsServers(@NonNull Inet4Address... dnsServers) {
-            return setDnsServers(makeArraySet(dnsServers));
-        }
-
-        /**
-         * Convenience method to build the parameters with no DNS server.
-         *
-         * <p>Equivalent to calling {@link #setDnsServers(Inet4Address...)} with no address.
-         */
-        public Builder withNoDnsServer() {
-            return setDnsServers();
-        }
-
-        /**
-         * Set excluded addresses that the DHCP server is not allowed to assign to clients.
-         *
-         * <p>This parameter is optional. DNS servers and default routers are always excluded
-         * and do not need to be set here.
-         */
-        public Builder setExcludedAddrs(@NonNull Set<Inet4Address> excludedAddrs) {
-            this.mExcludedAddrs = excludedAddrs;
-            return this;
-        }
-
-        /**
-         * Set excluded addresses that the DHCP server is not allowed to assign to clients.
-         *
-         * <p>This parameter is optional. DNS servers and default routers are always excluded
-         * and do not need to be set here.
-         */
-        public Builder setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) {
-            return setExcludedAddrs(makeArraySet(excludedAddrs));
-        }
-
-        /**
-         * Set the lease time for leases assigned by the DHCP server.
-         *
-         * <p>This parameter is required.
-         */
-        public Builder setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) {
-            this.mDhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
-            return this;
-        }
-
-        /**
-         * Set the link MTU to be advertised to DHCP clients.
-         *
-         * <p>If set to {@link #MTU_UNSET}, no MTU will be advertised to clients. This parameter
-         * is optional and defaults to {@link #MTU_UNSET}.
-         */
-        public Builder setLinkMtu(int linkMtu) {
-            this.mLinkMtu = linkMtu;
-            return this;
-        }
-
-        /**
-         * Set whether the DHCP server should send the ANDROID_METERED vendor-specific option.
-         *
-         * <p>If not set, the default value is false.
-         */
-        public Builder setMetered(boolean metered) {
-            this.mMetered = metered;
-            return this;
-        }
-
-        /**
-         * Create a new {@link DhcpServingParams} instance based on parameters set in the builder.
-         *
-         * <p>This method has no side-effects. If it does not throw, a valid
-         * {@link DhcpServingParams} is returned.
-         * @return The constructed parameters.
-         * @throws InvalidParameterException At least one parameter is missing or invalid.
-         */
-        @NonNull
-        public DhcpServingParams build() throws InvalidParameterException {
-            if (mServerAddr == null) {
-                throw new InvalidParameterException("Missing serverAddr");
-            }
-            if (mDefaultRouters == null) {
-                throw new InvalidParameterException("Missing defaultRouters");
-            }
-            if (mDnsServers == null) {
-                // Empty set is OK, but enforce explicitly setting it
-                throw new InvalidParameterException("Missing dnsServers");
-            }
-            if (mDhcpLeaseTimeSecs <= 0 || mDhcpLeaseTimeSecs > toUnsignedLong(INFINITE_LEASE)) {
-                throw new InvalidParameterException("Invalid lease time: " + mDhcpLeaseTimeSecs);
-            }
-            if (mLinkMtu != MTU_UNSET && (mLinkMtu < IPV4_MIN_MTU || mLinkMtu > IPV4_MAX_MTU)) {
-                throw new InvalidParameterException("Invalid link MTU: " + mLinkMtu);
-            }
-            if (!mServerAddr.isIpv4()) {
-                throw new InvalidParameterException("serverAddr must be IPv4");
-            }
-            if (mServerAddr.getPrefixLength() < MIN_PREFIX_LENGTH
-                    || mServerAddr.getPrefixLength() > MAX_PREFIX_LENGTH) {
-                throw new InvalidParameterException("Prefix length is not in supported range");
-            }
-
-            final IpPrefix prefix = makeIpPrefix(mServerAddr);
-            for (Inet4Address addr : mDefaultRouters) {
-                if (!prefix.contains(addr)) {
-                    throw new InvalidParameterException(String.format(
-                            "Default router %s is not in server prefix %s", addr, mServerAddr));
-                }
-            }
-
-            final Set<Inet4Address> excl = new HashSet<>();
-            if (mExcludedAddrs != null) {
-                excl.addAll(mExcludedAddrs);
-            }
-            excl.add((Inet4Address) mServerAddr.getAddress());
-            excl.addAll(mDefaultRouters);
-            excl.addAll(mDnsServers);
-
-            return new DhcpServingParams(mServerAddr,
-                    Collections.unmodifiableSet(new HashSet<>(mDefaultRouters)),
-                    Collections.unmodifiableSet(new HashSet<>(mDnsServers)),
-                    Collections.unmodifiableSet(excl),
-                    mDhcpLeaseTimeSecs, mLinkMtu, mMetered);
-        }
-    }
-
-    /**
-     * Utility method to create an IpPrefix with the address and prefix length of a LinkAddress.
-     */
-    @NonNull
-    static IpPrefix makeIpPrefix(@NonNull LinkAddress addr) {
-        return new IpPrefix(addr.getAddress(), addr.getPrefixLength());
-    }
-
-    private static <T> ArraySet<T> makeArraySet(T[] elements) {
-        final ArraySet<T> set = new ArraySet<>(elements.length);
-        set.addAll(Arrays.asList(elements));
-        return set;
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
deleted file mode 100644
index eb49218..0000000
--- a/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ip;
-
-import static android.net.util.SocketUtils.makePacketSocketAddress;
-import static android.system.OsConstants.AF_PACKET;
-import static android.system.OsConstants.ARPHRD_ETHER;
-import static android.system.OsConstants.ETH_P_ALL;
-import static android.system.OsConstants.SOCK_NONBLOCK;
-import static android.system.OsConstants.SOCK_RAW;
-
-import android.net.util.ConnectivityPacketSummary;
-import android.net.util.InterfaceParams;
-import android.net.util.NetworkStackUtils;
-import android.net.util.PacketReader;
-import android.os.Handler;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.Log;
-
-import com.android.internal.util.HexDump;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-
-/**
- * Critical connectivity packet tracking daemon.
- *
- * Tracks ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
- *
- * This class's constructor, start() and stop() methods must only be called
- * from the same thread on which the passed in |log| is accessed.
- *
- * Log lines include a hexdump of the packet, which can be decoded via:
- *
- *     echo -n H3XSTR1NG | sed -e 's/\([0-9A-F][0-9A-F]\)/\1 /g' -e 's/^/000000 /'
- *                       | text2pcap - -
- *                       | tcpdump -n -vv -e -r -
- *
- * @hide
- */
-public class ConnectivityPacketTracker {
-    private static final String TAG = ConnectivityPacketTracker.class.getSimpleName();
-    private static final boolean DBG = false;
-    private static final String MARK_START = "--- START ---";
-    private static final String MARK_STOP = "--- STOP ---";
-    private static final String MARK_NAMED_START = "--- START (%s) ---";
-    private static final String MARK_NAMED_STOP = "--- STOP (%s) ---";
-
-    private final String mTag;
-    private final LocalLog mLog;
-    private final PacketReader mPacketListener;
-    private boolean mRunning;
-    private String mDisplayName;
-
-    public ConnectivityPacketTracker(Handler h, InterfaceParams ifParams, LocalLog log) {
-        if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
-
-        mTag = TAG + "." + ifParams.name;
-        mLog = log;
-        mPacketListener = new PacketListener(h, ifParams);
-    }
-
-    public void start(String displayName) {
-        mRunning = true;
-        mDisplayName = displayName;
-        mPacketListener.start();
-    }
-
-    public void stop() {
-        mPacketListener.stop();
-        mRunning = false;
-        mDisplayName = null;
-    }
-
-    private final class PacketListener extends PacketReader {
-        private final InterfaceParams mInterface;
-
-        PacketListener(Handler h, InterfaceParams ifParams) {
-            super(h, ifParams.defaultMtu);
-            mInterface = ifParams;
-        }
-
-        @Override
-        protected FileDescriptor createFd() {
-            FileDescriptor s = null;
-            try {
-                s = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0);
-                NetworkStackUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
-                Os.bind(s, makePacketSocketAddress((short) ETH_P_ALL, mInterface.index));
-            } catch (ErrnoException | IOException e) {
-                logError("Failed to create packet tracking socket: ", e);
-                closeFd(s);
-                return null;
-            }
-            return s;
-        }
-
-        @Override
-        protected void handlePacket(byte[] recvbuf, int length) {
-            final String summary = ConnectivityPacketSummary.summarize(
-                    mInterface.macAddr, recvbuf, length);
-            if (summary == null) return;
-
-            if (DBG) Log.d(mTag, summary);
-            addLogEntry(summary + "\n[" + HexDump.toHexString(recvbuf, 0, length) + "]");
-        }
-
-        @Override
-        protected void onStart() {
-            final String msg = TextUtils.isEmpty(mDisplayName)
-                    ? MARK_START
-                    : String.format(MARK_NAMED_START, mDisplayName);
-            mLog.log(msg);
-        }
-
-        @Override
-        protected void onStop() {
-            String msg = TextUtils.isEmpty(mDisplayName)
-                    ? MARK_STOP
-                    : String.format(MARK_NAMED_STOP, mDisplayName);
-            if (!mRunning) msg += " (packet listener stopped unexpectedly)";
-            mLog.log(msg);
-        }
-
-        @Override
-        protected void logError(String msg, Exception e) {
-            Log.e(mTag, msg, e);
-            addLogEntry(msg + e);
-        }
-
-        private void addLogEntry(String entry) {
-            mLog.log(entry);
-        }
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
deleted file mode 100644
index 266b1b0..0000000
--- a/packages/NetworkStack/src/android/net/ip/IpClient.java
+++ /dev/null
@@ -1,1784 +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.net.ip;
-
-import static android.net.RouteInfo.RTN_UNICAST;
-import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
-
-import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.DhcpResults;
-import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NattKeepalivePacketDataParcelable;
-import android.net.NetworkStackIpMemoryStore;
-import android.net.ProvisioningConfigurationParcelable;
-import android.net.ProxyInfo;
-import android.net.RouteInfo;
-import android.net.TcpKeepalivePacketDataParcelable;
-import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.dhcp.DhcpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpManagerEvent;
-import android.net.shared.InitialConfiguration;
-import android.net.shared.ProvisioningConfiguration;
-import android.net.util.InterfaceParams;
-import android.net.util.SharedLog;
-import android.os.ConditionVariable;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IState;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.internal.util.WakeupMessage;
-import com.android.server.NetworkObserverRegistry;
-import com.android.server.NetworkStackService.NetworkStackServiceManager;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.net.InetAddress;
-import java.util.Collection;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-
-/**
- * IpClient
- *
- * This class provides the interface to IP-layer provisioning and maintenance
- * functionality that can be used by transport layers like Wi-Fi, Ethernet,
- * et cetera.
- *
- * [ Lifetime ]
- * IpClient is designed to be instantiated as soon as the interface name is
- * known and can be as long-lived as the class containing it (i.e. declaring
- * it "private final" is okay).
- *
- * @hide
- */
-public class IpClient extends StateMachine {
-    private static final boolean DBG = false;
-
-    // For message logging.
-    private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
-    private static final SparseArray<String> sWhatToString =
-            MessageUtils.findMessageNames(sMessageClasses);
-    // Two static concurrent hashmaps of interface name to logging classes.
-    // One holds StateMachine logs and the other connectivity packet logs.
-    private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>();
-    private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
-    private final NetworkStackIpMemoryStore mIpMemoryStore;
-
-    /**
-     * Dump all state machine and connectivity packet logs to the specified writer.
-     * @param skippedIfaces Interfaces for which logs should not be dumped.
-     */
-    public static void dumpAllLogs(PrintWriter writer, Set<String> skippedIfaces) {
-        for (String ifname : sSmLogs.keySet()) {
-            if (skippedIfaces.contains(ifname)) continue;
-
-            writer.println(String.format("--- BEGIN %s ---", ifname));
-
-            final SharedLog smLog = sSmLogs.get(ifname);
-            if (smLog != null) {
-                writer.println("State machine log:");
-                smLog.dump(null, writer, null);
-            }
-
-            writer.println("");
-
-            final LocalLog pktLog = sPktLogs.get(ifname);
-            if (pktLog != null) {
-                writer.println("Connectivity packet log:");
-                pktLog.readOnlyLocalLog().dump(null, writer, null);
-            }
-
-            writer.println(String.format("--- END %s ---", ifname));
-        }
-    }
-
-    // Use a wrapper class to log in order to ensure complete and detailed
-    // logging. This method is lighter weight than annotations/reflection
-    // and has the following benefits:
-    //
-    //     - No invoked method can be forgotten.
-    //       Any new method added to IpClient.Callback must be overridden
-    //       here or it will never be called.
-    //
-    //     - No invoking call site can be forgotten.
-    //       Centralized logging in this way means call sites don't need to
-    //       remember to log, and therefore no call site can be forgotten.
-    //
-    //     - No variation in log format among call sites.
-    //       Encourages logging of any available arguments, and all call sites
-    //       are necessarily logged identically.
-    //
-    // NOTE: Log first because passed objects may or may not be thread-safe and
-    // once passed on to the callback they may be modified by another thread.
-    //
-    // TODO: Find an lighter weight approach.
-    public static class IpClientCallbacksWrapper {
-        private static final String PREFIX = "INVOKE ";
-        private final IIpClientCallbacks mCallback;
-        private final SharedLog mLog;
-
-        @VisibleForTesting
-        protected IpClientCallbacksWrapper(IIpClientCallbacks callback, SharedLog log) {
-            mCallback = callback;
-            mLog = log;
-        }
-
-        private void log(String msg) {
-            mLog.log(PREFIX + msg);
-        }
-
-        private void log(String msg, Throwable e) {
-            mLog.e(PREFIX + msg, e);
-        }
-
-        public void onPreDhcpAction() {
-            log("onPreDhcpAction()");
-            try {
-                mCallback.onPreDhcpAction();
-            } catch (RemoteException e) {
-                log("Failed to call onPreDhcpAction", e);
-            }
-        }
-
-        public void onPostDhcpAction() {
-            log("onPostDhcpAction()");
-            try {
-                mCallback.onPostDhcpAction();
-            } catch (RemoteException e) {
-                log("Failed to call onPostDhcpAction", e);
-            }
-        }
-
-        public void onNewDhcpResults(DhcpResults dhcpResults) {
-            log("onNewDhcpResults({" + dhcpResults + "})");
-            try {
-                mCallback.onNewDhcpResults(toStableParcelable(dhcpResults));
-            } catch (RemoteException e) {
-                log("Failed to call onNewDhcpResults", e);
-            }
-        }
-
-        public void onProvisioningSuccess(LinkProperties newLp) {
-            log("onProvisioningSuccess({" + newLp + "})");
-            try {
-                mCallback.onProvisioningSuccess(newLp);
-            } catch (RemoteException e) {
-                log("Failed to call onProvisioningSuccess", e);
-            }
-        }
-
-        public void onProvisioningFailure(LinkProperties newLp) {
-            log("onProvisioningFailure({" + newLp + "})");
-            try {
-                mCallback.onProvisioningFailure(newLp);
-            } catch (RemoteException e) {
-                log("Failed to call onProvisioningFailure", e);
-            }
-        }
-
-        public void onLinkPropertiesChange(LinkProperties newLp) {
-            log("onLinkPropertiesChange({" + newLp + "})");
-            try {
-                mCallback.onLinkPropertiesChange(newLp);
-            } catch (RemoteException e) {
-                log("Failed to call onLinkPropertiesChange", e);
-            }
-        }
-
-        public void onReachabilityLost(String logMsg) {
-            log("onReachabilityLost(" + logMsg + ")");
-            try {
-                mCallback.onReachabilityLost(logMsg);
-            } catch (RemoteException e) {
-                log("Failed to call onReachabilityLost", e);
-            }
-        }
-
-        public void onQuit() {
-            log("onQuit()");
-            try {
-                mCallback.onQuit();
-            } catch (RemoteException e) {
-                log("Failed to call onQuit", e);
-            }
-        }
-
-        public void installPacketFilter(byte[] filter) {
-            log("installPacketFilter(byte[" + filter.length + "])");
-            try {
-                mCallback.installPacketFilter(filter);
-            } catch (RemoteException e) {
-                log("Failed to call installPacketFilter", e);
-            }
-        }
-
-        public void startReadPacketFilter() {
-            log("startReadPacketFilter()");
-            try {
-                mCallback.startReadPacketFilter();
-            } catch (RemoteException e) {
-                log("Failed to call startReadPacketFilter", e);
-            }
-        }
-
-        public void setFallbackMulticastFilter(boolean enabled) {
-            log("setFallbackMulticastFilter(" + enabled + ")");
-            try {
-                mCallback.setFallbackMulticastFilter(enabled);
-            } catch (RemoteException e) {
-                log("Failed to call setFallbackMulticastFilter", e);
-            }
-        }
-
-        public void setNeighborDiscoveryOffload(boolean enable) {
-            log("setNeighborDiscoveryOffload(" + enable + ")");
-            try {
-                mCallback.setNeighborDiscoveryOffload(enable);
-            } catch (RemoteException e) {
-                log("Failed to call setNeighborDiscoveryOffload", e);
-            }
-        }
-    }
-
-    public static final String DUMP_ARG_CONFIRM = "confirm";
-
-    // Below constants are picked up by MessageUtils and exempt from ProGuard optimization.
-    private static final int CMD_TERMINATE_AFTER_STOP             = 1;
-    private static final int CMD_STOP                             = 2;
-    private static final int CMD_START                            = 3;
-    private static final int CMD_CONFIRM                          = 4;
-    private static final int EVENT_PRE_DHCP_ACTION_COMPLETE       = 5;
-    // Triggered by NetlinkTracker to communicate netlink events.
-    private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
-    private static final int CMD_UPDATE_TCP_BUFFER_SIZES          = 7;
-    private static final int CMD_UPDATE_HTTP_PROXY                = 8;
-    private static final int CMD_SET_MULTICAST_FILTER             = 9;
-    private static final int EVENT_PROVISIONING_TIMEOUT           = 10;
-    private static final int EVENT_DHCPACTION_TIMEOUT             = 11;
-    private static final int EVENT_READ_PACKET_FILTER_COMPLETE    = 12;
-    private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13;
-    private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14;
-    private static final int CMD_UPDATE_L2KEY_GROUPHINT = 15;
-
-    // Internal commands to use instead of trying to call transitionTo() inside
-    // a given State's enter() method. Calling transitionTo() from enter/exit
-    // encounters a Log.wtf() that can cause trouble on eng builds.
-    private static final int CMD_JUMP_STARTED_TO_RUNNING          = 100;
-    private static final int CMD_JUMP_RUNNING_TO_STOPPING         = 101;
-    private static final int CMD_JUMP_STOPPING_TO_STOPPED         = 102;
-
-    // IpClient shares a handler with DhcpClient: commands must not overlap
-    public static final int DHCPCLIENT_CMD_BASE = 1000;
-
-    private static final int MAX_LOG_RECORDS = 500;
-    private static final int MAX_PACKET_RECORDS = 100;
-
-    private static final boolean NO_CALLBACKS = false;
-    private static final boolean SEND_CALLBACKS = true;
-
-    // This must match the interface prefix in clatd.c.
-    // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
-    private static final String CLAT_PREFIX = "v4-";
-
-    private static final int IMMEDIATE_FAILURE_DURATION = 0;
-
-    private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1;
-    private static final int PROV_CHANGE_LOST_PROVISIONING = 2;
-    private static final int PROV_CHANGE_GAINED_PROVISIONING = 3;
-    private static final int PROV_CHANGE_STILL_PROVISIONED = 4;
-
-    private final State mStoppedState = new StoppedState();
-    private final State mStoppingState = new StoppingState();
-    private final State mStartedState = new StartedState();
-    private final State mRunningState = new RunningState();
-
-    private final String mTag;
-    private final Context mContext;
-    private final String mInterfaceName;
-    private final String mClatInterfaceName;
-    @VisibleForTesting
-    protected final IpClientCallbacksWrapper mCallback;
-    private final Dependencies mDependencies;
-    private final CountDownLatch mShutdownLatch;
-    private final ConnectivityManager mCm;
-    private final INetd mNetd;
-    private final NetworkObserverRegistry mObserverRegistry;
-    private final IpClientLinkObserver mLinkObserver;
-    private final WakeupMessage mProvisioningTimeoutAlarm;
-    private final WakeupMessage mDhcpActionTimeoutAlarm;
-    private final SharedLog mLog;
-    private final LocalLog mConnectivityPacketLog;
-    private final MessageHandlingLogger mMsgStateLogger;
-    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-    private final InterfaceController mInterfaceCtrl;
-
-    private InterfaceParams mInterfaceParams;
-
-    /**
-     * Non-final member variables accessed only from within our StateMachine.
-     */
-    private LinkProperties mLinkProperties;
-    private android.net.shared.ProvisioningConfiguration mConfiguration;
-    private IpReachabilityMonitor mIpReachabilityMonitor;
-    private DhcpClient mDhcpClient;
-    private DhcpResults mDhcpResults;
-    private String mTcpBufferSizes;
-    private ProxyInfo mHttpProxy;
-    private ApfFilter mApfFilter;
-    private String mL2Key; // The L2 key for this network, for writing into the memory store
-    private String mGroupHint; // The group hint for this network, for writing into the memory store
-    private boolean mMulticastFiltering;
-    private long mStartTimeMillis;
-
-    /**
-     * Reading the snapshot is an asynchronous operation initiated by invoking
-     * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
-     * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable
-     * signals when a new snapshot is ready.
-     */
-    private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable();
-
-    public static class Dependencies {
-        /**
-         * Get interface parameters for the specified interface.
-         */
-        public InterfaceParams getInterfaceParams(String ifname) {
-            return InterfaceParams.getByName(ifname);
-        }
-
-        /**
-         * Get a INetd connector.
-         */
-        public INetd getNetd(Context context) {
-            return INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE));
-        }
-    }
-
-    public IpClient(Context context, String ifName, IIpClientCallbacks callback,
-            NetworkObserverRegistry observerRegistry, NetworkStackServiceManager nssManager) {
-        this(context, ifName, callback, observerRegistry, nssManager, new Dependencies());
-    }
-
-    @VisibleForTesting
-    IpClient(Context context, String ifName, IIpClientCallbacks callback,
-            NetworkObserverRegistry observerRegistry, NetworkStackServiceManager nssManager,
-            Dependencies deps) {
-        super(IpClient.class.getSimpleName() + "." + ifName);
-        Preconditions.checkNotNull(ifName);
-        Preconditions.checkNotNull(callback);
-
-        mTag = getName();
-
-        mContext = context;
-        mInterfaceName = ifName;
-        mClatInterfaceName = CLAT_PREFIX + ifName;
-        mDependencies = deps;
-        mShutdownLatch = new CountDownLatch(1);
-        mCm = mContext.getSystemService(ConnectivityManager.class);
-        mObserverRegistry = observerRegistry;
-        mIpMemoryStore =
-                new NetworkStackIpMemoryStore(context, nssManager.getIpMemoryStoreService());
-
-        sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
-        mLog = sSmLogs.get(mInterfaceName);
-        sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS));
-        mConnectivityPacketLog = sPktLogs.get(mInterfaceName);
-        mMsgStateLogger = new MessageHandlingLogger();
-        mCallback = new IpClientCallbacksWrapper(callback, mLog);
-
-        // TODO: Consider creating, constructing, and passing in some kind of
-        // InterfaceController.Dependencies class.
-        mNetd = deps.getNetd(mContext);
-        mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog);
-
-        mLinkObserver = new IpClientLinkObserver(
-                mInterfaceName,
-                () -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED)) {
-            @Override
-            public void onInterfaceAdded(String iface) {
-                super.onInterfaceAdded(iface);
-                if (mClatInterfaceName.equals(iface)) {
-                    mCallback.setNeighborDiscoveryOffload(false);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceAdded(" + iface + ")";
-                logMsg(msg);
-            }
-
-            @Override
-            public void onInterfaceRemoved(String iface) {
-                super.onInterfaceRemoved(iface);
-                // TODO: Also observe mInterfaceName going down and take some
-                // kind of appropriate action.
-                if (mClatInterfaceName.equals(iface)) {
-                    // TODO: consider sending a message to the IpClient main
-                    // StateMachine thread, in case "NDO enabled" state becomes
-                    // tied to more things that 464xlat operation.
-                    mCallback.setNeighborDiscoveryOffload(true);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceRemoved(" + iface + ")";
-                logMsg(msg);
-            }
-
-            private void logMsg(String msg) {
-                Log.d(mTag, msg);
-                getHandler().post(() -> mLog.log("OBSERVED " + msg));
-            }
-        };
-
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-
-        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
-                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
-        mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
-                mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
-
-        // Anything the StateMachine may access must have been instantiated
-        // before this point.
-        configureAndStartStateMachine();
-
-        // Anything that may send messages to the StateMachine must only be
-        // configured to do so after the StateMachine has started (above).
-        startStateMachineUpdaters();
-    }
-
-    /**
-     * Make a IIpClient connector to communicate with this IpClient.
-     */
-    public IIpClient makeConnector() {
-        return new IpClientConnector();
-    }
-
-    class IpClientConnector extends IIpClient.Stub {
-        @Override
-        public void completedPreDhcpAction() {
-            checkNetworkStackCallingPermission();
-            IpClient.this.completedPreDhcpAction();
-        }
-        @Override
-        public void confirmConfiguration() {
-            checkNetworkStackCallingPermission();
-            IpClient.this.confirmConfiguration();
-        }
-        @Override
-        public void readPacketFilterComplete(byte[] data) {
-            checkNetworkStackCallingPermission();
-            IpClient.this.readPacketFilterComplete(data);
-        }
-        @Override
-        public void shutdown() {
-            checkNetworkStackCallingPermission();
-            IpClient.this.shutdown();
-        }
-        @Override
-        public void startProvisioning(ProvisioningConfigurationParcelable req) {
-            checkNetworkStackCallingPermission();
-            IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req));
-        }
-        @Override
-        public void stop() {
-            checkNetworkStackCallingPermission();
-            IpClient.this.stop();
-        }
-        @Override
-        public void setL2KeyAndGroupHint(String l2Key, String groupHint) {
-            checkNetworkStackCallingPermission();
-            IpClient.this.setL2KeyAndGroupHint(l2Key, groupHint);
-        }
-        @Override
-        public void setTcpBufferSizes(String tcpBufferSizes) {
-            checkNetworkStackCallingPermission();
-            IpClient.this.setTcpBufferSizes(tcpBufferSizes);
-        }
-        @Override
-        public void setHttpProxy(ProxyInfo proxyInfo) {
-            checkNetworkStackCallingPermission();
-            IpClient.this.setHttpProxy(proxyInfo);
-        }
-        @Override
-        public void setMulticastFilter(boolean enabled) {
-            checkNetworkStackCallingPermission();
-            IpClient.this.setMulticastFilter(enabled);
-        }
-        @Override
-        public void addKeepalivePacketFilter(int slot, TcpKeepalivePacketDataParcelable pkt) {
-            checkNetworkStackCallingPermission();
-            IpClient.this.addKeepalivePacketFilter(slot, pkt);
-        }
-        @Override
-        public void addNattKeepalivePacketFilter(int slot, NattKeepalivePacketDataParcelable pkt) {
-            checkNetworkStackCallingPermission();
-            IpClient.this.addNattKeepalivePacketFilter(slot, pkt);
-        }
-        @Override
-        public void removeKeepalivePacketFilter(int slot) {
-            checkNetworkStackCallingPermission();
-            IpClient.this.removeKeepalivePacketFilter(slot);
-        }
-
-        @Override
-        public int getInterfaceVersion() {
-            return this.VERSION;
-        }
-    }
-
-    public String getInterfaceName() {
-        return mInterfaceName;
-    }
-
-    private void configureAndStartStateMachine() {
-        // CHECKSTYLE:OFF IndentationCheck
-        addState(mStoppedState);
-        addState(mStartedState);
-            addState(mRunningState, mStartedState);
-        addState(mStoppingState);
-        // CHECKSTYLE:ON IndentationCheck
-
-        setInitialState(mStoppedState);
-
-        super.start();
-    }
-
-    private void startStateMachineUpdaters() {
-        mObserverRegistry.registerObserverForNonblockingCallback(mLinkObserver);
-    }
-
-    private void stopStateMachineUpdaters() {
-        mObserverRegistry.unregisterObserver(mLinkObserver);
-    }
-
-    @Override
-    protected void onQuitting() {
-        mCallback.onQuit();
-        mShutdownLatch.countDown();
-    }
-
-    /**
-     * Shut down this IpClient instance altogether.
-     */
-    public void shutdown() {
-        stop();
-        sendMessage(CMD_TERMINATE_AFTER_STOP);
-    }
-
-    /**
-     * Start provisioning with the provided parameters.
-     */
-    public void startProvisioning(ProvisioningConfiguration req) {
-        if (!req.isValid()) {
-            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-            return;
-        }
-
-        mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName);
-        if (mInterfaceParams == null) {
-            logError("Failed to find InterfaceParams for " + mInterfaceName);
-            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND);
-            return;
-        }
-
-        mCallback.setNeighborDiscoveryOffload(true);
-        sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
-    }
-
-    /**
-     * Stop this IpClient.
-     *
-     * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
-     */
-    public void stop() {
-        sendMessage(CMD_STOP);
-    }
-
-    /**
-     * Confirm the provisioning configuration.
-     */
-    public void confirmConfiguration() {
-        sendMessage(CMD_CONFIRM);
-    }
-
-    /**
-     * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
-     * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
-     * proceed.
-     */
-    public void completedPreDhcpAction() {
-        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
-    }
-
-    /**
-     * Indicate that packet filter read is complete.
-     */
-    public void readPacketFilterComplete(byte[] data) {
-        sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data);
-    }
-
-    /**
-     * Set the TCP buffer sizes to use.
-     *
-     * This may be called, repeatedly, at any time before or after a call to
-     * #startProvisioning(). The setting is cleared upon calling #stop().
-     */
-    public void setTcpBufferSizes(String tcpBufferSizes) {
-        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
-    }
-
-    /**
-     * Set the L2 key and group hint for storing info into the memory store.
-     */
-    public void setL2KeyAndGroupHint(String l2Key, String groupHint) {
-        sendMessage(CMD_UPDATE_L2KEY_GROUPHINT, new Pair<>(l2Key, groupHint));
-    }
-
-    /**
-     * Set the HTTP Proxy configuration to use.
-     *
-     * This may be called, repeatedly, at any time before or after a call to
-     * #startProvisioning(). The setting is cleared upon calling #stop().
-     */
-    public void setHttpProxy(ProxyInfo proxyInfo) {
-        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
-    }
-
-    /**
-     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
-     * if not, Callback.setFallbackMulticastFilter() is called.
-     */
-    public void setMulticastFilter(boolean enabled) {
-        sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
-    }
-
-    /**
-     * Called by WifiStateMachine to add TCP keepalive packet filter before setting up
-     * keepalive offload.
-     */
-    public void addKeepalivePacketFilter(int slot, @NonNull TcpKeepalivePacketDataParcelable pkt) {
-        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */, pkt);
-    }
-
-    /**
-     *  Called by WifiStateMachine to add NATT keepalive packet filter before setting up
-     *  keepalive offload.
-     */
-    public void addNattKeepalivePacketFilter(int slot,
-            @NonNull NattKeepalivePacketDataParcelable pkt) {
-        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */ , pkt);
-    }
-
-    /**
-     * Called by WifiStateMachine to remove keepalive packet filter after stopping keepalive
-     * offload.
-     */
-    public void removeKeepalivePacketFilter(int slot) {
-        sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF, slot, 0 /* Unused */);
-    }
-
-    /**
-     * Dump logs of this IpClient.
-     */
-    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
-            // Execute confirmConfiguration() and take no further action.
-            confirmConfiguration();
-            return;
-        }
-
-        // Thread-unsafe access to mApfFilter but just used for debugging.
-        final ApfFilter apfFilter = mApfFilter;
-        final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration;
-        final ApfCapabilities apfCapabilities = (provisioningConfig != null)
-                ? provisioningConfig.mApfCapabilities : null;
-
-        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        pw.println(mTag + " APF dump:");
-        pw.increaseIndent();
-        if (apfFilter != null) {
-            if (apfCapabilities.hasDataAccess()) {
-                // Request a new snapshot, then wait for it.
-                mApfDataSnapshotComplete.close();
-                mCallback.startReadPacketFilter();
-                if (!mApfDataSnapshotComplete.block(1000)) {
-                    pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT");
-                }
-            }
-            apfFilter.dump(pw);
-
-        } else {
-            pw.print("No active ApfFilter; ");
-            if (provisioningConfig == null) {
-                pw.println("IpClient not yet started.");
-            } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
-                pw.println("Hardware does not support APF.");
-            } else {
-                pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
-            }
-        }
-        pw.decreaseIndent();
-        pw.println();
-        pw.println(mTag + " current ProvisioningConfiguration:");
-        pw.increaseIndent();
-        pw.println(Objects.toString(provisioningConfig, "N/A"));
-        pw.decreaseIndent();
-
-        final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
-        if (iprm != null) {
-            pw.println();
-            pw.println(mTag + " current IpReachabilityMonitor state:");
-            pw.increaseIndent();
-            iprm.dump(pw);
-            pw.decreaseIndent();
-        }
-
-        pw.println();
-        pw.println(mTag + " StateMachine dump:");
-        pw.increaseIndent();
-        mLog.dump(fd, pw, args);
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " connectivity packet log:");
-        pw.println();
-        pw.println("Debug with python and scapy via:");
-        pw.println("shell$ python");
-        pw.println(">>> from scapy import all as scapy");
-        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
-        pw.println();
-
-        pw.increaseIndent();
-        mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
-        pw.decreaseIndent();
-    }
-
-
-    /**
-     * Internals.
-     */
-
-    @Override
-    protected String getWhatToString(int what) {
-        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
-    }
-
-    @Override
-    protected String getLogRecString(Message msg) {
-        final String logLine = String.format(
-                "%s/%d %d %d %s [%s]",
-                mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index,
-                msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
-
-        final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
-        mLog.log(richerLogLine);
-        if (DBG) {
-            Log.d(mTag, richerLogLine);
-        }
-
-        mMsgStateLogger.reset();
-        return logLine;
-    }
-
-    @Override
-    protected boolean recordLogRec(Message msg) {
-        // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
-        // and we already log any LinkProperties change that results in an
-        // invocation of IpClient.Callback#onLinkPropertiesChange().
-        final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
-        if (!shouldLog) {
-            mMsgStateLogger.reset();
-        }
-        return shouldLog;
-    }
-
-    private void logError(String fmt, Object... args) {
-        final String msg = "ERROR " + String.format(fmt, args);
-        Log.e(mTag, msg);
-        mLog.log(msg);
-    }
-
-    // This needs to be called with care to ensure that our LinkProperties
-    // are in sync with the actual LinkProperties of the interface. For example,
-    // we should only call this if we know for sure that there are no IP addresses
-    // assigned to the interface, etc.
-    private void resetLinkProperties() {
-        mLinkObserver.clearLinkProperties();
-        mConfiguration = null;
-        mDhcpResults = null;
-        mTcpBufferSizes = "";
-        mHttpProxy = null;
-
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-    }
-
-    private void recordMetric(final int type) {
-        // We may record error metrics prior to starting.
-        // Map this to IMMEDIATE_FAILURE_DURATION.
-        final long duration = (mStartTimeMillis > 0)
-                ? (SystemClock.elapsedRealtime() - mStartTimeMillis)
-                : IMMEDIATE_FAILURE_DURATION;
-        mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
-    }
-
-    // For now: use WifiStateMachine's historical notion of provisioned.
-    @VisibleForTesting
-    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
-        // For historical reasons, we should connect even if all we have is
-        // an IPv4 address and nothing else.
-        if (lp.hasIpv4Address() || lp.isProvisioned()) {
-            return true;
-        }
-        if (config == null) {
-            return false;
-        }
-
-        // When an InitialConfiguration is specified, ignore any difference with previous
-        // properties and instead check if properties observed match the desired properties.
-        return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
-    }
-
-    // TODO: Investigate folding all this into the existing static function
-    // LinkProperties.compareProvisioning() or some other single function that
-    // takes two LinkProperties objects and returns a ProvisioningChange
-    // object that is a correct and complete assessment of what changed, taking
-    // account of the asymmetries described in the comments in this function.
-    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
-    private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
-        int delta;
-        InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
-        final boolean wasProvisioned = isProvisioned(oldLp, config);
-        final boolean isProvisioned = isProvisioned(newLp, config);
-
-        if (!wasProvisioned && isProvisioned) {
-            delta = PROV_CHANGE_GAINED_PROVISIONING;
-        } else if (wasProvisioned && isProvisioned) {
-            delta = PROV_CHANGE_STILL_PROVISIONED;
-        } else if (!wasProvisioned && !isProvisioned) {
-            delta = PROV_CHANGE_STILL_NOT_PROVISIONED;
-        } else {
-            // (wasProvisioned && !isProvisioned)
-            //
-            // Note that this is true even if we lose a configuration element
-            // (e.g., a default gateway) that would not be required to advance
-            // into provisioned state. This is intended: if we have a default
-            // router and we lose it, that's a sure sign of a problem, but if
-            // we connect to a network with no IPv4 DNS servers, we consider
-            // that to be a network without DNS servers and connect anyway.
-            //
-            // See the comment below.
-            delta = PROV_CHANGE_LOST_PROVISIONING;
-        }
-
-        final boolean lostIPv6 = oldLp.isIpv6Provisioned() && !newLp.isIpv6Provisioned();
-        final boolean lostIPv4Address = oldLp.hasIpv4Address() && !newLp.hasIpv4Address();
-        final boolean lostIPv6Router = oldLp.hasIpv6DefaultRoute() && !newLp.hasIpv6DefaultRoute();
-
-        // If bad wifi avoidance is disabled, then ignore IPv6 loss of
-        // provisioning. Otherwise, when a hotspot that loses Internet
-        // access sends out a 0-lifetime RA to its clients, the clients
-        // will disconnect and then reconnect, avoiding the bad hotspot,
-        // instead of getting stuck on the bad hotspot. http://b/31827713 .
-        //
-        // This is incorrect because if the hotspot then regains Internet
-        // access with a different prefix, TCP connections on the
-        // deprecated addresses will remain stuck.
-        //
-        // Note that we can still be disconnected by IpReachabilityMonitor
-        // if the IPv6 default gateway (but not the IPv6 DNS servers; see
-        // accompanying code in IpReachabilityMonitor) is unreachable.
-        final boolean ignoreIPv6ProvisioningLoss =
-                mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
-                && mCm.shouldAvoidBadWifi();
-
-        // Additionally:
-        //
-        // Partial configurations (e.g., only an IPv4 address with no DNS
-        // servers and no default route) are accepted as long as DHCPv4
-        // succeeds. On such a network, isProvisioned() will always return
-        // false, because the configuration is not complete, but we want to
-        // connect anyway. It might be a disconnected network such as a
-        // Chromecast or a wireless printer, for example.
-        //
-        // Because on such a network isProvisioned() will always return false,
-        // delta will never be LOST_PROVISIONING. So check for loss of
-        // provisioning here too.
-        if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
-            delta = PROV_CHANGE_LOST_PROVISIONING;
-        }
-
-        // Additionally:
-        //
-        // If the previous link properties had a global IPv6 address and an
-        // IPv6 default route then also consider the loss of that default route
-        // to be a loss of provisioning. See b/27962810.
-        if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
-            delta = PROV_CHANGE_LOST_PROVISIONING;
-        }
-
-        return delta;
-    }
-
-    private void dispatchCallback(int delta, LinkProperties newLp) {
-        switch (delta) {
-            case PROV_CHANGE_GAINED_PROVISIONING:
-                if (DBG) {
-                    Log.d(mTag, "onProvisioningSuccess()");
-                }
-                recordMetric(IpManagerEvent.PROVISIONING_OK);
-                mCallback.onProvisioningSuccess(newLp);
-                break;
-
-            case PROV_CHANGE_LOST_PROVISIONING:
-                if (DBG) {
-                    Log.d(mTag, "onProvisioningFailure()");
-                }
-                recordMetric(IpManagerEvent.PROVISIONING_FAIL);
-                mCallback.onProvisioningFailure(newLp);
-                break;
-
-            default:
-                if (DBG) {
-                    Log.d(mTag, "onLinkPropertiesChange()");
-                }
-                mCallback.onLinkPropertiesChange(newLp);
-                break;
-        }
-    }
-
-    // Updates all IpClient-related state concerned with LinkProperties.
-    // Returns a ProvisioningChange for possibly notifying other interested
-    // parties that are not fronted by IpClient.
-    private int setLinkProperties(LinkProperties newLp) {
-        if (mApfFilter != null) {
-            mApfFilter.setLinkProperties(newLp);
-        }
-        if (mIpReachabilityMonitor != null) {
-            mIpReachabilityMonitor.updateLinkProperties(newLp);
-        }
-
-        int delta = compareProvisioning(mLinkProperties, newLp);
-        mLinkProperties = new LinkProperties(newLp);
-
-        if (delta == PROV_CHANGE_GAINED_PROVISIONING) {
-            // TODO: Add a proper ProvisionedState and cancel the alarm in
-            // its enter() method.
-            mProvisioningTimeoutAlarm.cancel();
-        }
-
-        return delta;
-    }
-
-    private LinkProperties assembleLinkProperties() {
-        // [1] Create a new LinkProperties object to populate.
-        LinkProperties newLp = new LinkProperties();
-        newLp.setInterfaceName(mInterfaceName);
-
-        // [2] Pull in data from netlink:
-        //         - IPv4 addresses
-        //         - IPv6 addresses
-        //         - IPv6 routes
-        //         - IPv6 DNS servers
-        //
-        // N.B.: this is fundamentally race-prone and should be fixed by
-        // changing IpClientLinkObserver from a hybrid edge/level model to an
-        // edge-only model, or by giving IpClient its own netlink socket(s)
-        // so as to track all required information directly.
-        LinkProperties netlinkLinkProperties = mLinkObserver.getLinkProperties();
-        newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
-        for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
-            newLp.addRoute(route);
-        }
-        addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
-
-        // [3] Add in data from DHCPv4, if available.
-        //
-        // mDhcpResults is never shared with any other owner so we don't have
-        // to worry about concurrent modification.
-        if (mDhcpResults != null) {
-            final List<RouteInfo> routes =
-                    mDhcpResults.toStaticIpConfiguration().getRoutes(mInterfaceName);
-            for (RouteInfo route : routes) {
-                newLp.addRoute(route);
-            }
-            addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
-            newLp.setDomains(mDhcpResults.domains);
-
-            if (mDhcpResults.mtu != 0) {
-                newLp.setMtu(mDhcpResults.mtu);
-            }
-        }
-
-        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
-        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
-            newLp.setTcpBufferSizes(mTcpBufferSizes);
-        }
-        if (mHttpProxy != null) {
-            newLp.setHttpProxy(mHttpProxy);
-        }
-
-        // [5] Add data from InitialConfiguration
-        if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
-            InitialConfiguration config = mConfiguration.mInitialConfig;
-            // Add InitialConfiguration routes and dns server addresses once all addresses
-            // specified in the InitialConfiguration have been observed with Netlink.
-            if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
-                for (IpPrefix prefix : config.directlyConnectedRoutes) {
-                    newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName, RTN_UNICAST));
-                }
-            }
-            addAllReachableDnsServers(newLp, config.dnsServers);
-        }
-        final LinkProperties oldLp = mLinkProperties;
-        if (DBG) {
-            Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
-                    netlinkLinkProperties, newLp, oldLp));
-        }
-
-        // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
-        // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
-        return newLp;
-    }
-
-    private static void addAllReachableDnsServers(
-            LinkProperties lp, Iterable<InetAddress> dnses) {
-        // TODO: Investigate deleting this reachability check.  We should be
-        // able to pass everything down to netd and let netd do evaluation
-        // and RFC6724-style sorting.
-        for (InetAddress dns : dnses) {
-            if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
-                lp.addDnsServer(dns);
-            }
-        }
-    }
-
-    // Returns false if we have lost provisioning, true otherwise.
-    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
-        final LinkProperties newLp = assembleLinkProperties();
-        if (Objects.equals(newLp, mLinkProperties)) {
-            return true;
-        }
-        final int delta = setLinkProperties(newLp);
-        // Most of the attributes stored in the memory store are deduced from
-        // the link properties, therefore when the properties update the memory
-        // store record should be updated too.
-        maybeSaveNetworkToIpMemoryStore();
-        if (sendCallbacks) {
-            dispatchCallback(delta, newLp);
-        }
-        return (delta != PROV_CHANGE_LOST_PROVISIONING);
-    }
-
-    private void handleIPv4Success(DhcpResults dhcpResults) {
-        mDhcpResults = new DhcpResults(dhcpResults);
-        final LinkProperties newLp = assembleLinkProperties();
-        final int delta = setLinkProperties(newLp);
-
-        if (DBG) {
-            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
-        }
-        mCallback.onNewDhcpResults(dhcpResults);
-        maybeSaveNetworkToIpMemoryStore();
-        dispatchCallback(delta, newLp);
-    }
-
-    private void handleIPv4Failure() {
-        // TODO: Investigate deleting this clearIPv4Address() call.
-        //
-        // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
-        // that could trigger a call to this function. If we missed handling
-        // that message in StartedState for some reason we would still clear
-        // any addresses upon entry to StoppedState.
-        mInterfaceCtrl.clearIPv4Address();
-        mDhcpResults = null;
-        if (DBG) {
-            Log.d(mTag, "onNewDhcpResults(null)");
-        }
-        mCallback.onNewDhcpResults(null);
-
-        handleProvisioningFailure();
-    }
-
-    private void handleProvisioningFailure() {
-        final LinkProperties newLp = assembleLinkProperties();
-        int delta = setLinkProperties(newLp);
-        // If we've gotten here and we're still not provisioned treat that as
-        // a total loss of provisioning.
-        //
-        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
-        // there was no usable IPv6 obtained before a non-zero provisioning
-        // timeout expired.
-        //
-        // Regardless: GAME OVER.
-        if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) {
-            delta = PROV_CHANGE_LOST_PROVISIONING;
-        }
-
-        dispatchCallback(delta, newLp);
-        if (delta == PROV_CHANGE_LOST_PROVISIONING) {
-            transitionTo(mStoppingState);
-        }
-    }
-
-    private void doImmediateProvisioningFailure(int failureType) {
-        logError("onProvisioningFailure(): %s", failureType);
-        recordMetric(failureType);
-        mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
-    }
-
-    private boolean startIPv4() {
-        // If we have a StaticIpConfiguration attempt to apply it and
-        // handle the result accordingly.
-        if (mConfiguration.mStaticIpConfig != null) {
-            if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.getIpAddress())) {
-                handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
-            } else {
-                return false;
-            }
-        } else {
-            // Start DHCPv4.
-            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams);
-            mDhcpClient.registerForPreDhcpNotification();
-            mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
-        }
-
-        return true;
-    }
-
-    private boolean startIPv6() {
-        return mInterfaceCtrl.setIPv6PrivacyExtensions(true)
-                && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode)
-                && mInterfaceCtrl.enableIPv6();
-    }
-
-    private boolean applyInitialConfig(InitialConfiguration config) {
-        // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
-        for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIpv6)) {
-            if (!mInterfaceCtrl.addAddress(addr)) return false;
-        }
-
-        return true;
-    }
-
-    private boolean startIpReachabilityMonitor() {
-        try {
-            // TODO: Fetch these parameters from settings, and install a
-            // settings observer to watch for update and re-program these
-            // parameters (Q: is this level of dynamic updatability really
-            // necessary or does reading from settings at startup suffice?).
-            final int numSolicits = 5;
-            final int interSolicitIntervalMs = 750;
-            setNeighborParameters(mNetd, mInterfaceName, numSolicits, interSolicitIntervalMs);
-        } catch (Exception e) {
-            mLog.e("Failed to adjust neighbor parameters", e);
-            // Carry on using the system defaults (currently: 3, 1000);
-        }
-
-        try {
-            mIpReachabilityMonitor = new IpReachabilityMonitor(
-                    mContext,
-                    mInterfaceParams,
-                    getHandler(),
-                    mLog,
-                    new IpReachabilityMonitor.Callback() {
-                        @Override
-                        public void notifyLost(InetAddress ip, String logMsg) {
-                            mCallback.onReachabilityLost(logMsg);
-                        }
-                    },
-                    mConfiguration.mUsingMultinetworkPolicyTracker);
-        } catch (IllegalArgumentException iae) {
-            // Failed to start IpReachabilityMonitor. Log it and call
-            // onProvisioningFailure() immediately.
-            //
-            // See http://b/31038971.
-            logError("IpReachabilityMonitor failure: %s", iae);
-            mIpReachabilityMonitor = null;
-        }
-
-        return (mIpReachabilityMonitor != null);
-    }
-
-    private void stopAllIP() {
-        // We don't need to worry about routes, just addresses, because:
-        //     - disableIpv6() will clear autoconf IPv6 routes as well, and
-        //     - we don't get IPv4 routes from netlink
-        // so we neither react to nor need to wait for changes in either.
-
-        mInterfaceCtrl.disableIPv6();
-        mInterfaceCtrl.clearAllAddresses();
-    }
-
-    private void maybeSaveNetworkToIpMemoryStore() {
-        // TODO : implement this
-    }
-
-    class StoppedState extends State {
-        @Override
-        public void enter() {
-            stopAllIP();
-
-            resetLinkProperties();
-            if (mStartTimeMillis > 0) {
-                // Completed a life-cycle; send a final empty LinkProperties
-                // (cleared in resetLinkProperties() above) and record an event.
-                mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties));
-                recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
-                mStartTimeMillis = 0;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_TERMINATE_AFTER_STOP:
-                    stopStateMachineUpdaters();
-                    quit();
-                    break;
-
-                case CMD_STOP:
-                    break;
-
-                case CMD_START:
-                    mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj;
-                    transitionTo(mStartedState);
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_TCP_BUFFER_SIZES:
-                    mTcpBufferSizes = (String) msg.obj;
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_HTTP_PROXY:
-                    mHttpProxy = (ProxyInfo) msg.obj;
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_L2KEY_GROUPHINT: {
-                    final Pair<String, String> args = (Pair<String, String>) msg.obj;
-                    mL2Key = args.first;
-                    mGroupHint = args.second;
-                    break;
-                }
-
-                case CMD_SET_MULTICAST_FILTER:
-                    mMulticastFiltering = (boolean) msg.obj;
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    // Everything is already stopped.
-                    logError("Unexpected CMD_ON_QUIT (already stopped).");
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    class StoppingState extends State {
-        @Override
-        public void enter() {
-            if (mDhcpClient == null) {
-                // There's no DHCPv4 for which to wait; proceed to stopped.
-                deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED));
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_JUMP_STOPPING_TO_STOPPED:
-                    transitionTo(mStoppedState);
-                    break;
-
-                case CMD_STOP:
-                    break;
-
-                case DhcpClient.CMD_CLEAR_LINKADDRESS:
-                    mInterfaceCtrl.clearIPv4Address();
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    mDhcpClient = null;
-                    transitionTo(mStoppedState);
-                    break;
-
-                default:
-                    deferMessage(msg);
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    class StartedState extends State {
-        @Override
-        public void enter() {
-            mStartTimeMillis = SystemClock.elapsedRealtime();
-
-            if (mConfiguration.mProvisioningTimeoutMs > 0) {
-                final long alarmTime = SystemClock.elapsedRealtime()
-                        + mConfiguration.mProvisioningTimeoutMs;
-                mProvisioningTimeoutAlarm.schedule(alarmTime);
-            }
-
-            if (readyToProceed()) {
-                deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING));
-            } else {
-                // Clear all IPv4 and IPv6 before proceeding to RunningState.
-                // Clean up any leftover state from an abnormal exit from
-                // tethering or during an IpClient restart.
-                stopAllIP();
-            }
-        }
-
-        @Override
-        public void exit() {
-            mProvisioningTimeoutAlarm.cancel();
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_JUMP_STARTED_TO_RUNNING:
-                    transitionTo(mRunningState);
-                    break;
-
-                case CMD_STOP:
-                    transitionTo(mStoppingState);
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    if (readyToProceed()) {
-                        transitionTo(mRunningState);
-                    }
-                    break;
-
-                case CMD_UPDATE_L2KEY_GROUPHINT: {
-                    final Pair<String, String> args = (Pair<String, String>) msg.obj;
-                    mL2Key = args.first;
-                    mGroupHint = args.second;
-                    // TODO : attributes should be saved to the memory store with
-                    // these new values if they differ from the previous ones.
-                    // If the state machine is in pure StartedState, then the values to input
-                    // are not known yet and should be updated when the LinkProperties are updated.
-                    // If the state machine is in RunningState (which is a child of StartedState)
-                    // then the next NUD check should be used to store the new values to avoid
-                    // inputting current values for what may be a different L3 network.
-                    break;
-                }
-
-                case EVENT_PROVISIONING_TIMEOUT:
-                    handleProvisioningFailure();
-                    break;
-
-                default:
-                    // It's safe to process messages out of order because the
-                    // only message that can both
-                    //     a) be received at this time and
-                    //     b) affect provisioning state
-                    // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
-                    deferMessage(msg);
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-
-        private boolean readyToProceed() {
-            return (!mLinkProperties.hasIpv4Address() && !mLinkProperties.hasGlobalIpv6Address());
-        }
-    }
-
-    class RunningState extends State {
-        private ConnectivityPacketTracker mPacketTracker;
-        private boolean mDhcpActionInFlight;
-
-        @Override
-        public void enter() {
-            ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
-            apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
-            apfConfig.multicastFilter = mMulticastFiltering;
-            // Get the Configuration for ApfFilter from Context
-            apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames();
-            apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList();
-            mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
-            // TODO: investigate the effects of any multicast filtering racing/interfering with the
-            // rest of this IP configuration startup.
-            if (mApfFilter == null) {
-                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
-            }
-
-            mPacketTracker = createPacketTracker();
-            if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
-
-            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
-                enqueueJumpToStoppingState();
-                return;
-            }
-
-            if (mConfiguration.mEnableIPv4 && !startIPv4()) {
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
-                enqueueJumpToStoppingState();
-                return;
-            }
-
-            final InitialConfiguration config = mConfiguration.mInitialConfig;
-            if ((config != null) && !applyInitialConfig(config)) {
-                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-                enqueueJumpToStoppingState();
-                return;
-            }
-
-            if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
-                doImmediateProvisioningFailure(
-                        IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
-                enqueueJumpToStoppingState();
-                return;
-            }
-        }
-
-        @Override
-        public void exit() {
-            stopDhcpAction();
-
-            if (mIpReachabilityMonitor != null) {
-                mIpReachabilityMonitor.stop();
-                mIpReachabilityMonitor = null;
-            }
-
-            if (mDhcpClient != null) {
-                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
-                mDhcpClient.doQuit();
-            }
-
-            if (mPacketTracker != null) {
-                mPacketTracker.stop();
-                mPacketTracker = null;
-            }
-
-            if (mApfFilter != null) {
-                mApfFilter.shutdown();
-                mApfFilter = null;
-            }
-
-            resetLinkProperties();
-        }
-
-        private void enqueueJumpToStoppingState() {
-            deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING));
-        }
-
-        private ConnectivityPacketTracker createPacketTracker() {
-            try {
-                return new ConnectivityPacketTracker(
-                        getHandler(), mInterfaceParams, mConnectivityPacketLog);
-            } catch (IllegalArgumentException e) {
-                return null;
-            }
-        }
-
-        private void ensureDhcpAction() {
-            if (!mDhcpActionInFlight) {
-                mCallback.onPreDhcpAction();
-                mDhcpActionInFlight = true;
-                final long alarmTime = SystemClock.elapsedRealtime()
-                        + mConfiguration.mRequestedPreDhcpActionMs;
-                mDhcpActionTimeoutAlarm.schedule(alarmTime);
-            }
-        }
-
-        private void stopDhcpAction() {
-            mDhcpActionTimeoutAlarm.cancel();
-            if (mDhcpActionInFlight) {
-                mCallback.onPostDhcpAction();
-                mDhcpActionInFlight = false;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_JUMP_RUNNING_TO_STOPPING:
-                case CMD_STOP:
-                    transitionTo(mStoppingState);
-                    break;
-
-                case CMD_START:
-                    logError("ALERT: START received in StartedState. Please fix caller.");
-                    break;
-
-                case CMD_CONFIRM:
-                    // TODO: Possibly introduce a second type of confirmation
-                    // that both probes (a) on-link neighbors and (b) does
-                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
-                    // roams.
-                    if (mIpReachabilityMonitor != null) {
-                        mIpReachabilityMonitor.probeAll();
-                    }
-                    break;
-
-                case EVENT_PRE_DHCP_ACTION_COMPLETE:
-                    // It's possible to reach here if, for example, someone
-                    // calls completedPreDhcpAction() after provisioning with
-                    // a static IP configuration.
-                    if (mDhcpClient != null) {
-                        mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
-                    }
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
-                        transitionTo(mStoppingState);
-                    }
-                    break;
-
-                case CMD_UPDATE_TCP_BUFFER_SIZES:
-                    mTcpBufferSizes = (String) msg.obj;
-                    // This cannot possibly change provisioning state.
-                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_HTTP_PROXY:
-                    mHttpProxy = (ProxyInfo) msg.obj;
-                    // This cannot possibly change provisioning state.
-                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
-                    break;
-
-                case CMD_SET_MULTICAST_FILTER: {
-                    mMulticastFiltering = (boolean) msg.obj;
-                    if (mApfFilter != null) {
-                        mApfFilter.setMulticastFilter(mMulticastFiltering);
-                    } else {
-                        mCallback.setFallbackMulticastFilter(mMulticastFiltering);
-                    }
-                    break;
-                }
-
-                case EVENT_READ_PACKET_FILTER_COMPLETE: {
-                    if (mApfFilter != null) {
-                        mApfFilter.setDataSnapshot((byte[]) msg.obj);
-                    }
-                    mApfDataSnapshotComplete.open();
-                    break;
-                }
-
-                case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: {
-                    final int slot = msg.arg1;
-
-                    if (mApfFilter != null) {
-                        if (msg.obj instanceof NattKeepalivePacketDataParcelable) {
-                            mApfFilter.addNattKeepalivePacketFilter(slot,
-                                    (NattKeepalivePacketDataParcelable) msg.obj);
-                        } else if (msg.obj instanceof TcpKeepalivePacketDataParcelable) {
-                            mApfFilter.addTcpKeepalivePacketFilter(slot,
-                                    (TcpKeepalivePacketDataParcelable) msg.obj);
-                        }
-                    }
-                    break;
-                }
-
-                case CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF: {
-                    final int slot = msg.arg1;
-                    if (mApfFilter != null) {
-                        mApfFilter.removeKeepalivePacketFilter(slot);
-                    }
-                    break;
-                }
-
-                case EVENT_DHCPACTION_TIMEOUT:
-                    stopDhcpAction();
-                    break;
-
-                case DhcpClient.CMD_PRE_DHCP_ACTION:
-                    if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
-                        ensureDhcpAction();
-                    } else {
-                        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
-                    }
-                    break;
-
-                case DhcpClient.CMD_CLEAR_LINKADDRESS:
-                    mInterfaceCtrl.clearIPv4Address();
-                    break;
-
-                case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
-                    final LinkAddress ipAddress = (LinkAddress) msg.obj;
-                    if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
-                        mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
-                    } else {
-                        logError("Failed to set IPv4 address.");
-                        dispatchCallback(PROV_CHANGE_LOST_PROVISIONING,
-                                new LinkProperties(mLinkProperties));
-                        transitionTo(mStoppingState);
-                    }
-                    break;
-                }
-
-                // This message is only received when:
-                //
-                //     a) initial address acquisition succeeds,
-                //     b) renew succeeds or is NAK'd,
-                //     c) rebind succeeds or is NAK'd, or
-                //     c) the lease expires,
-                //
-                // but never when initial address acquisition fails. The latter
-                // condition is now governed by the provisioning timeout.
-                case DhcpClient.CMD_POST_DHCP_ACTION:
-                    stopDhcpAction();
-
-                    switch (msg.arg1) {
-                        case DhcpClient.DHCP_SUCCESS:
-                            handleIPv4Success((DhcpResults) msg.obj);
-                            break;
-                        case DhcpClient.DHCP_FAILURE:
-                            handleIPv4Failure();
-                            break;
-                        default:
-                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
-                    }
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    // DHCPv4 quit early for some reason.
-                    logError("Unexpected CMD_ON_QUIT.");
-                    mDhcpClient = null;
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    private static class MessageHandlingLogger {
-        public String processedInState;
-        public String receivedInState;
-
-        public void reset() {
-            processedInState = null;
-            receivedInState = null;
-        }
-
-        public void handled(State processedIn, IState receivedIn) {
-            processedInState = processedIn.getClass().getSimpleName();
-            receivedInState = receivedIn.getName();
-        }
-
-        public String toString() {
-            return String.format("rcvd_in=%s, proc_in=%s",
-                                 receivedInState, processedInState);
-        }
-    }
-
-    private static void setNeighborParameters(
-            INetd netd, String ifName, int numSolicits, int interSolicitIntervalMs)
-            throws RemoteException, IllegalArgumentException {
-        Preconditions.checkNotNull(netd);
-        Preconditions.checkArgument(!TextUtils.isEmpty(ifName));
-        Preconditions.checkArgument(numSolicits > 0);
-        Preconditions.checkArgument(interSolicitIntervalMs > 0);
-
-        for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) {
-            netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms",
-                    Integer.toString(interSolicitIntervalMs));
-            netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit",
-                    Integer.toString(numSolicits));
-        }
-    }
-
-    // TODO: extract out into CollectionUtils.
-    static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
-        for (T t : coll) {
-            if (fn.test(t)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
-        return !any(coll, not(fn));
-    }
-
-    static <T> Predicate<T> not(Predicate<T> fn) {
-        return (t) -> !fn.test(t);
-    }
-
-    static <T> String join(String delimiter, Collection<T> coll) {
-        return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
-    }
-
-    static <T> T find(Iterable<T> coll, Predicate<T> fn) {
-        for (T t: coll) {
-            if (fn.test(t)) {
-                return t;
-            }
-        }
-        return null;
-    }
-
-    static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
-        return coll.stream().filter(fn).collect(Collectors.toList());
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/ip/IpClientLinkObserver.java b/packages/NetworkStack/src/android/net/ip/IpClientLinkObserver.java
deleted file mode 100644
index 8ad99aa0..0000000
--- a/packages/NetworkStack/src/android/net/ip/IpClientLinkObserver.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ip;
-
-import android.net.InetAddresses;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.RouteInfo;
-import android.util.Log;
-
-import com.android.server.NetworkObserver;
-
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Keeps track of link configuration received from Netd.
- *
- * An instance of this class is constructed by passing in an interface name and a callback. The
- * owner is then responsible for registering the tracker with NetworkObserverRegistry. When the
- * class receives update notifications, it applies the update to its local LinkProperties, and if
- * something has changed, notifies its owner of the update via the callback.
- *
- * The owner can then call {@code getLinkProperties()} in order to find out
- * what changed. If in the meantime the LinkProperties stored here have changed,
- * this class will return the current LinkProperties. Because each change
- * triggers an update callback after the change is made, the owner may get more
- * callbacks than strictly necessary (some of which may be no-ops), but will not
- * be out of sync once all callbacks have been processed.
- *
- * Threading model:
- *
- * - The owner of this class is expected to create it, register it, and call
- *   getLinkProperties or clearLinkProperties on its thread.
- * - Most of the methods in the class are implementing NetworkObserver and are called
- *   on the handler used to register the observer.
- * - All accesses to mLinkProperties must be synchronized(this). All the other
- *   member variables are immutable once the object is constructed.
- *
- * @hide
- */
-public class IpClientLinkObserver implements NetworkObserver {
-    private final String mTag;
-
-    /**
-     * Callback used by {@link IpClientLinkObserver} to send update notifications.
-     */
-    public interface Callback {
-        /**
-         * Called when some properties of the link were updated.
-         */
-        void update();
-    }
-
-    private final String mInterfaceName;
-    private final Callback mCallback;
-    private final LinkProperties mLinkProperties;
-    private DnsServerRepository mDnsServerRepository;
-
-    private static final boolean DBG = false;
-
-    public IpClientLinkObserver(String iface, Callback callback) {
-        mTag = "NetlinkTracker/" + iface;
-        mInterfaceName = iface;
-        mCallback = callback;
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-        mDnsServerRepository = new DnsServerRepository();
-    }
-
-    private void maybeLog(String operation, String iface, LinkAddress address) {
-        if (DBG) {
-            Log.d(mTag, operation + ": " + address + " on " + iface
-                    + " flags " + address.getFlags() + " scope " + address.getScope());
-        }
-    }
-
-    private void maybeLog(String operation, Object o) {
-        if (DBG) {
-            Log.d(mTag, operation + ": " + o.toString());
-        }
-    }
-
-    @Override
-    public void onInterfaceRemoved(String iface) {
-        maybeLog("interfaceRemoved", iface);
-        if (mInterfaceName.equals(iface)) {
-            // Our interface was removed. Clear our LinkProperties and tell our owner that they are
-            // now empty. Note that from the moment that the interface is removed, any further
-            // interface-specific messages (e.g., RTM_DELADDR) will not reach us, because the netd
-            // code that parses them will not be able to resolve the ifindex to an interface name.
-            clearLinkProperties();
-            mCallback.update();
-        }
-    }
-
-    @Override
-    public void onInterfaceAddressUpdated(LinkAddress address, String iface) {
-        if (mInterfaceName.equals(iface)) {
-            maybeLog("addressUpdated", iface, address);
-            boolean changed;
-            synchronized (this) {
-                changed = mLinkProperties.addLinkAddress(address);
-            }
-            if (changed) {
-                mCallback.update();
-            }
-        }
-    }
-
-    @Override
-    public void onInterfaceAddressRemoved(LinkAddress address, String iface) {
-        if (mInterfaceName.equals(iface)) {
-            maybeLog("addressRemoved", iface, address);
-            boolean changed;
-            synchronized (this) {
-                changed = mLinkProperties.removeLinkAddress(address);
-            }
-            if (changed) {
-                mCallback.update();
-            }
-        }
-    }
-
-    @Override
-    public void onRouteUpdated(RouteInfo route) {
-        if (mInterfaceName.equals(route.getInterface())) {
-            maybeLog("routeUpdated", route);
-            boolean changed;
-            synchronized (this) {
-                changed = mLinkProperties.addRoute(route);
-            }
-            if (changed) {
-                mCallback.update();
-            }
-        }
-    }
-
-    @Override
-    public void onRouteRemoved(RouteInfo route) {
-        if (mInterfaceName.equals(route.getInterface())) {
-            maybeLog("routeRemoved", route);
-            boolean changed;
-            synchronized (this) {
-                changed = mLinkProperties.removeRoute(route);
-            }
-            if (changed) {
-                mCallback.update();
-            }
-        }
-    }
-
-    @Override
-    public void onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
-        if (mInterfaceName.equals(iface)) {
-            maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
-            boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
-            if (changed) {
-                synchronized (this) {
-                    mDnsServerRepository.setDnsServersOn(mLinkProperties);
-                }
-                mCallback.update();
-            }
-        }
-    }
-
-    /**
-     * Returns a copy of this object's LinkProperties.
-     */
-    public synchronized LinkProperties getLinkProperties() {
-        return new LinkProperties(mLinkProperties);
-    }
-
-    /**
-     * Reset this object's LinkProperties.
-     */
-    public synchronized void clearLinkProperties() {
-        // Clear the repository before clearing mLinkProperties. That way, if a clear() happens
-        // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in
-        // mLinkProperties, as desired.
-        mDnsServerRepository = new DnsServerRepository();
-        mLinkProperties.clear();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-    }
-
-    /**
-     * Tracks DNS server updates received from Netlink.
-     *
-     * The network may announce an arbitrary number of DNS servers in Router Advertisements at any
-     * time. Each announcement has a lifetime; when the lifetime expires, the servers should not be
-     * used any more. In this way, the network can gracefully migrate clients from one set of DNS
-     * servers to another. Announcements can both raise and lower the lifetime, and an announcement
-     * can expire servers by announcing them with a lifetime of zero.
-     *
-     * Typically the system will only use a small number (2 or 3; {@code NUM_CURRENT_SERVERS}) of
-     * DNS servers at any given time. These are referred to as the current servers. In case all the
-     * current servers expire, the class also keeps track of a larger (but limited) number of
-     * servers that are promoted to current servers when the current ones expire. In order to
-     * minimize updates to the rest of the system (and potentially expensive cache flushes) this
-     * class attempts to keep the list of current servers constant where possible. More
-     * specifically, the list of current servers is only updated if a new server is learned and
-     * there are not yet {@code NUM_CURRENT_SERVERS} current servers, or if one or more of the
-     * current servers expires or is pushed out of the set. Therefore, the current servers will not
-     * necessarily be the ones with the highest lifetime, but the ones learned first.
-     *
-     * This is by design: if instead the class always preferred the servers with the highest
-     * lifetime, a (misconfigured?) network where two or more routers announce more than
-     * {@code NUM_CURRENT_SERVERS} unique servers would cause persistent oscillations.
-     *
-     * TODO: Currently servers are only expired when a new DNS update is received.
-     * Update them using timers, or possibly on every notification received by NetlinkTracker.
-     *
-     * Threading model: run by NetlinkTracker. Methods are synchronized(this) just in case netlink
-     * notifications are sent by multiple threads. If future threads use alarms to expire, those
-     * alarms must also be synchronized(this).
-     *
-     */
-    private static class DnsServerRepository {
-
-        /** How many DNS servers we will use. 3 is suggested by RFC 6106. */
-        static final int NUM_CURRENT_SERVERS = 3;
-
-        /** How many DNS servers we'll keep track of, in total. */
-        static final int NUM_SERVERS = 12;
-
-        /** Stores up to {@code NUM_CURRENT_SERVERS} DNS servers we're currently using. */
-        private Set<InetAddress> mCurrentServers;
-
-        public static final String TAG = "DnsServerRepository";
-
-        /**
-         * Stores all the DNS servers we know about, for use when the current servers expire.
-         * Always sorted in order of decreasing expiry. The elements in this list are also the
-         * values of mIndex, and may be elements in mCurrentServers.
-         */
-        private ArrayList<DnsServerEntry> mAllServers;
-
-        /**
-         * Indexes the servers so we can update their lifetimes more quickly in the common case
-         * where servers are not being added, but only being refreshed.
-         */
-        private HashMap<InetAddress, DnsServerEntry> mIndex;
-
-        DnsServerRepository() {
-            mCurrentServers = new HashSet<>();
-            mAllServers = new ArrayList<>(NUM_SERVERS);
-            mIndex = new HashMap<>(NUM_SERVERS);
-        }
-
-        /** Sets the DNS servers of the provided LinkProperties object to the current servers. */
-        public synchronized void setDnsServersOn(LinkProperties lp) {
-            lp.setDnsServers(mCurrentServers);
-        }
-
-        /**
-         * Notifies the class of new DNS server information.
-         * @param lifetime the time in seconds that the DNS servers are valid.
-         * @param addresses the string representations of the IP addresses of DNS servers to use.
-         */
-        public synchronized boolean addServers(long lifetime, String[] addresses) {
-            // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned.
-            // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds
-            // (136 years) is close enough.
-            long now = System.currentTimeMillis();
-            long expiry = now + 1000 * lifetime;
-
-            // Go through the list of servers. For each one, update the entry if one exists, and
-            // create one if it doesn't.
-            for (String addressString : addresses) {
-                InetAddress address;
-                try {
-                    address = InetAddresses.parseNumericAddress(addressString);
-                } catch (IllegalArgumentException ex) {
-                    continue;
-                }
-
-                if (!updateExistingEntry(address, expiry)) {
-                    // There was no entry for this server. Create one, unless it's already expired
-                    // (i.e., if the lifetime is zero; it cannot be < 0 because it's unsigned).
-                    if (expiry > now) {
-                        DnsServerEntry entry = new DnsServerEntry(address, expiry);
-                        mAllServers.add(entry);
-                        mIndex.put(address, entry);
-                    }
-                }
-            }
-
-            // Sort the servers by expiry.
-            Collections.sort(mAllServers);
-
-            // Prune excess entries and update the current server list.
-            return updateCurrentServers();
-        }
-
-        private synchronized boolean updateExistingEntry(InetAddress address, long expiry) {
-            DnsServerEntry existing = mIndex.get(address);
-            if (existing != null) {
-                existing.expiry = expiry;
-                return true;
-            }
-            return false;
-        }
-
-        private synchronized boolean updateCurrentServers() {
-            long now = System.currentTimeMillis();
-            boolean changed = false;
-
-            // Prune excess or expired entries.
-            for (int i = mAllServers.size() - 1; i >= 0; i--) {
-                if (i >= NUM_SERVERS || mAllServers.get(i).expiry < now) {
-                    DnsServerEntry removed = mAllServers.remove(i);
-                    mIndex.remove(removed.address);
-                    changed |= mCurrentServers.remove(removed.address);
-                } else {
-                    break;
-                }
-            }
-
-            // Add servers to the current set, in order of decreasing lifetime, until it has enough.
-            // Prefer existing servers over new servers in order to minimize updates to the rest of
-            // the system and avoid persistent oscillations.
-            for (DnsServerEntry entry : mAllServers) {
-                if (mCurrentServers.size() < NUM_CURRENT_SERVERS) {
-                    changed |= mCurrentServers.add(entry.address);
-                } else {
-                    break;
-                }
-            }
-            return changed;
-        }
-    }
-
-    /**
-     * Represents a DNS server entry with an expiry time.
-     *
-     * Implements Comparable so DNS server entries can be sorted by lifetime, longest-lived first.
-     * The ordering of entries with the same lifetime is unspecified, because given two servers with
-     * identical lifetimes, we don't care which one we use, and only comparing the lifetime is much
-     * faster than comparing the IP address as well.
-     *
-     * Note: this class has a natural ordering that is inconsistent with equals.
-     */
-    private static class DnsServerEntry implements Comparable<DnsServerEntry> {
-        /** The IP address of the DNS server. */
-        public final InetAddress address;
-        /** The time until which the DNS server may be used. A Java millisecond time as might be
-         * returned by currentTimeMillis(). */
-        public long expiry;
-
-        DnsServerEntry(InetAddress address, long expiry) throws IllegalArgumentException {
-            this.address = address;
-            this.expiry = expiry;
-        }
-
-        public int compareTo(DnsServerEntry other) {
-            return Long.compare(other.expiry, this.expiry);
-        }
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
deleted file mode 100644
index 6ae9a2b..0000000
--- a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
+++ /dev/null
@@ -1,247 +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.net.ip;
-
-import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH;
-import static android.net.netlink.NetlinkConstants.hexify;
-import static android.net.netlink.NetlinkConstants.stringForNlMsgType;
-import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
-import static android.system.OsConstants.AF_NETLINK;
-import static android.system.OsConstants.NETLINK_ROUTE;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOCK_NONBLOCK;
-
-import android.net.MacAddress;
-import android.net.netlink.NetlinkErrorMessage;
-import android.net.netlink.NetlinkMessage;
-import android.net.netlink.NetlinkSocket;
-import android.net.netlink.RtNetlinkNeighborMessage;
-import android.net.netlink.StructNdMsg;
-import android.net.util.NetworkStackUtils;
-import android.net.util.PacketReader;
-import android.net.util.SharedLog;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.net.InetAddress;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.StringJoiner;
-
-
-/**
- * IpNeighborMonitor.
- *
- * Monitors the kernel rtnetlink neighbor notifications and presents to callers
- * NeighborEvents describing each event. Callers can provide a consumer instance
- * to both filter (e.g. by interface index and IP address) and handle the
- * generated NeighborEvents.
- *
- * @hide
- */
-public class IpNeighborMonitor extends PacketReader {
-    private static final String TAG = IpNeighborMonitor.class.getSimpleName();
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false;
-
-    /**
-     * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
-     * for the given IP address on the specified interface index.
-     *
-     * @return 0 if the request was successfully passed to the kernel; otherwise return
-     *         a non-zero error code.
-     */
-    public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) {
-        final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
-        if (DBG) { Log.d(TAG, msgSnippet); }
-
-        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
-                1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
-
-        try {
-            NetlinkSocket.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
-        } catch (ErrnoException e) {
-            Log.e(TAG, "Error " + msgSnippet + ": " + e);
-            return -e.errno;
-        }
-
-        return 0;
-    }
-
-    public static class NeighborEvent {
-        final long elapsedMs;
-        final short msgType;
-        final int ifindex;
-        final InetAddress ip;
-        final short nudState;
-        final MacAddress macAddr;
-
-        public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
-                short nudState, MacAddress macAddr) {
-            this.elapsedMs = elapsedMs;
-            this.msgType = msgType;
-            this.ifindex = ifindex;
-            this.ip = ip;
-            this.nudState = nudState;
-            this.macAddr = macAddr;
-        }
-
-        boolean isConnected() {
-            return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateConnected(nudState);
-        }
-
-        boolean isValid() {
-            return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateValid(nudState);
-        }
-
-        @Override
-        public String toString() {
-            final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
-            return j.add("@" + elapsedMs)
-                    .add(stringForNlMsgType(msgType))
-                    .add("if=" + ifindex)
-                    .add(ip.getHostAddress())
-                    .add(StructNdMsg.stringForNudState(nudState))
-                    .add("[" + macAddr + "]")
-                    .toString();
-        }
-    }
-
-    public interface NeighborEventConsumer {
-        // Every neighbor event received on the netlink socket is passed in
-        // here. Subclasses should filter for events of interest.
-        public void accept(NeighborEvent event);
-    }
-
-    private final SharedLog mLog;
-    private final NeighborEventConsumer mConsumer;
-
-    public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
-        super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
-        mLog = log.forSubComponent(TAG);
-        mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
-    }
-
-    @Override
-    protected FileDescriptor createFd() {
-        FileDescriptor fd = null;
-
-        try {
-            fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, NETLINK_ROUTE);
-            Os.bind(fd, makeNetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH));
-            NetlinkSocket.connectToKernel(fd);
-
-            if (VDBG) {
-                final SocketAddress nlAddr = Os.getsockname(fd);
-                Log.d(TAG, "bound to sockaddr_nl{" + nlAddr.toString() + "}");
-            }
-        } catch (ErrnoException|SocketException e) {
-            logError("Failed to create rtnetlink socket", e);
-            NetworkStackUtils.closeSocketQuietly(fd);
-            return null;
-        }
-
-        return fd;
-    }
-
-    @Override
-    protected void handlePacket(byte[] recvbuf, int length) {
-        final long whenMs = SystemClock.elapsedRealtime();
-
-        final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
-        byteBuffer.order(ByteOrder.nativeOrder());
-
-        parseNetlinkMessageBuffer(byteBuffer, whenMs);
-    }
-
-    private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
-        while (byteBuffer.remaining() > 0) {
-            final int position = byteBuffer.position();
-            final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
-            if (nlMsg == null || nlMsg.getHeader() == null) {
-                byteBuffer.position(position);
-                mLog.e("unparsable netlink msg: " + hexify(byteBuffer));
-                break;
-            }
-
-            final int srcPortId = nlMsg.getHeader().nlmsg_pid;
-            if (srcPortId !=  0) {
-                mLog.e("non-kernel source portId: " + Integer.toUnsignedLong(srcPortId));
-                break;
-            }
-
-            if (nlMsg instanceof NetlinkErrorMessage) {
-                mLog.e("netlink error: " + nlMsg);
-                continue;
-            } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
-                mLog.i("non-rtnetlink neighbor msg: " + nlMsg);
-                continue;
-            }
-
-            evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
-        }
-    }
-
-    private void evaluateRtNetlinkNeighborMessage(
-            RtNetlinkNeighborMessage neighMsg, long whenMs) {
-        final short msgType = neighMsg.getHeader().nlmsg_type;
-        final StructNdMsg ndMsg = neighMsg.getNdHeader();
-        if (ndMsg == null) {
-            mLog.e("RtNetlinkNeighborMessage without ND message header!");
-            return;
-        }
-
-        final int ifindex = ndMsg.ndm_ifindex;
-        final InetAddress destination = neighMsg.getDestination();
-        final short nudState =
-                (msgType == RTM_DELNEIGH)
-                ? StructNdMsg.NUD_NONE
-                : ndMsg.ndm_state;
-
-        final NeighborEvent event = new NeighborEvent(
-                whenMs, msgType, ifindex, destination, nudState,
-                getMacAddress(neighMsg.getLinkLayerAddress()));
-
-        if (VDBG) {
-            Log.d(TAG, neighMsg.toString());
-        }
-        if (DBG) {
-            Log.d(TAG, event.toString());
-        }
-
-        mConsumer.accept(event);
-    }
-
-    private static MacAddress getMacAddress(byte[] linkLayerAddress) {
-        if (linkLayerAddress != null) {
-            try {
-                return MacAddress.fromBytes(linkLayerAddress);
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "Failed to parse link-layer address: " + hexify(linkLayerAddress));
-            }
-        }
-
-        return null;
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
deleted file mode 100644
index c19a24e..0000000
--- a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
+++ /dev/null
@@ -1,398 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ip;
-
-import static android.net.metrics.IpReachabilityEvent.NUD_FAILED;
-import static android.net.metrics.IpReachabilityEvent.NUD_FAILED_ORGANIC;
-import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST;
-import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.RouteInfo;
-import android.net.ip.IpNeighborMonitor.NeighborEvent;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpReachabilityEvent;
-import android.net.netlink.StructNdMsg;
-import android.net.util.InterfaceParams;
-import android.net.util.SharedLog;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.PrintWriter;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-
-/**
- * IpReachabilityMonitor.
- *
- * Monitors on-link IP reachability and notifies callers whenever any on-link
- * addresses of interest appear to have become unresponsive.
- *
- * This code does not concern itself with "why" a neighbour might have become
- * unreachable. Instead, it primarily reacts to the kernel's notion of IP
- * reachability for each of the neighbours we know to be critically important
- * to normal network connectivity. As such, it is often "just the messenger":
- * the neighbours about which it warns are already deemed by the kernel to have
- * become unreachable.
- *
- *
- * How it works:
- *
- *   1. The "on-link neighbours of interest" found in a given LinkProperties
- *      instance are added to a "watch list" via #updateLinkProperties().
- *      This usually means all default gateways and any on-link DNS servers.
- *
- *   2. We listen continuously for netlink neighbour messages (RTM_NEWNEIGH,
- *      RTM_DELNEIGH), watching only for neighbours in the watch list.
- *
- *        - A neighbour going into NUD_REACHABLE, NUD_STALE, NUD_DELAY, and
- *          even NUD_PROBE is perfectly normal; we merely record the new state.
- *
- *        - A neighbour's entry may be deleted (RTM_DELNEIGH), for example due
- *          to garbage collection.  This is not necessarily of immediate
- *          concern; we record the neighbour as moving to NUD_NONE.
- *
- *        - A neighbour transitioning to NUD_FAILED (for any reason) is
- *          critically important and is handled as described below in #4.
- *
- *   3. All on-link neighbours in the watch list can be forcibly "probed" by
- *      calling #probeAll(). This should be called whenever it is important to
- *      verify that critical neighbours on the link are still reachable, e.g.
- *      when roaming between BSSIDs.
- *
- *        - The kernel will send unicast ARP requests for IPv4 neighbours and
- *          unicast NS packets for IPv6 neighbours.  The expected replies will
- *          likely be unicast.
- *
- *        - The forced probing is done holding a wakelock. The kernel may,
- *          however, initiate probing of a neighbor on its own, i.e. whenever
- *          a neighbour has expired from NUD_DELAY.
- *
- *        - The kernel sends:
- *
- *              /proc/sys/net/ipv{4,6}/neigh/<ifname>/ucast_solicit
- *
- *          number of probes (usually 3) every:
- *
- *              /proc/sys/net/ipv{4,6}/neigh/<ifname>/retrans_time_ms
- *
- *          number of milliseconds (usually 1000ms). This normally results in
- *          3 unicast packets, 1 per second.
- *
- *        - If no response is received to any of the probe packets, the kernel
- *          marks the neighbour as being in state NUD_FAILED, and the listening
- *          process in #2 will learn of it.
- *
- *   4. We call the supplied Callback#notifyLost() function if the loss of a
- *      neighbour in NUD_FAILED would cause IPv4 or IPv6 configuration to
- *      become incomplete (a loss of provisioning).
- *
- *        - For example, losing all our IPv4 on-link DNS servers (or losing
- *          our only IPv6 default gateway) constitutes a loss of IPv4 (IPv6)
- *          provisioning; Callback#notifyLost() would be called.
- *
- *        - Since it can be non-trivial to reacquire certain IP provisioning
- *          state it may be best for the link to disconnect completely and
- *          reconnect afresh.
- *
- * Accessing an instance of this class from multiple threads is NOT safe.
- *
- * @hide
- */
-public class IpReachabilityMonitor {
-    private static final String TAG = "IpReachabilityMonitor";
-    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
-
-    public interface Callback {
-        // This callback function must execute as quickly as possible as it is
-        // run on the same thread that listens to kernel neighbor updates.
-        //
-        // TODO: refactor to something like notifyProvisioningLost(String msg).
-        public void notifyLost(InetAddress ip, String logMsg);
-    }
-
-    /**
-     * Encapsulates IpReachabilityMonitor depencencies on systems that hinder unit testing.
-     * TODO: consider also wrapping MultinetworkPolicyTracker in this interface.
-     */
-    interface Dependencies {
-        void acquireWakeLock(long durationMs);
-
-        static Dependencies makeDefault(Context context, String iface) {
-            final String lockName = TAG + "." + iface;
-            final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-            final WakeLock lock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
-
-            return new Dependencies() {
-                public void acquireWakeLock(long durationMs) {
-                    lock.acquire(durationMs);
-                }
-            };
-        }
-    }
-
-    private final InterfaceParams mInterfaceParams;
-    private final IpNeighborMonitor mIpNeighborMonitor;
-    private final SharedLog mLog;
-    private final Callback mCallback;
-    private final Dependencies mDependencies;
-    private final boolean mUsingMultinetworkPolicyTracker;
-    private final ConnectivityManager mCm;
-    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-    private LinkProperties mLinkProperties = new LinkProperties();
-    private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
-    // Time in milliseconds of the last forced probe request.
-    private volatile long mLastProbeTimeMs;
-
-    public IpReachabilityMonitor(
-            Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
-            boolean usingMultinetworkPolicyTracker) {
-        this(context, ifParams, h, log, callback, usingMultinetworkPolicyTracker,
-                Dependencies.makeDefault(context, ifParams.name));
-    }
-
-    @VisibleForTesting
-    IpReachabilityMonitor(Context context, InterfaceParams ifParams, Handler h, SharedLog log,
-            Callback callback, boolean usingMultinetworkPolicyTracker, Dependencies dependencies) {
-        if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
-
-        mInterfaceParams = ifParams;
-        mLog = log.forSubComponent(TAG);
-        mCallback = callback;
-        mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker;
-        mCm = context.getSystemService(ConnectivityManager.class);
-        mDependencies = dependencies;
-
-        mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
-                (NeighborEvent event) -> {
-                    if (mInterfaceParams.index != event.ifindex) return;
-                    if (!mNeighborWatchList.containsKey(event.ip)) return;
-
-                    final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
-
-                    // TODO: Consider what to do with other states that are not within
-                    // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
-                    if (event.nudState == StructNdMsg.NUD_FAILED) {
-                        mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
-                        handleNeighborLost(event);
-                    }
-                });
-        mIpNeighborMonitor.start();
-    }
-
-    public void stop() {
-        mIpNeighborMonitor.stop();
-        clearLinkProperties();
-    }
-
-    public void dump(PrintWriter pw) {
-        if (Looper.myLooper() == mIpNeighborMonitor.getHandler().getLooper()) {
-            pw.println(describeWatchList("\n"));
-            return;
-        }
-
-        final ConditionVariable cv = new ConditionVariable(false);
-        mIpNeighborMonitor.getHandler().post(() -> {
-            pw.println(describeWatchList("\n"));
-            cv.open();
-        });
-
-        if (!cv.block(1000)) {
-            pw.println("Timed out waiting for IpReachabilityMonitor dump");
-        }
-    }
-
-    private String describeWatchList() { return describeWatchList(" "); }
-
-    private String describeWatchList(String sep) {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("iface{" + mInterfaceParams + "}," + sep);
-        sb.append("ntable=[" + sep);
-        String delimiter = "";
-        for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
-            sb.append(delimiter).append(entry.getKey().getHostAddress() + "/" + entry.getValue());
-            delimiter = "," + sep;
-        }
-        sb.append("]");
-        return sb.toString();
-    }
-
-    private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
-        for (RouteInfo route : routes) {
-            if (!route.hasGateway() && route.matches(ip)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void updateLinkProperties(LinkProperties lp) {
-        if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
-            // TODO: figure out whether / how to cope with interface changes.
-            Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
-                    "' does not match: " + mInterfaceParams.name);
-            return;
-        }
-
-        mLinkProperties = new LinkProperties(lp);
-        Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
-
-        final List<RouteInfo> routes = mLinkProperties.getRoutes();
-        for (RouteInfo route : routes) {
-            if (route.hasGateway()) {
-                InetAddress gw = route.getGateway();
-                if (isOnLink(routes, gw)) {
-                    newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
-                }
-            }
-        }
-
-        for (InetAddress dns : lp.getDnsServers()) {
-            if (isOnLink(routes, dns)) {
-                newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
-            }
-        }
-
-        mNeighborWatchList = newNeighborWatchList;
-        if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
-    }
-
-    public void clearLinkProperties() {
-        mLinkProperties.clear();
-        mNeighborWatchList.clear();
-        if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
-    }
-
-    private void handleNeighborLost(NeighborEvent event) {
-        final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
-
-        InetAddress ip = null;
-        for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
-            // TODO: Consider using NeighborEvent#isValid() here; it's more
-            // strict but may interact badly if other entries are somehow in
-            // NUD_INCOMPLETE (say, during network attach).
-            if (entry.getValue().nudState != StructNdMsg.NUD_FAILED) continue;
-
-            ip = entry.getKey();
-            for (RouteInfo route : mLinkProperties.getRoutes()) {
-                if (ip.equals(route.getGateway())) {
-                    whatIfLp.removeRoute(route);
-                }
-            }
-
-            if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
-                // We should do this unconditionally, but alas we cannot: b/31827713.
-                whatIfLp.removeDnsServer(ip);
-            }
-        }
-
-        final boolean lostProvisioning =
-                (mLinkProperties.isIpv4Provisioned() && !whatIfLp.isIpv4Provisioned())
-                || (mLinkProperties.isIpv6Provisioned() && !whatIfLp.isIpv6Provisioned());
-
-        if (lostProvisioning) {
-            final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
-            Log.w(TAG, logMsg);
-            if (mCallback != null) {
-                // TODO: remove |ip| when the callback signature no longer has
-                // an InetAddress argument.
-                mCallback.notifyLost(ip, logMsg);
-            }
-        }
-        logNudFailed(lostProvisioning);
-    }
-
-    private boolean avoidingBadLinks() {
-        return !mUsingMultinetworkPolicyTracker || mCm.shouldAvoidBadWifi();
-    }
-
-    public void probeAll() {
-        final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
-
-        if (!ipProbeList.isEmpty()) {
-            // Keep the CPU awake long enough to allow all ARP/ND
-            // probes a reasonable chance at success. See b/23197666.
-            //
-            // The wakelock we use is (by default) refcounted, and this version
-            // of acquire(timeout) queues a release message to keep acquisitions
-            // and releases balanced.
-            mDependencies.acquireWakeLock(getProbeWakeLockDuration());
-        }
-
-        for (InetAddress ip : ipProbeList) {
-            final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceParams.index, ip);
-            mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
-                     ip.getHostAddress(), rval));
-            logEvent(IpReachabilityEvent.PROBE, rval);
-        }
-        mLastProbeTimeMs = SystemClock.elapsedRealtime();
-    }
-
-    private static long getProbeWakeLockDuration() {
-        // Ideally, this would be computed by examining the values of:
-        //
-        //     /proc/sys/net/ipv[46]/neigh/<ifname>/ucast_solicit
-        //
-        // and:
-        //
-        //     /proc/sys/net/ipv[46]/neigh/<ifname>/retrans_time_ms
-        //
-        // For now, just make some assumptions.
-        final long numUnicastProbes = 3;
-        final long retransTimeMs = 1000;
-        final long gracePeriodMs = 500;
-        return (numUnicastProbes * retransTimeMs) + gracePeriodMs;
-    }
-
-    private void logEvent(int probeType, int errorCode) {
-        int eventType = probeType | (errorCode & 0xff);
-        mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
-    }
-
-    private void logNudFailed(boolean lostProvisioning) {
-        long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
-        boolean isFromProbe = (duration < getProbeWakeLockDuration());
-        int eventType = nudFailureEventType(isFromProbe, lostProvisioning);
-        mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
-    }
-
-    /**
-     * Returns the NUD failure event type code corresponding to the given conditions.
-     */
-    private static int nudFailureEventType(boolean isFromProbe, boolean isProvisioningLost) {
-        if (isFromProbe) {
-            return isProvisioningLost ? PROVISIONING_LOST : NUD_FAILED;
-        } else {
-            return isProvisioningLost ? PROVISIONING_LOST_ORGANIC : NUD_FAILED_ORGANIC;
-        }
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
deleted file mode 100644
index 08c3f60..0000000
--- a/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
+++ /dev/null
@@ -1,435 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.util;
-
-import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_UDP;
-
-import static com.android.server.util.NetworkStackConstants.ARP_HWTYPE_ETHER;
-import static com.android.server.util.NetworkStackConstants.ARP_PAYLOAD_LEN;
-import static com.android.server.util.NetworkStackConstants.ARP_REPLY;
-import static com.android.server.util.NetworkStackConstants.ARP_REQUEST;
-import static com.android.server.util.NetworkStackConstants.DHCP4_CLIENT_PORT;
-import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN;
-import static com.android.server.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET;
-import static com.android.server.util.NetworkStackConstants.ETHER_HEADER_LEN;
-import static com.android.server.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
-import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_ARP;
-import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV4;
-import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV6;
-import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MIN_LENGTH;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
-import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_LEN;
-import static com.android.server.util.NetworkStackConstants.IPV4_DST_ADDR_OFFSET;
-import static com.android.server.util.NetworkStackConstants.IPV4_FLAGS_OFFSET;
-import static com.android.server.util.NetworkStackConstants.IPV4_FRAGMENT_MASK;
-import static com.android.server.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
-import static com.android.server.util.NetworkStackConstants.IPV4_IHL_MASK;
-import static com.android.server.util.NetworkStackConstants.IPV4_PROTOCOL_OFFSET;
-import static com.android.server.util.NetworkStackConstants.IPV4_SRC_ADDR_OFFSET;
-import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN;
-import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN;
-import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
-import static com.android.server.util.NetworkStackConstants.IPV6_SRC_ADDR_OFFSET;
-import static com.android.server.util.NetworkStackConstants.UDP_HEADER_LEN;
-
-import android.net.MacAddress;
-import android.net.dhcp.DhcpPacket;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.StringJoiner;
-
-
-/**
- * Critical connectivity packet summarizing class.
- *
- * Outputs short descriptions of ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
- *
- * @hide
- */
-public class ConnectivityPacketSummary {
-    private static final String TAG = ConnectivityPacketSummary.class.getSimpleName();
-
-    private final byte[] mHwAddr;
-    private final byte[] mBytes;
-    private final int mLength;
-    private final ByteBuffer mPacket;
-    private final String mSummary;
-
-    public static String summarize(MacAddress hwaddr, byte[] buffer) {
-        return summarize(hwaddr, buffer, buffer.length);
-    }
-
-    // Methods called herein perform some but by no means all error checking.
-    // They may throw runtime exceptions on malformed packets.
-    public static String summarize(MacAddress macAddr, byte[] buffer, int length) {
-        if ((macAddr == null) || (buffer == null)) return null;
-        length = Math.min(length, buffer.length);
-        return (new ConnectivityPacketSummary(macAddr, buffer, length)).toString();
-    }
-
-    private ConnectivityPacketSummary(MacAddress macAddr, byte[] buffer, int length) {
-        mHwAddr = macAddr.toByteArray();
-        mBytes = buffer;
-        mLength = Math.min(length, mBytes.length);
-        mPacket = ByteBuffer.wrap(mBytes, 0, mLength);
-        mPacket.order(ByteOrder.BIG_ENDIAN);
-
-        final StringJoiner sj = new StringJoiner(" ");
-        // TODO: support other link-layers, or even no link-layer header.
-        parseEther(sj);
-        mSummary = sj.toString();
-    }
-
-    public String toString() {
-        return mSummary;
-    }
-
-    private void parseEther(StringJoiner sj) {
-        if (mPacket.remaining() < ETHER_HEADER_LEN) {
-            sj.add("runt:").add(asString(mPacket.remaining()));
-            return;
-        }
-
-        mPacket.position(ETHER_SRC_ADDR_OFFSET);
-        final ByteBuffer srcMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN);
-        sj.add(ByteBuffer.wrap(mHwAddr).equals(srcMac) ? "TX" : "RX");
-        sj.add(getMacAddressString(srcMac));
-
-        mPacket.position(ETHER_DST_ADDR_OFFSET);
-        final ByteBuffer dstMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN);
-        sj.add(">").add(getMacAddressString(dstMac));
-
-        mPacket.position(ETHER_TYPE_OFFSET);
-        final int etherType = asUint(mPacket.getShort());
-        switch (etherType) {
-            case ETHER_TYPE_ARP:
-                sj.add("arp");
-                parseARP(sj);
-                break;
-            case ETHER_TYPE_IPV4:
-                sj.add("ipv4");
-                parseIPv4(sj);
-                break;
-            case ETHER_TYPE_IPV6:
-                sj.add("ipv6");
-                parseIPv6(sj);
-                break;
-            default:
-                // Unknown ether type.
-                sj.add("ethtype").add(asString(etherType));
-                break;
-        }
-    }
-
-    private void parseARP(StringJoiner sj) {
-        if (mPacket.remaining() < ARP_PAYLOAD_LEN) {
-            sj.add("runt:").add(asString(mPacket.remaining()));
-            return;
-        }
-
-        if (asUint(mPacket.getShort()) != ARP_HWTYPE_ETHER ||
-            asUint(mPacket.getShort()) != ETHER_TYPE_IPV4 ||
-            asUint(mPacket.get()) != ETHER_ADDR_LEN ||
-            asUint(mPacket.get()) != IPV4_ADDR_LEN) {
-            sj.add("unexpected header");
-            return;
-        }
-
-        final int opCode = asUint(mPacket.getShort());
-
-        final String senderHwAddr = getMacAddressString(mPacket);
-        final String senderIPv4 = getIPv4AddressString(mPacket);
-        getMacAddressString(mPacket);  // target hardware address, unused
-        final String targetIPv4 = getIPv4AddressString(mPacket);
-
-        if (opCode == ARP_REQUEST) {
-            sj.add("who-has").add(targetIPv4);
-        } else if (opCode == ARP_REPLY) {
-            sj.add("reply").add(senderIPv4).add(senderHwAddr);
-        } else {
-            sj.add("unknown opcode").add(asString(opCode));
-        }
-    }
-
-    private void parseIPv4(StringJoiner sj) {
-        if (!mPacket.hasRemaining()) {
-            sj.add("runt");
-            return;
-        }
-
-        final int startOfIpLayer = mPacket.position();
-        final int ipv4HeaderLength = (mPacket.get(startOfIpLayer) & IPV4_IHL_MASK) * 4;
-        if (mPacket.remaining() < ipv4HeaderLength ||
-            mPacket.remaining() < IPV4_HEADER_MIN_LEN) {
-            sj.add("runt:").add(asString(mPacket.remaining()));
-            return;
-        }
-        final int startOfTransportLayer = startOfIpLayer + ipv4HeaderLength;
-
-        mPacket.position(startOfIpLayer + IPV4_FLAGS_OFFSET);
-        final int flagsAndFragment = asUint(mPacket.getShort());
-        final boolean isFragment = (flagsAndFragment & IPV4_FRAGMENT_MASK) != 0;
-
-        mPacket.position(startOfIpLayer + IPV4_PROTOCOL_OFFSET);
-        final int protocol = asUint(mPacket.get());
-
-        mPacket.position(startOfIpLayer + IPV4_SRC_ADDR_OFFSET);
-        final String srcAddr = getIPv4AddressString(mPacket);
-
-        mPacket.position(startOfIpLayer + IPV4_DST_ADDR_OFFSET);
-        final String dstAddr = getIPv4AddressString(mPacket);
-
-        sj.add(srcAddr).add(">").add(dstAddr);
-
-        mPacket.position(startOfTransportLayer);
-        if (protocol == IPPROTO_UDP) {
-            sj.add("udp");
-            if (isFragment) sj.add("fragment");
-            else parseUDP(sj);
-        } else {
-            sj.add("proto").add(asString(protocol));
-            if (isFragment) sj.add("fragment");
-        }
-    }
-
-    private void parseIPv6(StringJoiner sj) {
-        if (mPacket.remaining() < IPV6_HEADER_LEN) {
-            sj.add("runt:").add(asString(mPacket.remaining()));
-            return;
-        }
-
-        final int startOfIpLayer = mPacket.position();
-
-        mPacket.position(startOfIpLayer + IPV6_PROTOCOL_OFFSET);
-        final int protocol = asUint(mPacket.get());
-
-        mPacket.position(startOfIpLayer + IPV6_SRC_ADDR_OFFSET);
-        final String srcAddr = getIPv6AddressString(mPacket);
-        final String dstAddr = getIPv6AddressString(mPacket);
-
-        sj.add(srcAddr).add(">").add(dstAddr);
-
-        mPacket.position(startOfIpLayer + IPV6_HEADER_LEN);
-        if (protocol == IPPROTO_ICMPV6) {
-            sj.add("icmp6");
-            parseICMPv6(sj);
-        } else {
-            sj.add("proto").add(asString(protocol));
-        }
-    }
-
-    private void parseICMPv6(StringJoiner sj) {
-        if (mPacket.remaining() < ICMPV6_HEADER_MIN_LEN) {
-            sj.add("runt:").add(asString(mPacket.remaining()));
-            return;
-        }
-
-        final int icmp6Type = asUint(mPacket.get());
-        final int icmp6Code = asUint(mPacket.get());
-        mPacket.getShort();  // checksum, unused
-
-        switch (icmp6Type) {
-            case ICMPV6_ROUTER_SOLICITATION:
-                sj.add("rs");
-                parseICMPv6RouterSolicitation(sj);
-                break;
-            case ICMPV6_ROUTER_ADVERTISEMENT:
-                sj.add("ra");
-                parseICMPv6RouterAdvertisement(sj);
-                break;
-            case ICMPV6_NEIGHBOR_SOLICITATION:
-                sj.add("ns");
-                parseICMPv6NeighborMessage(sj);
-                break;
-            case ICMPV6_NEIGHBOR_ADVERTISEMENT:
-                sj.add("na");
-                parseICMPv6NeighborMessage(sj);
-                break;
-            default:
-                sj.add("type").add(asString(icmp6Type));
-                sj.add("code").add(asString(icmp6Code));
-                break;
-        }
-    }
-
-    private void parseICMPv6RouterSolicitation(StringJoiner sj) {
-        final int RESERVED = 4;
-        if (mPacket.remaining() < RESERVED) {
-            sj.add("runt:").add(asString(mPacket.remaining()));
-            return;
-        }
-
-        mPacket.position(mPacket.position() + RESERVED);
-        parseICMPv6NeighborDiscoveryOptions(sj);
-    }
-
-    private void parseICMPv6RouterAdvertisement(StringJoiner sj) {
-        final int FLAGS_AND_TIMERS = 3 * 4;
-        if (mPacket.remaining() < FLAGS_AND_TIMERS) {
-            sj.add("runt:").add(asString(mPacket.remaining()));
-            return;
-        }
-
-        mPacket.position(mPacket.position() + FLAGS_AND_TIMERS);
-        parseICMPv6NeighborDiscoveryOptions(sj);
-    }
-
-    private void parseICMPv6NeighborMessage(StringJoiner sj) {
-        final int RESERVED = 4;
-        final int minReq = RESERVED + IPV6_ADDR_LEN;
-        if (mPacket.remaining() < minReq) {
-            sj.add("runt:").add(asString(mPacket.remaining()));
-            return;
-        }
-
-        mPacket.position(mPacket.position() + RESERVED);
-        sj.add(getIPv6AddressString(mPacket));
-        parseICMPv6NeighborDiscoveryOptions(sj);
-    }
-
-    private void parseICMPv6NeighborDiscoveryOptions(StringJoiner sj) {
-        // All ND options are TLV, where T is one byte and L is one byte equal
-        // to the length of T + L + V in units of 8 octets.
-        while (mPacket.remaining() >= ICMPV6_ND_OPTION_MIN_LENGTH) {
-            final int ndType = asUint(mPacket.get());
-            final int ndLength = asUint(mPacket.get());
-            final int ndBytes = ndLength * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR - 2;
-            if (ndBytes < 0 || ndBytes > mPacket.remaining()) {
-                sj.add("<malformed>");
-                break;
-            }
-            final int position = mPacket.position();
-
-            switch (ndType) {
-                    case ICMPV6_ND_OPTION_SLLA:
-                        sj.add("slla");
-                        sj.add(getMacAddressString(mPacket));
-                        break;
-                    case ICMPV6_ND_OPTION_TLLA:
-                        sj.add("tlla");
-                        sj.add(getMacAddressString(mPacket));
-                        break;
-                    case ICMPV6_ND_OPTION_MTU:
-                        sj.add("mtu");
-                        final short reserved = mPacket.getShort();
-                        sj.add(asString(mPacket.getInt()));
-                        break;
-                    default:
-                        // Skip.
-                        break;
-            }
-
-            mPacket.position(position + ndBytes);
-        }
-    }
-
-    private void parseUDP(StringJoiner sj) {
-        if (mPacket.remaining() < UDP_HEADER_LEN) {
-            sj.add("runt:").add(asString(mPacket.remaining()));
-            return;
-        }
-
-        final int previous = mPacket.position();
-        final int srcPort = asUint(mPacket.getShort());
-        final int dstPort = asUint(mPacket.getShort());
-        sj.add(asString(srcPort)).add(">").add(asString(dstPort));
-
-        mPacket.position(previous + UDP_HEADER_LEN);
-        if (srcPort == DHCP4_CLIENT_PORT || dstPort == DHCP4_CLIENT_PORT) {
-            sj.add("dhcp4");
-            parseDHCPv4(sj);
-        }
-    }
-
-    private void parseDHCPv4(StringJoiner sj) {
-        final DhcpPacket dhcpPacket;
-        try {
-            dhcpPacket = DhcpPacket.decodeFullPacket(mBytes, mLength, DhcpPacket.ENCAP_L2);
-            sj.add(dhcpPacket.toString());
-        } catch (DhcpPacket.ParseException e) {
-            sj.add("parse error: " + e);
-        }
-    }
-
-    private static String getIPv4AddressString(ByteBuffer ipv4) {
-        return getIpAddressString(ipv4, IPV4_ADDR_LEN);
-    }
-
-    private static String getIPv6AddressString(ByteBuffer ipv6) {
-        return getIpAddressString(ipv6, IPV6_ADDR_LEN);
-    }
-
-    private static String getIpAddressString(ByteBuffer ip, int byteLength) {
-        if (ip == null || ip.remaining() < byteLength) return "invalid";
-
-        byte[] bytes = new byte[byteLength];
-        ip.get(bytes, 0, byteLength);
-        try {
-            InetAddress addr = InetAddress.getByAddress(bytes);
-            return addr.getHostAddress();
-        } catch (UnknownHostException uhe) {
-            return "unknown";
-        }
-    }
-
-    private static String getMacAddressString(ByteBuffer mac) {
-        if (mac == null || mac.remaining() < ETHER_ADDR_LEN) return "invalid";
-
-        byte[] bytes = new byte[ETHER_ADDR_LEN];
-        mac.get(bytes, 0, bytes.length);
-        Object[] printableBytes = new Object[bytes.length];
-        int i = 0;
-        for (byte b : bytes) printableBytes[i++] = new Byte(b);
-
-        final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
-        return String.format(MAC48_FORMAT, printableBytes);
-    }
-
-    /**
-     * Convenience method to convert an int to a String.
-     */
-    public static String asString(int i) {
-        return Integer.toString(i);
-    }
-
-    /**
-     * Convenience method to read a byte as an unsigned int.
-     */
-    public static int asUint(byte b) {
-        return (b & 0xff);
-    }
-
-    /**
-     * Convenience method to read a short as an unsigned int.
-     */
-    public static int asUint(short s) {
-        return (s & 0xffff);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/util/DataStallUtils.java b/packages/NetworkStack/src/android/net/util/DataStallUtils.java
deleted file mode 100644
index b6dbeb1..0000000
--- a/packages/NetworkStack/src/android/net/util/DataStallUtils.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.util;
-
-/**
- * Collection of utilities for data stall.
- */
-public class DataStallUtils {
-    /**
-     * Detect data stall via using dns timeout counts.
-     */
-    public static final int DATA_STALL_EVALUATION_TYPE_DNS = 1;
-    // Default configuration values for data stall detection.
-    public static final int DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = 5;
-    public static final int DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS = 60 * 1000;
-    public static final int DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS = 30 * 60 * 1000;
-    /**
-     * The threshold value for the number of consecutive dns timeout events received to be a
-     * signal of data stall. The number of consecutive timeouts needs to be {@code >=} this
-     * threshold to be considered a data stall. Set the value to {@code <= 0} to disable. Note
-     * that the value should be {@code > 0} if the DNS data stall detection is enabled.
-     *
-     */
-    public static final String CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD =
-            "data_stall_consecutive_dns_timeout_threshold";
-
-    /**
-     * The minimal time interval in milliseconds for data stall reevaluation.
-     *
-     */
-    public static final String CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL =
-            "data_stall_min_evaluate_interval";
-
-    /**
-     * DNS timeouts older than this timeout (in milliseconds) are not considered for detecting
-     * a data stall.
-     *
-     */
-    public static final String CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD =
-            "data_stall_valid_dns_time_threshold";
-
-    /**
-     * Which data stall detection signal to use. This is a bitmask constructed by bitwise-or-ing
-     * (i.e. {@code |}) the DATA_STALL_EVALUATION_TYPE_* values.
-     *
-     * Type: int
-     * Valid values:
-     *   {@link #DATA_STALL_EVALUATION_TYPE_DNS} : Use dns as a signal.
-     */
-    public static final String CONFIG_DATA_STALL_EVALUATION_TYPE = "data_stall_evaluation_type";
-    public static final int DEFAULT_DATA_STALL_EVALUATION_TYPES = DATA_STALL_EVALUATION_TYPE_DNS;
-    // The default number of DNS events kept of the log kept for dns signal evaluation. Each event
-    // is represented by a {@link com.android.server.connectivity.NetworkMonitor#DnsResult} objects.
-    // It's also the size of array of {@link com.android.server.connectivity.nano.DnsEvent} kept in
-    // metrics. Note that increasing the size may cause statsd log buffer bust. Need to check the
-    // design in statsd when you try to increase the size.
-    public static final int DEFAULT_DNS_LOG_SIZE = 20;
-}
diff --git a/packages/NetworkStack/src/android/net/util/FdEventsReader.java b/packages/NetworkStack/src/android/net/util/FdEventsReader.java
deleted file mode 100644
index 1380ea7..0000000
--- a/packages/NetworkStack/src/android/net/util/FdEventsReader.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.util;
-
-import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
-import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.MessageQueue;
-import android.system.ErrnoException;
-import android.system.OsConstants;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-
-/**
- * This class encapsulates the mechanics of registering a file descriptor
- * with a thread's Looper and handling read events (and errors).
- *
- * Subclasses MUST implement createFd() and SHOULD override handlePacket(). They MAY override
- * onStop() and onStart().
- *
- * Subclasses can expect a call life-cycle like the following:
- *
- *     [1] when a client calls start(), createFd() is called, followed by the onStart() hook if all
- *         goes well. Implementations may override onStart() for additional initialization.
- *
- *     [2] yield, waiting for read event or error notification:
- *
- *             [a] readPacket() && handlePacket()
- *
- *             [b] if (no error):
- *                     goto 2
- *                 else:
- *                     goto 3
- *
- *     [3] when a client calls stop(), the onStop() hook is called (unless already stopped or never
- *         started). Implementations may override onStop() for additional cleanup.
- *
- * The packet receive buffer is recycled on every read call, so subclasses
- * should make any copies they would like inside their handlePacket()
- * implementation.
- *
- * All public methods MUST only be called from the same thread with which
- * the Handler constructor argument is associated.
- *
- * @param <BufferType> the type of the buffer used to read data.
- * @hide
- */
-public abstract class FdEventsReader<BufferType> {
-    private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
-    private static final int UNREGISTER_THIS_FD = 0;
-
-    @NonNull
-    private final Handler mHandler;
-    @NonNull
-    private final MessageQueue mQueue;
-    @NonNull
-    private final BufferType mBuffer;
-    @Nullable
-    private FileDescriptor mFd;
-    private long mPacketsReceived;
-
-    protected static void closeFd(FileDescriptor fd) {
-        try {
-            SocketUtils.closeSocket(fd);
-        } catch (IOException ignored) {
-        }
-    }
-
-    protected FdEventsReader(@NonNull Handler h, @NonNull BufferType buffer) {
-        mHandler = h;
-        mQueue = mHandler.getLooper().getQueue();
-        mBuffer = buffer;
-    }
-
-    /** Start this FdEventsReader. */
-    public void start() {
-        if (onCorrectThread()) {
-            createAndRegisterFd();
-        } else {
-            mHandler.post(() -> {
-                logError("start() called from off-thread", null);
-                createAndRegisterFd();
-            });
-        }
-    }
-
-    /** Stop this FdEventsReader and destroy the file descriptor. */
-    public void stop() {
-        if (onCorrectThread()) {
-            unregisterAndDestroyFd();
-        } else {
-            mHandler.post(() -> {
-                logError("stop() called from off-thread", null);
-                unregisterAndDestroyFd();
-            });
-        }
-    }
-
-    @NonNull
-    public Handler getHandler() {
-        return mHandler;
-    }
-
-    protected abstract int recvBufSize(@NonNull BufferType buffer);
-
-    /** Returns the size of the receive buffer. */
-    public int recvBufSize() {
-        return recvBufSize(mBuffer);
-    }
-
-    /**
-     * Get the number of successful calls to {@link #readPacket(FileDescriptor, Object)}.
-     *
-     * <p>A call was successful if {@link #readPacket(FileDescriptor, Object)} returned a value > 0.
-     */
-    public final long numPacketsReceived() {
-        return mPacketsReceived;
-    }
-
-    /**
-     * Subclasses MUST create the listening socket here, including setting all desired socket
-     * options, interface or address/port binding, etc. The socket MUST be created nonblocking.
-     */
-    @Nullable
-    protected abstract FileDescriptor createFd();
-
-    /**
-     * Implementations MUST return the bytes read or throw an Exception.
-     *
-     * <p>The caller may throw a {@link ErrnoException} with {@link OsConstants#EAGAIN} or
-     * {@link OsConstants#EINTR}, in which case {@link FdEventsReader} will ignore the buffer
-     * contents and respectively wait for further input or retry the read immediately. For all other
-     * exceptions, the {@link FdEventsReader} will be stopped with no more interactions with this
-     * method.
-     */
-    protected abstract int readPacket(@NonNull FileDescriptor fd, @NonNull BufferType buffer)
-            throws Exception;
-
-    /**
-     * Called by the main loop for every packet.  Any desired copies of
-     * |recvbuf| should be made in here, as the underlying byte array is
-     * reused across all reads.
-     */
-    protected void handlePacket(@NonNull BufferType recvbuf, int length) {}
-
-    /**
-     * Called by the main loop to log errors.  In some cases |e| may be null.
-     */
-    protected void logError(@NonNull String msg, @Nullable Exception e) {}
-
-    /**
-     * Called by start(), if successful, just prior to returning.
-     */
-    protected void onStart() {}
-
-    /**
-     * Called by stop() just prior to returning.
-     */
-    protected void onStop() {}
-
-    private void createAndRegisterFd() {
-        if (mFd != null) return;
-
-        try {
-            mFd = createFd();
-        } catch (Exception e) {
-            logError("Failed to create socket: ", e);
-            closeFd(mFd);
-            mFd = null;
-        }
-
-        if (mFd == null) return;
-
-        mQueue.addOnFileDescriptorEventListener(
-                mFd,
-                FD_EVENTS,
-                (fd, events) -> {
-                    // Always call handleInput() so read/recvfrom are given
-                    // a proper chance to encounter a meaningful errno and
-                    // perhaps log a useful error message.
-                    if (!isRunning() || !handleInput()) {
-                        unregisterAndDestroyFd();
-                        return UNREGISTER_THIS_FD;
-                    }
-                    return FD_EVENTS;
-                });
-        onStart();
-    }
-
-    private boolean isRunning() {
-        return (mFd != null) && mFd.valid();
-    }
-
-    // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
-    private boolean handleInput() {
-        while (isRunning()) {
-            final int bytesRead;
-
-            try {
-                bytesRead = readPacket(mFd, mBuffer);
-                if (bytesRead < 1) {
-                    if (isRunning()) logError("Socket closed, exiting", null);
-                    break;
-                }
-                mPacketsReceived++;
-            } catch (ErrnoException e) {
-                if (e.errno == OsConstants.EAGAIN) {
-                    // We've read everything there is to read this time around.
-                    return true;
-                } else if (e.errno == OsConstants.EINTR) {
-                    continue;
-                } else {
-                    if (isRunning()) logError("readPacket error: ", e);
-                    break;
-                }
-            } catch (Exception e) {
-                if (isRunning()) logError("readPacket error: ", e);
-                break;
-            }
-
-            try {
-                handlePacket(mBuffer, bytesRead);
-            } catch (Exception e) {
-                logError("handlePacket error: ", e);
-                break;
-            }
-        }
-
-        return false;
-    }
-
-    private void unregisterAndDestroyFd() {
-        if (mFd == null) return;
-
-        mQueue.removeOnFileDescriptorEventListener(mFd);
-        closeFd(mFd);
-        mFd = null;
-        onStop();
-    }
-
-    private boolean onCorrectThread() {
-        return (mHandler.getLooper() == Looper.myLooper());
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
deleted file mode 100644
index 541f9d8..0000000
--- a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.SparseArray;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.util.List;
-import java.util.function.Predicate;
-
-/**
- * Collection of utilities for the network stack.
- */
-public class NetworkStackUtils {
-    // TODO: Refer to DeviceConfig definition.
-    public static final String NAMESPACE_CONNECTIVITY = "connectivity";
-
-    /**
-     * A list of captive portal detection specifications used in addition to the fallback URLs.
-     * Each spec has the format url@@/@@statusCodeRegex@@/@@contentRegex. Specs are separated
-     * by "@@,@@".
-     */
-    public static final String CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS =
-            "captive_portal_fallback_probe_specs";
-
-    /**
-     * A comma separated list of URLs used for captive portal detection in addition to the
-     * fallback HTTP url associated with the CAPTIVE_PORTAL_FALLBACK_URL settings.
-     */
-    public static final String CAPTIVE_PORTAL_OTHER_FALLBACK_URLS =
-            "captive_portal_other_fallback_urls";
-
-    /**
-     * Which User-Agent string to use in the header of the captive portal detection probes.
-     * The User-Agent field is unset when this setting has no value (HttpUrlConnection default).
-     */
-    public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent";
-
-    /**
-     * Whether to use HTTPS for network validation. This is enabled by default and the setting
-     * needs to be set to 0 to disable it. This setting is a misnomer because captive portals
-     * don't actually use HTTPS, but it's consistent with the other settings.
-     */
-    public static final String CAPTIVE_PORTAL_USE_HTTPS = "captive_portal_use_https";
-
-    /**
-     * The URL used for HTTPS captive portal detection upon a new connection.
-     * A 204 response code from the server is used for validation.
-     */
-    public static final String CAPTIVE_PORTAL_HTTPS_URL = "captive_portal_https_url";
-
-    /**
-     * The URL used for HTTP captive portal detection upon a new connection.
-     * A 204 response code from the server is used for validation.
-     */
-    public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
-
-    /**
-     * The URL used for fallback HTTP captive portal detection when previous HTTP
-     * and HTTPS captive portal detection attemps did not return a conclusive answer.
-     */
-    public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
-
-    /**
-     * What to do when connecting a network that presents a captive portal.
-     * Must be one of the CAPTIVE_PORTAL_MODE_* constants above.
-     *
-     * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
-     */
-    public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
-
-    /**
-     * Don't attempt to detect captive portals.
-     */
-    public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0;
-
-    /**
-     * When detecting a captive portal, display a notification that
-     * prompts the user to sign in.
-     */
-    public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1;
-
-    /**
-     * When detecting a captive portal, immediately disconnect from the
-     * network and do not reconnect to that network in the future.
-     */
-    public static final int CAPTIVE_PORTAL_MODE_AVOID = 2;
-
-    static {
-        System.loadLibrary("networkstackutilsjni");
-    }
-
-    /**
-     * @return True if the array is null or 0-length.
-     */
-    public static <T> boolean isEmpty(T[] array) {
-        return array == null || array.length == 0;
-    }
-
-    /**
-     * Close a socket, ignoring any exception while closing.
-     */
-    public static void closeSocketQuietly(FileDescriptor fd) {
-        try {
-            SocketUtils.closeSocket(fd);
-        } catch (IOException ignored) {
-        }
-    }
-
-    /**
-     * Returns an int array from the given Integer list.
-     */
-    public static int[] convertToIntArray(@NonNull List<Integer> list) {
-        int[] array = new int[list.size()];
-        for (int i = 0; i < list.size(); i++) {
-            array[i] = list.get(i);
-        }
-        return array;
-    }
-
-    /**
-     * Returns a long array from the given long list.
-     */
-    public static long[] convertToLongArray(@NonNull List<Long> list) {
-        long[] array = new long[list.size()];
-        for (int i = 0; i < list.size(); i++) {
-            array[i] = list.get(i);
-        }
-        return array;
-    }
-
-    /**
-     * @return True if there exists at least one element in the sparse array for which
-     * condition {@code predicate}
-     */
-    public static <T> boolean any(SparseArray<T> array, Predicate<T> predicate) {
-        for (int i = 0; i < array.size(); ++i) {
-            if (predicate.test(array.valueAt(i))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
-     * @param namespace The namespace containing the property to look up.
-     * @param name The name of the property to look up.
-     * @param defaultValue The value to return if the property does not exist or has no valid value.
-     * @return the corresponding value, or defaultValue if none exists.
-     */
-    @Nullable
-    public static String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name,
-            @Nullable String defaultValue) {
-        // TODO: Link to DeviceConfig API once it is ready.
-        return defaultValue;
-    }
-
-    /**
-     * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
-     * @param namespace The namespace containing the property to look up.
-     * @param name The name of the property to look up.
-     * @param defaultValue The value to return if the property does not exist or has no non-null
-     *                     value.
-     * @return the corresponding value, or defaultValue if none exists.
-     */
-    public static int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name,
-            int defaultValue) {
-        String value = getDeviceConfigProperty(namespace, name, null /* defaultValue */);
-        try {
-            return (value != null) ? Integer.parseInt(value) : defaultValue;
-        } catch (NumberFormatException e) {
-            return defaultValue;
-        }
-    }
-
-    /**
-     * Attaches a socket filter that accepts DHCP packets to the given socket.
-     */
-    public static native void attachDhcpFilter(FileDescriptor fd) throws SocketException;
-
-    /**
-     * Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket.
-     * @param fd the socket's {@link FileDescriptor}.
-     * @param packetType the hardware address type, one of ARPHRD_*.
-     */
-    public static native void attachRaFilter(FileDescriptor fd, int packetType)
-            throws SocketException;
-
-    /**
-     * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity.
-     *
-     * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges.
-     *
-     * @param fd the socket's {@link FileDescriptor}.
-     * @param packetType the hardware address type, one of ARPHRD_*.
-     */
-    public static native void attachControlPacketFilter(FileDescriptor fd, int packetType)
-            throws SocketException;
-
-    /**
-     * Add an entry into the ARP cache.
-     */
-    public static void addArpEntry(Inet4Address ipv4Addr, android.net.MacAddress ethAddr,
-            String ifname, FileDescriptor fd) throws IOException {
-        addArpEntry(ethAddr.toByteArray(), ipv4Addr.getAddress(), ifname, fd);
-    }
-
-    private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname,
-            FileDescriptor fd) throws IOException;
-
-    /**
-     * Return IP address and port in a string format.
-     */
-    public static String addressAndPortToString(InetAddress address, int port) {
-        return String.format(
-                (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d",
-                        address.getHostAddress(), port);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java
deleted file mode 100644
index 4aec6b6..0000000
--- a/packages/NetworkStack/src/android/net/util/PacketReader.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.util;
-
-import static java.lang.Math.max;
-
-import android.os.Handler;
-import android.system.Os;
-
-import java.io.FileDescriptor;
-
-/**
- * Specialization of {@link FdEventsReader} that reads packets into a byte array.
- *
- * TODO: rename this class to something more correctly descriptive (something
- * like [or less horrible than] FdReadEventsHandler?).
- *
- * @hide
- */
-public abstract class PacketReader extends FdEventsReader<byte[]> {
-
-    public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;
-
-    protected PacketReader(Handler h) {
-        this(h, DEFAULT_RECV_BUF_SIZE);
-    }
-
-    protected PacketReader(Handler h, int recvBufSize) {
-        super(h, new byte[max(recvBufSize, DEFAULT_RECV_BUF_SIZE)]);
-    }
-
-    @Override
-    protected final int recvBufSize(byte[] buffer) {
-        return buffer.length;
-    }
-
-    /**
-     * Subclasses MAY override this to change the default read() implementation
-     * in favour of, say, recvfrom().
-     *
-     * Implementations MUST return the bytes read or throw an Exception.
-     */
-    @Override
-    protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
-        return Os.read(fd, packetBuffer, 0, packetBuffer.length);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/util/SharedLog.java b/packages/NetworkStack/src/android/net/util/SharedLog.java
deleted file mode 100644
index 4fabf10..0000000
--- a/packages/NetworkStack/src/android/net/util/SharedLog.java
+++ /dev/null
@@ -1,201 +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.net.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.StringJoiner;
-
-
-/**
- * Class to centralize logging functionality for tethering.
- *
- * All access to class methods other than dump() must be on the same thread.
- *
- * @hide
- */
-public class SharedLog {
-    private static final int DEFAULT_MAX_RECORDS = 500;
-    private static final String COMPONENT_DELIMITER = ".";
-
-    private enum Category {
-        NONE,
-        ERROR,
-        MARK,
-        WARN,
-    };
-
-    private final LocalLog mLocalLog;
-    // The tag to use for output to the system log. This is not output to the
-    // LocalLog because that would be redundant.
-    private final String mTag;
-    // The component (or subcomponent) of a system that is sharing this log.
-    // This can grow in depth if components call forSubComponent() to obtain
-    // their SharedLog instance. The tag is not included in the component for
-    // brevity.
-    private final String mComponent;
-
-    public SharedLog(String tag) {
-        this(DEFAULT_MAX_RECORDS, tag);
-    }
-
-    public SharedLog(int maxRecords, String tag) {
-        this(new LocalLog(maxRecords), tag, tag);
-    }
-
-    private SharedLog(LocalLog localLog, String tag, String component) {
-        mLocalLog = localLog;
-        mTag = tag;
-        mComponent = component;
-    }
-
-    public String getTag() {
-        return mTag;
-    }
-
-    /**
-     * Create a SharedLog based on this log with an additional component prefix on each logged line.
-     */
-    public SharedLog forSubComponent(String component) {
-        if (!isRootLogInstance()) {
-            component = mComponent + COMPONENT_DELIMITER + component;
-        }
-        return new SharedLog(mLocalLog, mTag, component);
-    }
-
-    /**
-     * Dump the contents of this log.
-     *
-     * <p>This method may be called on any thread.
-     */
-    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        mLocalLog.readOnlyLocalLog().dump(fd, writer, args);
-    }
-
-    //////
-    // Methods that both log an entry and emit it to the system log.
-    //////
-
-    /**
-     * Log an error due to an exception. This does not include the exception stacktrace.
-     *
-     * <p>The log entry will be also added to the system log.
-     * @see #e(String, Throwable)
-     */
-    public void e(Exception e) {
-        Log.e(mTag, record(Category.ERROR, e.toString()));
-    }
-
-    /**
-     * Log an error message.
-     *
-     * <p>The log entry will be also added to the system log.
-     */
-    public void e(String msg) {
-        Log.e(mTag, record(Category.ERROR, msg));
-    }
-
-    /**
-     * Log an error due to an exception, with the exception stacktrace if provided.
-     *
-     * <p>The error and exception message appear in the shared log, but the stacktrace is only
-     * logged in general log output (logcat). The log entry will be also added to the system log.
-     */
-    public void e(@NonNull String msg, @Nullable Throwable exception) {
-        if (exception == null) {
-            e(msg);
-            return;
-        }
-        Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception);
-    }
-
-    /**
-     * Log an informational message.
-     *
-     * <p>The log entry will be also added to the system log.
-     */
-    public void i(String msg) {
-        Log.i(mTag, record(Category.NONE, msg));
-    }
-
-    /**
-     * Log a warning message.
-     *
-     * <p>The log entry will be also added to the system log.
-     */
-    public void w(String msg) {
-        Log.w(mTag, record(Category.WARN, msg));
-    }
-
-    //////
-    // Methods that only log an entry (and do NOT emit to the system log).
-    //////
-
-    /**
-     * Log a general message to be only included in the in-memory log.
-     *
-     * <p>The log entry will *not* be added to the system log.
-     */
-    public void log(String msg) {
-        record(Category.NONE, msg);
-    }
-
-    /**
-     * Log a general, formatted message to be only included in the in-memory log.
-     *
-     * <p>The log entry will *not* be added to the system log.
-     * @see String#format(String, Object...)
-     */
-    public void logf(String fmt, Object... args) {
-        log(String.format(fmt, args));
-    }
-
-    /**
-     * Log a message with MARK level.
-     *
-     * <p>The log entry will *not* be added to the system log.
-     */
-    public void mark(String msg) {
-        record(Category.MARK, msg);
-    }
-
-    private String record(Category category, String msg) {
-        final String entry = logLine(category, msg);
-        mLocalLog.log(entry);
-        return entry;
-    }
-
-    private String logLine(Category category, String msg) {
-        final StringJoiner sj = new StringJoiner(" ");
-        if (!isRootLogInstance()) sj.add("[" + mComponent + "]");
-        if (category != Category.NONE) sj.add(category.toString());
-        return sj.add(msg).toString();
-    }
-
-    // Check whether this SharedLog instance is nominally the top level in
-    // a potential hierarchy of shared logs (the root of a tree),
-    // or is a subcomponent within the hierarchy.
-    private boolean isRootLogInstance() {
-        return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag);
-    }
-}
diff --git a/packages/NetworkStack/src/android/net/util/Stopwatch.java b/packages/NetworkStack/src/android/net/util/Stopwatch.java
deleted file mode 100644
index c316699..0000000
--- a/packages/NetworkStack/src/android/net/util/Stopwatch.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.util;
-
-import android.os.SystemClock;
-
-
-/**
- * @hide
- */
-public class Stopwatch {
-    private long mStartTimeMs;
-    private long mStopTimeMs;
-
-    public boolean isStarted() {
-        return (mStartTimeMs > 0);
-    }
-
-    public boolean isStopped() {
-        return (mStopTimeMs > 0);
-    }
-
-    public boolean isRunning() {
-        return (isStarted() && !isStopped());
-    }
-
-    /**
-     * Start the Stopwatch.
-     */
-    public Stopwatch start() {
-        if (!isStarted()) {
-            mStartTimeMs = SystemClock.elapsedRealtime();
-        }
-        return this;
-    }
-
-    /**
-     * Stop the Stopwatch.
-     * @return the total time recorded, in milliseconds, or 0 if not started.
-     */
-    public long stop() {
-        if (isRunning()) {
-            mStopTimeMs = SystemClock.elapsedRealtime();
-        }
-        // Return either the delta after having stopped, or 0.
-        return (mStopTimeMs - mStartTimeMs);
-    }
-
-    /**
-     * Return the total time recorded to date, in milliseconds.
-     * If the Stopwatch is not running, returns the same value as stop(),
-     * i.e. either the total time recorded before stopping or 0.
-     */
-    public long lap() {
-        if (isRunning()) {
-            return (SystemClock.elapsedRealtime() - mStartTimeMs);
-        } else {
-            return stop();
-        }
-    }
-
-    /**
-     * Reset the Stopwatch. It will be stopped when this method returns.
-     */
-    public void reset() {
-        mStartTimeMs = 0;
-        mStopTimeMs = 0;
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/networkstack/metrics/DataStallDetectionStats.java b/packages/NetworkStack/src/com/android/networkstack/metrics/DataStallDetectionStats.java
deleted file mode 100644
index 2523ecd..0000000
--- a/packages/NetworkStack/src/com/android/networkstack/metrics/DataStallDetectionStats.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.networkstack.metrics;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.util.NetworkStackUtils;
-import android.net.wifi.WifiInfo;
-
-import com.android.internal.util.HexDump;
-import com.android.server.connectivity.nano.CellularData;
-import com.android.server.connectivity.nano.DataStallEventProto;
-import com.android.server.connectivity.nano.DnsEvent;
-import com.android.server.connectivity.nano.WifiData;
-
-import com.google.protobuf.nano.MessageNano;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Class to record the stats of detection level information for data stall.
- *
- * @hide
- */
-public final class DataStallDetectionStats {
-    private static final int UNKNOWN_SIGNAL_STRENGTH = -1;
-    @NonNull
-    final byte[] mCellularInfo;
-    @NonNull
-    final byte[] mWifiInfo;
-    @NonNull
-    final byte[] mDns;
-    final int mEvaluationType;
-    final int mNetworkType;
-
-    public DataStallDetectionStats(@Nullable byte[] cell, @Nullable byte[] wifi,
-                @NonNull int[] returnCode, @NonNull long[] dnsTime, int evalType, int netType) {
-        mCellularInfo = emptyCellDataIfNull(cell);
-        mWifiInfo = emptyWifiInfoIfNull(wifi);
-
-        DnsEvent dns = new DnsEvent();
-        dns.dnsReturnCode = returnCode;
-        dns.dnsTime = dnsTime;
-        mDns = MessageNano.toByteArray(dns);
-        mEvaluationType = evalType;
-        mNetworkType = netType;
-    }
-
-    private byte[] emptyCellDataIfNull(@Nullable byte[] cell) {
-        if (cell != null) return cell;
-
-        CellularData data  = new CellularData();
-        data.ratType = DataStallEventProto.RADIO_TECHNOLOGY_UNKNOWN;
-        data.networkMccmnc = "";
-        data.simMccmnc = "";
-        data.signalStrength = UNKNOWN_SIGNAL_STRENGTH;
-        return MessageNano.toByteArray(data);
-    }
-
-    private byte[] emptyWifiInfoIfNull(@Nullable byte[] wifi) {
-        if (wifi != null) return wifi;
-
-        WifiData data = new WifiData();
-        data.wifiBand = DataStallEventProto.AP_BAND_UNKNOWN;
-        data.signalStrength = UNKNOWN_SIGNAL_STRENGTH;
-        return MessageNano.toByteArray(data);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("type: ").append(mNetworkType)
-          .append(", evaluation type: ")
-          .append(mEvaluationType)
-          .append(", wifi info: ")
-          .append(HexDump.toHexString(mWifiInfo))
-          .append(", cell info: ")
-          .append(HexDump.toHexString(mCellularInfo))
-          .append(", dns: ")
-          .append(HexDump.toHexString(mDns));
-        return sb.toString();
-    }
-
-    @Override
-    public boolean equals(@Nullable final Object o) {
-        if (!(o instanceof DataStallDetectionStats)) return false;
-        final DataStallDetectionStats other = (DataStallDetectionStats) o;
-        return (mNetworkType == other.mNetworkType)
-            && (mEvaluationType == other.mEvaluationType)
-            && Arrays.equals(mWifiInfo, other.mWifiInfo)
-            && Arrays.equals(mCellularInfo, other.mCellularInfo)
-            && Arrays.equals(mDns, other.mDns);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mNetworkType, mEvaluationType, mWifiInfo, mCellularInfo, mDns);
-    }
-
-    /**
-     * Utility to create an instance of {@Link DataStallDetectionStats}
-     *
-     * @hide
-     */
-    public static class Builder {
-        @Nullable
-        private byte[] mCellularInfo;
-        @Nullable
-        private byte[] mWifiInfo;
-        @NonNull
-        private final List<Integer> mDnsReturnCode = new ArrayList<Integer>();
-        @NonNull
-        private final List<Long> mDnsTimeStamp = new ArrayList<Long>();
-        private int mEvaluationType;
-        private int mNetworkType;
-
-        /**
-         * Add a dns event into Builder.
-         *
-         * @param code the return code of the dns event.
-         * @param timeMs the elapsedRealtime in ms that the the dns event was received from netd.
-         * @return {@code this} {@link Builder} instance.
-         */
-        public Builder addDnsEvent(int code, long timeMs) {
-            mDnsReturnCode.add(code);
-            mDnsTimeStamp.add(timeMs);
-            return this;
-        }
-
-        /**
-         * Set the dns evaluation type into Builder.
-         *
-         * @param type the return code of the dns event.
-         * @return {@code this} {@link Builder} instance.
-         */
-        public Builder setEvaluationType(int type) {
-            mEvaluationType = type;
-            return this;
-        }
-
-        /**
-         * Set the network type into Builder.
-         *
-         * @param type the network type of the logged network.
-         * @return {@code this} {@link Builder} instance.
-         */
-        public Builder setNetworkType(int type) {
-            mNetworkType = type;
-            return this;
-        }
-
-        /**
-         * Set the wifi data into Builder.
-         *
-         * @param info a {@link WifiInfo} of the connected wifi network.
-         * @return {@code this} {@link Builder} instance.
-         */
-        public Builder setWiFiData(@Nullable final WifiInfo info) {
-            WifiData data = new WifiData();
-            data.wifiBand = getWifiBand(info);
-            data.signalStrength = (info != null) ? info.getRssi() : UNKNOWN_SIGNAL_STRENGTH;
-            mWifiInfo = MessageNano.toByteArray(data);
-            return this;
-        }
-
-        private static int getWifiBand(@Nullable final WifiInfo info) {
-            if (info == null) return DataStallEventProto.AP_BAND_UNKNOWN;
-
-            int freq = info.getFrequency();
-            // Refer to ScanResult.is5GHz() and ScanResult.is24GHz().
-            if (freq > 4900 && freq < 5900) {
-                return DataStallEventProto.AP_BAND_5GHZ;
-            } else if (freq > 2400 && freq < 2500) {
-                return DataStallEventProto.AP_BAND_2GHZ;
-            } else {
-                return DataStallEventProto.AP_BAND_UNKNOWN;
-            }
-        }
-
-        /**
-         * Set the cellular data into Builder.
-         *
-         * @param radioType the radio technology of the logged cellular network.
-         * @param roaming a boolean indicates if logged cellular network is roaming or not.
-         * @param networkMccmnc the mccmnc of the camped network.
-         * @param simMccmnc the mccmnc of the sim.
-         * @return {@code this} {@link Builder} instance.
-         */
-        public Builder setCellData(int radioType, boolean roaming,
-                @NonNull String networkMccmnc, @NonNull String simMccmnc, int ss) {
-            CellularData data  = new CellularData();
-            data.ratType = radioType;
-            data.isRoaming = roaming;
-            data.networkMccmnc = networkMccmnc;
-            data.simMccmnc = simMccmnc;
-            data.signalStrength = ss;
-            mCellularInfo = MessageNano.toByteArray(data);
-            return this;
-        }
-
-        /**
-         * Create a new {@Link DataStallDetectionStats}.
-         */
-        public DataStallDetectionStats build() {
-            return new DataStallDetectionStats(mCellularInfo, mWifiInfo,
-                    NetworkStackUtils.convertToIntArray(mDnsReturnCode),
-                    NetworkStackUtils.convertToLongArray(mDnsTimeStamp),
-                    mEvaluationType, mNetworkType);
-        }
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/networkstack/metrics/DataStallStatsUtils.java b/packages/NetworkStack/src/com/android/networkstack/metrics/DataStallStatsUtils.java
deleted file mode 100644
index 9308901..0000000
--- a/packages/NetworkStack/src/com/android/networkstack/metrics/DataStallStatsUtils.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.networkstack.metrics;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.captiveportal.CaptivePortalProbeResult;
-import android.util.Log;
-
-import com.android.internal.util.HexDump;
-import com.android.server.connectivity.nano.DataStallEventProto;
-
-/**
- * Collection of utilities for data stall metrics.
- *
- * To see if the logs are properly sent to statsd, execute following command.
- *
- * $ adb shell cmd stats print-logs
- * $ adb logcat | grep statsd  OR $ adb logcat -b stats
- *
- * @hide
- */
-public class DataStallStatsUtils {
-    private static final String TAG = DataStallStatsUtils.class.getSimpleName();
-    private static final boolean DBG = false;
-
-    private static int probeResultToEnum(@Nullable final CaptivePortalProbeResult result) {
-        if (result == null) return DataStallEventProto.INVALID;
-
-        if (result.isSuccessful()) {
-            return DataStallEventProto.VALID;
-        } else if (result.isPortal()) {
-            return DataStallEventProto.PORTAL;
-        } else if (result.isPartialConnectivity()) {
-            return DataStallEventProto.PARTIAL;
-        } else {
-            return DataStallEventProto.INVALID;
-        }
-    }
-
-    /**
-     * Write the metric to {@link StatsLog}.
-     */
-    public static void write(@NonNull final DataStallDetectionStats stats,
-            @NonNull final CaptivePortalProbeResult result) {
-        int validationResult = probeResultToEnum(result);
-        if (DBG) {
-            Log.d(TAG, "write: " + stats + " with result: " + validationResult
-                    + ", dns: " + HexDump.toHexString(stats.mDns));
-        }
-        NetworkStackStatsLog.write(NetworkStackStatsLog.DATA_STALL_EVENT,
-                stats.mEvaluationType,
-                validationResult,
-                stats.mNetworkType,
-                stats.mWifiInfo,
-                stats.mCellularInfo,
-                stats.mDns);
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/networkstack/util/DnsUtils.java b/packages/NetworkStack/src/com/android/networkstack/util/DnsUtils.java
deleted file mode 100644
index 4767d55..0000000
--- a/packages/NetworkStack/src/com/android/networkstack/util/DnsUtils.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.networkstack.util;
-
-import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP;
-import static android.net.DnsResolver.TYPE_A;
-import static android.net.DnsResolver.TYPE_AAAA;
-
-import android.annotation.NonNull;
-import android.net.DnsResolver;
-import android.net.Network;
-import android.net.TrafficStats;
-import android.util.Log;
-
-import com.android.internal.util.TrafficStatsConstants;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Collection of utilities for dns query.
- */
-public class DnsUtils {
-    // Decide what queries to make depending on what IP addresses are on the system.
-    public static final int TYPE_ADDRCONFIG = -1;
-    private static final String TAG = DnsUtils.class.getSimpleName();
-
-    /**
-     * Return both A and AAAA query results regardless the ip address type of the giving network.
-     * Used for probing in NetworkMonitor.
-     */
-    @NonNull
-    public static InetAddress[] getAllByName(@NonNull final DnsResolver dnsResolver,
-            @NonNull final Network network, @NonNull String host, int timeout)
-            throws UnknownHostException {
-        final List<InetAddress> result = new ArrayList<InetAddress>();
-
-        try {
-            result.addAll(Arrays.asList(
-                    getAllByName(dnsResolver, network, host, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
-                    timeout)));
-        } catch (UnknownHostException e) {
-            // Might happen if the host is v4-only, still need to query TYPE_A
-        }
-        try {
-            result.addAll(Arrays.asList(
-                    getAllByName(dnsResolver, network, host, TYPE_A, FLAG_NO_CACHE_LOOKUP,
-                    timeout)));
-        } catch (UnknownHostException e) {
-            // Might happen if the host is v6-only, still need to return AAAA answers
-        }
-        if (result.size() == 0) {
-            throw new UnknownHostException(host);
-        }
-        return result.toArray(new InetAddress[0]);
-    }
-
-    /**
-     * Return dns query result based on the given QueryType(TYPE_A, TYPE_AAAA) or TYPE_ADDRCONFIG.
-     * Used for probing in NetworkMonitor.
-     */
-    @NonNull
-    public static InetAddress[] getAllByName(@NonNull final DnsResolver dnsResolver,
-            @NonNull final Network network, @NonNull final String host, int type, int flag,
-            int timeoutMs) throws UnknownHostException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final AtomicReference<List<InetAddress>> resultRef = new AtomicReference<>();
-
-        final DnsResolver.Callback<List<InetAddress>> callback =
-                new DnsResolver.Callback<List<InetAddress>>() {
-            @Override
-            public void onAnswer(List<InetAddress> answer, int rcode) {
-                if (rcode == 0) {
-                    resultRef.set(answer);
-                }
-                latch.countDown();
-            }
-
-            @Override
-            public void onError(@NonNull DnsResolver.DnsException e) {
-                Log.d(TAG, "DNS error resolving " + host + ": " + e.getMessage());
-                latch.countDown();
-            }
-        };
-        final int oldTag = TrafficStats.getAndSetThreadStatsTag(
-                TrafficStatsConstants.TAG_SYSTEM_PROBE);
-
-        if (type == TYPE_ADDRCONFIG) {
-            dnsResolver.query(network, host, flag, r -> r.run(), null /* cancellationSignal */,
-                    callback);
-        } else {
-            dnsResolver.query(network, host, type, flag, r -> r.run(),
-                    null /* cancellationSignal */, callback);
-        }
-
-        TrafficStats.setThreadStatsTag(oldTag);
-
-        try {
-            latch.await(timeoutMs, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-        }
-
-        final List<InetAddress> result = resultRef.get();
-        if (result == null || result.size() == 0) {
-            throw new UnknownHostException(host);
-        }
-
-        return result.toArray(new InetAddress[0]);
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/server/NetworkObserver.java b/packages/NetworkStack/src/com/android/server/NetworkObserver.java
deleted file mode 100644
index cccec0b..0000000
--- a/packages/NetworkStack/src/com/android/server/NetworkObserver.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.net.LinkAddress;
-import android.net.RouteInfo;
-
-/**
- * Observer for network events, to use with {@link NetworkObserverRegistry}.
- */
-public interface NetworkObserver {
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener#onInterfaceChanged(java.lang.String, boolean)
-     */
-    default void onInterfaceChanged(String ifName, boolean up) {}
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener#onInterfaceRemoved(String)
-     */
-    default void onInterfaceRemoved(String ifName) {}
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener
-     *          #onInterfaceAddressUpdated(String, String, int, int)
-     */
-    default void onInterfaceAddressUpdated(LinkAddress address, String ifName) {}
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener
-     *          #onInterfaceAddressRemoved(String, String, int, int)
-     */
-    default void onInterfaceAddressRemoved(LinkAddress address, String ifName) {}
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener#onInterfaceLinkStateChanged(String, boolean)
-     */
-    default void onInterfaceLinkStateChanged(String ifName, boolean up) {}
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener#onInterfaceAdded(String)
-     */
-    default void onInterfaceAdded(String ifName) {}
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener
-     *          #onInterfaceClassActivityChanged(boolean, int, long, int)
-     */
-    default void onInterfaceClassActivityChanged(
-            boolean isActive, int label, long timestamp, int uid) {}
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener#onQuotaLimitReached(String, String)
-     */
-    default void onQuotaLimitReached(String alertName, String ifName) {}
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener
-     *          #onInterfaceDnsServerInfo(String, long, String[])
-     */
-    default void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) {}
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener
-     *          #onRouteChanged(boolean, String, String, String)
-     */
-    default void onRouteUpdated(RouteInfo route) {}
-
-    /**
-     * @see android.net.INetdUnsolicitedEventListener
-     *          #onRouteChanged(boolean, String, String, String)
-     */
-    default void onRouteRemoved(RouteInfo route) {}
-}
diff --git a/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java
deleted file mode 100644
index afe166b..0000000
--- a/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server;
-
-import static android.net.RouteInfo.RTN_UNICAST;
-
-import android.annotation.NonNull;
-import android.net.INetd;
-import android.net.INetdUnsolicitedEventListener;
-import android.net.InetAddresses;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.RouteInfo;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * A class for reporting network events to clients.
- *
- * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to
- * all INetworkManagementEventObserver objects that have registered with it.
- */
-public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub {
-    private static final String TAG = NetworkObserverRegistry.class.getSimpleName();
-
-    /**
-     * Constructs a new NetworkObserverRegistry.
-     *
-     * <p>Only one registry should be used per process since netd will silently ignore multiple
-     * registrations from the same process.
-     */
-    NetworkObserverRegistry() {}
-
-    /**
-     * Start listening for Netd events.
-     *
-     * <p>This should be called before allowing any observer to be registered.
-     */
-    void register(@NonNull INetd netd) throws RemoteException {
-        netd.registerUnsolicitedEventListener(this);
-    }
-
-    private final ConcurrentHashMap<NetworkObserver, Optional<Handler>> mObservers =
-            new ConcurrentHashMap<>();
-
-    /**
-     * Registers the specified observer and start sending callbacks to it.
-     * This method may be called on any thread.
-     */
-    public void registerObserver(@NonNull NetworkObserver observer, @NonNull Handler handler) {
-        if (handler == null) {
-            throw new IllegalArgumentException("handler must be non-null");
-        }
-        mObservers.put(observer, Optional.of(handler));
-    }
-
-    /**
-     * Registers the specified observer, and start sending callbacks to it.
-     *
-     * <p>This method must only be called with callbacks that are nonblocking, such as callbacks
-     * that only send a message to a StateMachine.
-     */
-    public void registerObserverForNonblockingCallback(@NonNull NetworkObserver observer) {
-        mObservers.put(observer, Optional.empty());
-    }
-
-    /**
-     * Unregisters the specified observer and stop sending callbacks to it.
-     * This method may be called on any thread.
-     */
-    public void unregisterObserver(@NonNull NetworkObserver observer) {
-        mObservers.remove(observer);
-    }
-
-    @FunctionalInterface
-    private interface NetworkObserverEventCallback {
-        void sendCallback(NetworkObserver o);
-    }
-
-    private void invokeForAllObservers(@NonNull final NetworkObserverEventCallback callback) {
-        // ConcurrentHashMap#entrySet is weakly consistent: observers that were in the map before
-        // creation will be processed, those added during traversal may or may not.
-        for (Map.Entry<NetworkObserver, Optional<Handler>> entry : mObservers.entrySet()) {
-            final NetworkObserver observer = entry.getKey();
-            final Optional<Handler> handler = entry.getValue();
-            if (handler.isPresent()) {
-                handler.get().post(() -> callback.sendCallback(observer));
-                return;
-            }
-
-            try {
-                callback.sendCallback(observer);
-            } catch (RuntimeException e) {
-                Log.e(TAG, "Error sending callback to observer", e);
-            }
-        }
-    }
-
-    @Override
-    public void onInterfaceClassActivityChanged(boolean isActive,
-            int label, long timestamp, int uid) {
-        invokeForAllObservers(o -> o.onInterfaceClassActivityChanged(
-                isActive, label, timestamp, uid));
-    }
-
-    /**
-     * Notify our observers of a limit reached.
-     */
-    @Override
-    public void onQuotaLimitReached(String alertName, String ifName) {
-        invokeForAllObservers(o -> o.onQuotaLimitReached(alertName, ifName));
-    }
-
-    @Override
-    public void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) {
-        invokeForAllObservers(o -> o.onInterfaceDnsServerInfo(ifName, lifetime, servers));
-    }
-
-    @Override
-    public void onInterfaceAddressUpdated(String addr, String ifName, int flags, int scope) {
-        final LinkAddress address = new LinkAddress(addr, flags, scope);
-        invokeForAllObservers(o -> o.onInterfaceAddressUpdated(address, ifName));
-    }
-
-    @Override
-    public void onInterfaceAddressRemoved(String addr,
-            String ifName, int flags, int scope) {
-        final LinkAddress address = new LinkAddress(addr, flags, scope);
-        invokeForAllObservers(o -> o.onInterfaceAddressRemoved(address, ifName));
-    }
-
-    @Override
-    public void onInterfaceAdded(String ifName) {
-        invokeForAllObservers(o -> o.onInterfaceAdded(ifName));
-    }
-
-    @Override
-    public void onInterfaceRemoved(String ifName) {
-        invokeForAllObservers(o -> o.onInterfaceRemoved(ifName));
-    }
-
-    @Override
-    public void onInterfaceChanged(String ifName, boolean up) {
-        invokeForAllObservers(o -> o.onInterfaceChanged(ifName, up));
-    }
-
-    @Override
-    public void onInterfaceLinkStateChanged(String ifName, boolean up) {
-        invokeForAllObservers(o -> o.onInterfaceLinkStateChanged(ifName, up));
-    }
-
-    @Override
-    public void onRouteChanged(boolean updated, String route, String gateway, String ifName) {
-        final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
-                ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
-                ifName, RTN_UNICAST);
-        if (updated) {
-            invokeForAllObservers(o -> o.onRouteUpdated(processRoute));
-        } else {
-            invokeForAllObservers(o -> o.onRouteRemoved(processRoute));
-        }
-    }
-
-    @Override
-    public void onStrictCleartextDetected(int uid, String hex) {}
-
-    @Override
-    public int getInterfaceVersion() {
-        return INetdUnsolicitedEventListener.VERSION;
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
deleted file mode 100644
index c394d4c..0000000
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * 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;
-
-import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
-import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
-import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
-
-import static com.android.server.util.PermissionUtil.checkDumpPermission;
-import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.IIpMemoryStore;
-import android.net.IIpMemoryStoreCallbacks;
-import android.net.INetd;
-import android.net.INetworkMonitor;
-import android.net.INetworkMonitorCallbacks;
-import android.net.INetworkStackConnector;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.PrivateDnsConfigParcel;
-import android.net.dhcp.DhcpServer;
-import android.net.dhcp.DhcpServingParams;
-import android.net.dhcp.DhcpServingParamsParcel;
-import android.net.dhcp.IDhcpServerCallbacks;
-import android.net.ip.IIpClientCallbacks;
-import android.net.ip.IpClient;
-import android.net.shared.PrivateDnsConfig;
-import android.net.util.SharedLog;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.connectivity.NetworkMonitor;
-import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Android service used to start the network stack when bound to via an intent.
- *
- * <p>The service returns a binder for the system server to communicate with the network stack.
- */
-public class NetworkStackService extends Service {
-    private static final String TAG = NetworkStackService.class.getSimpleName();
-    private static NetworkStackConnector sConnector;
-
-    /**
-     * Create a binder connector for the system server to communicate with the network stack.
-     *
-     * <p>On platforms where the network stack runs in the system server process, this method may
-     * be called directly instead of obtaining the connector by binding to the service.
-     */
-    public static synchronized IBinder makeConnector(Context context) {
-        if (sConnector == null) {
-            sConnector = new NetworkStackConnector(context);
-        }
-        return sConnector;
-    }
-
-    @NonNull
-    @Override
-    public IBinder onBind(Intent intent) {
-        return makeConnector(this);
-    }
-
-    /**
-     * An interface for internal clients of the network stack service that can return
-     * or create inline instances of the service it manages.
-     */
-    public interface NetworkStackServiceManager {
-        /**
-         * Get an instance of the IpMemoryStoreService.
-         */
-        IIpMemoryStore getIpMemoryStoreService();
-    }
-
-    private static class NetworkStackConnector extends INetworkStackConnector.Stub
-            implements NetworkStackServiceManager {
-        private static final int NUM_VALIDATION_LOG_LINES = 20;
-        private final Context mContext;
-        private final INetd mNetd;
-        private final NetworkObserverRegistry mObserverRegistry;
-        private final ConnectivityManager mCm;
-        @GuardedBy("mIpClients")
-        private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>();
-        private final IpMemoryStoreService mIpMemoryStoreService;
-
-        private static final int MAX_VALIDATION_LOGS = 10;
-        @GuardedBy("mValidationLogs")
-        private final ArrayDeque<SharedLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS);
-
-        private static final int VERSION_UNKNOWN = 0;
-        private static final String DUMPSYS_ARG_VERSION = "version";
-
-        /** Version of the AIDL interfaces observed on the system */
-        private final AtomicInteger mSystemAidlVersion = new AtomicInteger(VERSION_UNKNOWN);
-
-        /** Whether different versions have been observed on interfaces provided by the system */
-        private volatile boolean mConflictingSystemAidlVersions = false;
-
-        private SharedLog addValidationLogs(Network network, String name) {
-            final SharedLog log = new SharedLog(NUM_VALIDATION_LOG_LINES, network + " - " + name);
-            synchronized (mValidationLogs) {
-                while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
-                    mValidationLogs.removeLast();
-                }
-                mValidationLogs.addFirst(log);
-            }
-            return log;
-        }
-
-        NetworkStackConnector(Context context) {
-            mContext = context;
-            mNetd = INetd.Stub.asInterface(
-                    (IBinder) context.getSystemService(Context.NETD_SERVICE));
-            mObserverRegistry = new NetworkObserverRegistry();
-            mCm = context.getSystemService(ConnectivityManager.class);
-            mIpMemoryStoreService = new IpMemoryStoreService(context);
-
-            try {
-                mObserverRegistry.register(mNetd);
-            } catch (RemoteException e) {
-                mLog.e("Error registering observer on Netd", e);
-            }
-        }
-
-        private void updateSystemAidlVersion(final int version) {
-            final int previousVersion = mSystemAidlVersion.getAndSet(version);
-            if (previousVersion != VERSION_UNKNOWN && previousVersion != version) {
-                mConflictingSystemAidlVersions = true;
-            }
-        }
-
-        @NonNull
-        private final SharedLog mLog = new SharedLog(TAG);
-
-        @Override
-        public void makeDhcpServer(@NonNull String ifName, @NonNull DhcpServingParamsParcel params,
-                @NonNull IDhcpServerCallbacks cb) throws RemoteException {
-            checkNetworkStackCallingPermission();
-            updateSystemAidlVersion(cb.getInterfaceVersion());
-            final DhcpServer server;
-            try {
-                server = new DhcpServer(
-                        ifName,
-                        DhcpServingParams.fromParcelableObject(params),
-                        mLog.forSubComponent(ifName + ".DHCP"));
-            } catch (DhcpServingParams.InvalidParameterException e) {
-                mLog.e("Invalid DhcpServingParams", e);
-                cb.onDhcpServerCreated(STATUS_INVALID_ARGUMENT, null);
-                return;
-            } catch (Exception e) {
-                mLog.e("Unknown error starting DhcpServer", e);
-                cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null);
-                return;
-            }
-            cb.onDhcpServerCreated(STATUS_SUCCESS, server);
-        }
-
-        @Override
-        public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb)
-                throws RemoteException {
-            updateSystemAidlVersion(cb.getInterfaceVersion());
-            final SharedLog log = addValidationLogs(network, name);
-            final NetworkMonitor nm = new NetworkMonitor(mContext, cb, network, log);
-            cb.onNetworkMonitorCreated(new NetworkMonitorImpl(nm));
-        }
-
-        @Override
-        public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException {
-            updateSystemAidlVersion(cb.getInterfaceVersion());
-            final IpClient ipClient = new IpClient(mContext, ifName, cb, mObserverRegistry, this);
-
-            synchronized (mIpClients) {
-                final Iterator<WeakReference<IpClient>> it = mIpClients.iterator();
-                while (it.hasNext()) {
-                    final IpClient ipc = it.next().get();
-                    if (ipc == null) {
-                        it.remove();
-                    }
-                }
-                mIpClients.add(new WeakReference<>(ipClient));
-            }
-
-            cb.onIpClientCreated(ipClient.makeConnector());
-        }
-
-        @Override
-        public IIpMemoryStore getIpMemoryStoreService() {
-            return mIpMemoryStoreService;
-        }
-
-        @Override
-        public void fetchIpMemoryStore(@NonNull final IIpMemoryStoreCallbacks cb)
-                throws RemoteException {
-            updateSystemAidlVersion(cb.getInterfaceVersion());
-            cb.onIpMemoryStoreFetched(mIpMemoryStoreService);
-        }
-
-        @Override
-        protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
-                @Nullable String[] args) {
-            checkDumpPermission();
-            if (args != null && args.length >= 1 && DUMPSYS_ARG_VERSION.equals(args[0])) {
-                dumpVersion(fout);
-                return;
-            }
-
-            final IndentingPrintWriter pw = new IndentingPrintWriter(fout, "  ");
-            pw.println("NetworkStack logs:");
-            mLog.dump(fd, pw, args);
-
-            // Dump full IpClient logs for non-GCed clients
-            pw.println();
-            pw.println("Recently active IpClient logs:");
-            final ArrayList<IpClient> ipClients = new ArrayList<>();
-            final HashSet<String> dumpedIpClientIfaces = new HashSet<>();
-            synchronized (mIpClients) {
-                for (WeakReference<IpClient> ipcRef : mIpClients) {
-                    final IpClient ipc = ipcRef.get();
-                    if (ipc != null) {
-                        ipClients.add(ipc);
-                    }
-                }
-            }
-
-            for (IpClient ipc : ipClients) {
-                pw.println(ipc.getName());
-                pw.increaseIndent();
-                ipc.dump(fd, pw, args);
-                pw.decreaseIndent();
-                dumpedIpClientIfaces.add(ipc.getInterfaceName());
-            }
-
-            // State machine and connectivity metrics logs are kept for GCed IpClients
-            pw.println();
-            pw.println("Other IpClient logs:");
-            IpClient.dumpAllLogs(fout, dumpedIpClientIfaces);
-
-            pw.println();
-            pw.println("Validation logs (most recent first):");
-            synchronized (mValidationLogs) {
-                for (SharedLog p : mValidationLogs) {
-                    pw.println(p.getTag());
-                    pw.increaseIndent();
-                    p.dump(fd, pw, args);
-                    pw.decreaseIndent();
-                }
-            }
-        }
-
-        /**
-         * Dump version information of the module and detected system version.
-         */
-        private void dumpVersion(@NonNull PrintWriter fout) {
-            fout.println("NetworkStackConnector: " + this.VERSION);
-            fout.println("SystemServer: " + mSystemAidlVersion);
-            fout.println("SystemServerConflicts: " + mConflictingSystemAidlVersions);
-        }
-
-        @Override
-        public int getInterfaceVersion() {
-            return this.VERSION;
-        }
-    }
-
-    private static class NetworkMonitorImpl extends INetworkMonitor.Stub {
-        private final NetworkMonitor mNm;
-
-        NetworkMonitorImpl(NetworkMonitor nm) {
-            mNm = nm;
-        }
-
-        @Override
-        public void start() {
-            checkNetworkStackCallingPermission();
-            mNm.start();
-        }
-
-        @Override
-        public void launchCaptivePortalApp() {
-            checkNetworkStackCallingPermission();
-            mNm.launchCaptivePortalApp();
-        }
-
-        @Override
-        public void notifyCaptivePortalAppFinished(int response) {
-            checkNetworkStackCallingPermission();
-            mNm.notifyCaptivePortalAppFinished(response);
-        }
-
-        @Override
-        public void setAcceptPartialConnectivity() {
-            checkNetworkStackCallingPermission();
-            mNm.setAcceptPartialConnectivity();
-        }
-
-        @Override
-        public void forceReevaluation(int uid) {
-            checkNetworkStackCallingPermission();
-            mNm.forceReevaluation(uid);
-        }
-
-        @Override
-        public void notifyPrivateDnsChanged(PrivateDnsConfigParcel config) {
-            checkNetworkStackCallingPermission();
-            mNm.notifyPrivateDnsSettingsChanged(PrivateDnsConfig.fromParcel(config));
-        }
-
-        @Override
-        public void notifyDnsResponse(int returnCode) {
-            checkNetworkStackCallingPermission();
-            mNm.notifyDnsResponse(returnCode);
-        }
-
-        @Override
-        public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) {
-            checkNetworkStackCallingPermission();
-            mNm.notifyNetworkConnected(lp, nc);
-        }
-
-        @Override
-        public void notifyNetworkDisconnected() {
-            checkNetworkStackCallingPermission();
-            mNm.notifyNetworkDisconnected();
-        }
-
-        @Override
-        public void notifyLinkPropertiesChanged(LinkProperties lp) {
-            checkNetworkStackCallingPermission();
-            mNm.notifyLinkPropertiesChanged(lp);
-        }
-
-        @Override
-        public void notifyNetworkCapabilitiesChanged(NetworkCapabilities nc) {
-            checkNetworkStackCallingPermission();
-            mNm.notifyNetworkCapabilitiesChanged(nc);
-        }
-
-        @Override
-        public int getInterfaceVersion() {
-            return this.VERSION;
-        }
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
deleted file mode 100644
index 8e9350d..0000000
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ /dev/null
@@ -1,2027 +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.connectivity;
-
-import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
-import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
-import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
-import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
-import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.DnsResolver.FLAG_EMPTY;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static android.net.captiveportal.CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs;
-import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
-import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
-import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
-import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS;
-import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
-import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE;
-import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL;
-import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD;
-import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS;
-import static android.net.util.DataStallUtils.DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
-import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_EVALUATION_TYPES;
-import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS;
-import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS;
-import static android.net.util.DataStallUtils.DEFAULT_DNS_LOG_SIZE;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_URL;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTPS_URL;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTP_URL;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_IGNORE;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_PROMPT;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USER_AGENT;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
-import static android.net.util.NetworkStackUtils.NAMESPACE_CONNECTIVITY;
-import static android.net.util.NetworkStackUtils.isEmpty;
-
-import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.DnsResolver;
-import android.net.INetworkMonitor;
-import android.net.INetworkMonitorCallbacks;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.ProxyInfo;
-import android.net.TrafficStats;
-import android.net.Uri;
-import android.net.captiveportal.CaptivePortalProbeResult;
-import android.net.captiveportal.CaptivePortalProbeSpec;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.NetworkEvent;
-import android.net.metrics.ValidationProbeEvent;
-import android.net.shared.NetworkMonitorUtils;
-import android.net.shared.PrivateDnsConfig;
-import android.net.util.NetworkStackUtils;
-import android.net.util.SharedLog;
-import android.net.util.Stopwatch;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.CellSignalStrength;
-import android.telephony.NetworkRegistrationInfo;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.ArrayRes;
-import androidx.annotation.StringRes;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.RingBufferIndices;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.internal.util.TrafficStatsConstants;
-import com.android.networkstack.R;
-import com.android.networkstack.metrics.DataStallDetectionStats;
-import com.android.networkstack.metrics.DataStallStatsUtils;
-import com.android.networkstack.util.DnsUtils;
-
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Random;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-
-/**
- * {@hide}
- */
-public class NetworkMonitor extends StateMachine {
-    private static final String TAG = NetworkMonitor.class.getSimpleName();
-    private static final boolean DBG  = true;
-    private static final boolean VDBG = false;
-    private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG);
-    private static final String DEFAULT_USER_AGENT    = "Mozilla/5.0 (X11; Linux x86_64) "
-                                                      + "AppleWebKit/537.36 (KHTML, like Gecko) "
-                                                      + "Chrome/60.0.3112.32 Safari/537.36";
-
-    @VisibleForTesting
-    static final String CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT =
-            "captive_portal_dns_probe_timeout";
-
-    private static final int SOCKET_TIMEOUT_MS = 10000;
-    private static final int PROBE_TIMEOUT_MS  = 3000;
-
-    enum EvaluationResult {
-        VALIDATED(true),
-        CAPTIVE_PORTAL(false);
-        final boolean mIsValidated;
-        EvaluationResult(boolean isValidated) {
-            this.mIsValidated = isValidated;
-        }
-    }
-
-    enum ValidationStage {
-        FIRST_VALIDATION(true),
-        REVALIDATION(false);
-        final boolean mIsFirstValidation;
-        ValidationStage(boolean isFirstValidation) {
-            this.mIsFirstValidation = isFirstValidation;
-        }
-    }
-
-    /**
-     * ConnectivityService has sent a notification to indicate that network has connected.
-     * Initiates Network Validation.
-     */
-    private static final int CMD_NETWORK_CONNECTED = 1;
-
-    /**
-     * Message to self indicating it's time to evaluate a network's connectivity.
-     * arg1 = Token to ignore old messages.
-     */
-    private static final int CMD_REEVALUATE = 6;
-
-    /**
-     * ConnectivityService has sent a notification to indicate that network has disconnected.
-     */
-    private static final int CMD_NETWORK_DISCONNECTED = 7;
-
-    /**
-     * Force evaluation even if it has succeeded in the past.
-     * arg1 = UID responsible for requesting this reeval.  Will be billed for data.
-     */
-    private static final int CMD_FORCE_REEVALUATION = 8;
-
-    /**
-     * Message to self indicating captive portal app finished.
-     * arg1 = one of: APP_RETURN_DISMISSED,
-     *                APP_RETURN_UNWANTED,
-     *                APP_RETURN_WANTED_AS_IS
-     * obj = mCaptivePortalLoggedInResponseToken as String
-     */
-    private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9;
-
-    /**
-     * Message indicating sign-in app should be launched.
-     * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
-     * user touches the sign in notification, or sent by
-     * ConnectivityService when the user touches the "sign into
-     * network" button in the wifi access point detail page.
-     */
-    private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11;
-
-    /**
-     * Retest network to see if captive portal is still in place.
-     * arg1 = UID responsible for requesting this reeval.  Will be billed for data.
-     *        0 indicates self-initiated, so nobody to blame.
-     */
-    private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12;
-
-    /**
-     * ConnectivityService notifies NetworkMonitor of settings changes to
-     * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in
-     * strict mode, then an event is sent back to ConnectivityService with the
-     * result of the resolution attempt.
-     *
-     * A separate message is used to trigger (re)evaluation of the Private DNS
-     * configuration, so that the message can be handled as needed in different
-     * states, including being ignored until after an ongoing captive portal
-     * validation phase is completed.
-     */
-    private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13;
-    private static final int CMD_EVALUATE_PRIVATE_DNS = 15;
-
-    /**
-     * Message to self indicating captive portal detection is completed.
-     * obj = CaptivePortalProbeResult for detection result;
-     */
-    private static final int CMD_PROBE_COMPLETE = 16;
-
-    /**
-     * ConnectivityService notifies NetworkMonitor of DNS query responses event.
-     * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query.
-     */
-    private static final int EVENT_DNS_NOTIFICATION = 17;
-
-    /**
-     * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and
-     * NetworkMonitor should ignore the https probe.
-     */
-    private static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18;
-
-    /**
-     * ConnectivityService notifies NetworkMonitor of changed LinkProperties.
-     * obj = new LinkProperties.
-     */
-    private static final int EVENT_LINK_PROPERTIES_CHANGED = 19;
-
-    /**
-     * ConnectivityService notifies NetworkMonitor of changed NetworkCapabilities.
-     * obj = new NetworkCapabilities.
-     */
-    private static final int EVENT_NETWORK_CAPABILITIES_CHANGED = 20;
-
-    // Start mReevaluateDelayMs at this value and double.
-    private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
-    private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
-    // Before network has been evaluated this many times, ignore repeated reevaluate requests.
-    private static final int IGNORE_REEVALUATE_ATTEMPTS = 5;
-    private int mReevaluateToken = 0;
-    private static final int NO_UID = 0;
-    private static final int INVALID_UID = -1;
-    private int mUidResponsibleForReeval = INVALID_UID;
-    // Stop blaming UID that requested re-evaluation after this many attempts.
-    private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5;
-    // Delay between reevaluations once a captive portal has been found.
-    private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
-
-    private String mPrivateDnsProviderHostname = "";
-
-    private final Context mContext;
-    private final INetworkMonitorCallbacks mCallback;
-    private final Network mCleartextDnsNetwork;
-    private final Network mNetwork;
-    private final TelephonyManager mTelephonyManager;
-    private final WifiManager mWifiManager;
-    private final ConnectivityManager mCm;
-    private final IpConnectivityLog mMetricsLog;
-    private final Dependencies mDependencies;
-    private final DataStallStatsUtils mDetectionStatsUtils;
-
-    // Configuration values for captive portal detection probes.
-    private final String mCaptivePortalUserAgent;
-    private final URL mCaptivePortalHttpsUrl;
-    private final URL mCaptivePortalHttpUrl;
-    private final URL[] mCaptivePortalFallbackUrls;
-    @Nullable
-    private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
-
-    private NetworkCapabilities mNetworkCapabilities;
-    private LinkProperties mLinkProperties;
-
-    @VisibleForTesting
-    protected boolean mIsCaptivePortalCheckEnabled;
-
-    private boolean mUseHttps;
-    // The total number of captive portal detection attempts for this NetworkMonitor instance.
-    private int mValidations = 0;
-
-    // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
-    private boolean mUserDoesNotWant = false;
-    // Avoids surfacing "Sign in to network" notification.
-    private boolean mDontDisplaySigninNotification = false;
-
-    private final State mDefaultState = new DefaultState();
-    private final State mValidatedState = new ValidatedState();
-    private final State mMaybeNotifyState = new MaybeNotifyState();
-    private final State mEvaluatingState = new EvaluatingState();
-    private final State mCaptivePortalState = new CaptivePortalState();
-    private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState();
-    private final State mProbingState = new ProbingState();
-    private final State mWaitingForNextProbeState = new WaitingForNextProbeState();
-
-    private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
-
-    private final SharedLog mValidationLogs;
-
-    private final Stopwatch mEvaluationTimer = new Stopwatch();
-
-    // This variable is set before transitioning to the mCaptivePortalState.
-    private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED;
-
-    // Random generator to select fallback URL index
-    private final Random mRandom;
-    private int mNextFallbackUrlIndex = 0;
-
-
-    private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
-    private int mEvaluateAttempts = 0;
-    private volatile int mProbeToken = 0;
-    private final int mConsecutiveDnsTimeoutThreshold;
-    private final int mDataStallMinEvaluateTime;
-    private final int mDataStallValidDnsTimeThreshold;
-    private final int mDataStallEvaluationType;
-    private final DnsStallDetector mDnsStallDetector;
-    private long mLastProbeTime;
-    // Set to true if data stall is suspected and reset to false after metrics are sent to statsd.
-    private boolean mCollectDataStallMetrics;
-    private boolean mAcceptPartialConnectivity;
-
-    public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
-            SharedLog validationLog) {
-        this(context, cb, network, new IpConnectivityLog(), validationLog,
-                Dependencies.DEFAULT, new DataStallStatsUtils());
-    }
-
-    @VisibleForTesting
-    protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
-            IpConnectivityLog logger, SharedLog validationLogs,
-            Dependencies deps, DataStallStatsUtils detectionStatsUtils) {
-        // Add suffix indicating which NetworkMonitor we're talking about.
-        super(TAG + "/" + network.toString());
-
-        // Logs with a tag of the form given just above, e.g.
-        //     <timestamp>   862  2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
-        setDbg(VDBG);
-
-        mContext = context;
-        mMetricsLog = logger;
-        mValidationLogs = validationLogs;
-        mCallback = cb;
-        mDependencies = deps;
-        mDetectionStatsUtils = detectionStatsUtils;
-        mNetwork = network;
-        mCleartextDnsNetwork = deps.getPrivateDnsBypassNetwork(network);
-        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-
-        // CHECKSTYLE:OFF IndentationCheck
-        addState(mDefaultState);
-        addState(mMaybeNotifyState, mDefaultState);
-            addState(mEvaluatingState, mMaybeNotifyState);
-                addState(mProbingState, mEvaluatingState);
-                addState(mWaitingForNextProbeState, mEvaluatingState);
-            addState(mCaptivePortalState, mMaybeNotifyState);
-        addState(mEvaluatingPrivateDnsState, mDefaultState);
-        addState(mValidatedState, mDefaultState);
-        setInitialState(mDefaultState);
-        // CHECKSTYLE:ON IndentationCheck
-
-        mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
-        mUseHttps = getUseHttpsValidation();
-        mCaptivePortalUserAgent = getCaptivePortalUserAgent();
-        mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
-        mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl());
-        mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
-        mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
-        mRandom = deps.getRandom();
-        // TODO: Evaluate to move data stall configuration to a specific class.
-        mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold();
-        mDnsStallDetector = new DnsStallDetector(mConsecutiveDnsTimeoutThreshold);
-        mDataStallMinEvaluateTime = getDataStallMinEvaluateTime();
-        mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
-        mDataStallEvaluationType = getDataStallEvaluationType();
-
-        // Provide empty LinkProperties and NetworkCapabilities to make sure they are never null,
-        // even before notifyNetworkConnected.
-        mLinkProperties = new LinkProperties();
-        mNetworkCapabilities = new NetworkCapabilities(null);
-    }
-
-    /**
-     * ConnectivityService notifies NetworkMonitor that the user already accepted partial
-     * connectivity previously, so NetworkMonitor can validate the network even if it has partial
-     * connectivity.
-     */
-    public void setAcceptPartialConnectivity() {
-        sendMessage(EVENT_ACCEPT_PARTIAL_CONNECTIVITY);
-    }
-
-    /**
-     * Request the NetworkMonitor to reevaluate the network.
-     */
-    public void forceReevaluation(int responsibleUid) {
-        sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0);
-    }
-
-    /**
-     * Send a notification to NetworkMonitor indicating that there was a DNS query response event.
-     * @param returnCode the DNS return code of the response.
-     */
-    public void notifyDnsResponse(int returnCode) {
-        sendMessage(EVENT_DNS_NOTIFICATION, returnCode);
-    }
-
-    /**
-     * Send a notification to NetworkMonitor indicating that private DNS settings have changed.
-     * @param newCfg The new private DNS configuration.
-     */
-    public void notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg) {
-        // Cancel any outstanding resolutions.
-        removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED);
-        // Send the update to the proper thread.
-        sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
-    }
-
-    /**
-     * Send a notification to NetworkMonitor indicating that the network is now connected.
-     */
-    public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) {
-        sendMessage(CMD_NETWORK_CONNECTED, new Pair<>(
-                new LinkProperties(lp), new NetworkCapabilities(nc)));
-    }
-
-    private void updateConnectedNetworkAttributes(Message connectedMsg) {
-        final Pair<LinkProperties, NetworkCapabilities> attrs =
-                (Pair<LinkProperties, NetworkCapabilities>) connectedMsg.obj;
-        mLinkProperties = attrs.first;
-        mNetworkCapabilities = attrs.second;
-    }
-
-    /**
-     * Send a notification to NetworkMonitor indicating that the network is now disconnected.
-     */
-    public void notifyNetworkDisconnected() {
-        sendMessage(CMD_NETWORK_DISCONNECTED);
-    }
-
-    /**
-     * Send a notification to NetworkMonitor indicating that link properties have changed.
-     */
-    public void notifyLinkPropertiesChanged(final LinkProperties lp) {
-        sendMessage(EVENT_LINK_PROPERTIES_CHANGED, new LinkProperties(lp));
-    }
-
-    /**
-     * Send a notification to NetworkMonitor indicating that network capabilities have changed.
-     */
-    public void notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc) {
-        sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, new NetworkCapabilities(nc));
-    }
-
-    /**
-     * Request the captive portal application to be launched.
-     */
-    public void launchCaptivePortalApp() {
-        sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP);
-    }
-
-    /**
-     * Notify that the captive portal app was closed with the provided response code.
-     */
-    public void notifyCaptivePortalAppFinished(int response) {
-        sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
-    }
-
-    @Override
-    protected void log(String s) {
-        if (DBG) Log.d(TAG + "/" + mCleartextDnsNetwork.toString(), s);
-    }
-
-    private void validationLog(int probeType, Object url, String msg) {
-        String probeName = ValidationProbeEvent.getProbeName(probeType);
-        validationLog(String.format("%s %s %s", probeName, url, msg));
-    }
-
-    private void validationLog(String s) {
-        if (DBG) log(s);
-        mValidationLogs.log(s);
-    }
-
-    private ValidationStage validationStage() {
-        return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION;
-    }
-
-    private boolean isValidationRequired() {
-        return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
-    }
-
-    private boolean isPrivateDnsValidationRequired() {
-        return NetworkMonitorUtils.isPrivateDnsValidationRequired(mNetworkCapabilities);
-    }
-
-    private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
-        try {
-            mCallback.notifyNetworkTested(result, redirectUrl);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error sending network test result", e);
-        }
-    }
-
-    private void showProvisioningNotification(String action) {
-        try {
-            mCallback.showProvisioningNotification(action, mContext.getPackageName());
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error showing provisioning notification", e);
-        }
-    }
-
-    private void hideProvisioningNotification() {
-        try {
-            mCallback.hideProvisioningNotification();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error hiding provisioning notification", e);
-        }
-    }
-
-    // DefaultState is the parent of all States.  It exists only to handle CMD_* messages but
-    // does not entail any real state (hence no enter() or exit() routines).
-    private class DefaultState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_NETWORK_CONNECTED:
-                    updateConnectedNetworkAttributes(message);
-                    logNetworkEvent(NetworkEvent.NETWORK_CONNECTED);
-                    transitionTo(mEvaluatingState);
-                    return HANDLED;
-                case CMD_NETWORK_DISCONNECTED:
-                    logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
-                    quit();
-                    return HANDLED;
-                case CMD_FORCE_REEVALUATION:
-                case CMD_CAPTIVE_PORTAL_RECHECK:
-                    final int dnsCount = mDnsStallDetector.getConsecutiveTimeoutCount();
-                    validationLog("Forcing reevaluation for UID " + message.arg1
-                            + ". Dns signal count: " + dnsCount);
-                    mUidResponsibleForReeval = message.arg1;
-                    transitionTo(mEvaluatingState);
-                    return HANDLED;
-                case CMD_CAPTIVE_PORTAL_APP_FINISHED:
-                    log("CaptivePortal App responded with " + message.arg1);
-
-                    // If the user has seen and acted on a captive portal notification, and the
-                    // captive portal app is now closed, disable HTTPS probes. This avoids the
-                    // following pathological situation:
-                    //
-                    // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out.
-                    // 2. User opens the app and logs into the captive portal.
-                    // 3. HTTP starts working, but HTTPS still doesn't work for some other reason -
-                    //    perhaps due to the network blocking HTTPS?
-                    //
-                    // In this case, we'll fail to validate the network even after the app is
-                    // dismissed. There is now no way to use this network, because the app is now
-                    // gone, so the user cannot select "Use this network as is".
-                    mUseHttps = false;
-
-                    switch (message.arg1) {
-                        case APP_RETURN_DISMISSED:
-                            sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0);
-                            break;
-                        case APP_RETURN_WANTED_AS_IS:
-                            mDontDisplaySigninNotification = true;
-                            // TODO: Distinguish this from a network that actually validates.
-                            // Displaying the "x" on the system UI icon may still be a good idea.
-                            transitionTo(mEvaluatingPrivateDnsState);
-                            break;
-                        case APP_RETURN_UNWANTED:
-                            mDontDisplaySigninNotification = true;
-                            mUserDoesNotWant = true;
-                            notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
-                            // TODO: Should teardown network.
-                            mUidResponsibleForReeval = 0;
-                            transitionTo(mEvaluatingState);
-                            break;
-                    }
-                    return HANDLED;
-                case CMD_PRIVATE_DNS_SETTINGS_CHANGED: {
-                    final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj;
-                    if (!isPrivateDnsValidationRequired() || cfg == null || !cfg.inStrictMode()) {
-                        // No DNS resolution required.
-                        //
-                        // We don't force any validation in opportunistic mode
-                        // here. Opportunistic mode nameservers are validated
-                        // separately within netd.
-                        //
-                        // Reset Private DNS settings state.
-                        mPrivateDnsProviderHostname = "";
-                        break;
-                    }
-
-                    mPrivateDnsProviderHostname = cfg.hostname;
-
-                    // DNS resolutions via Private DNS strict mode block for a
-                    // few seconds (~4.2) checking for any IP addresses to
-                    // arrive and validate. Initiating a (re)evaluation now
-                    // should not significantly alter the validation outcome.
-                    //
-                    // No matter what: enqueue a validation request; one of
-                    // three things can happen with this request:
-                    //     [1] ignored (EvaluatingState or CaptivePortalState)
-                    //     [2] transition to EvaluatingPrivateDnsState
-                    //         (DefaultState and ValidatedState)
-                    //     [3] handled (EvaluatingPrivateDnsState)
-                    //
-                    // The Private DNS configuration to be evaluated will:
-                    //     [1] be skipped (not in strict mode), or
-                    //     [2] validate (huzzah), or
-                    //     [3] encounter some problem (invalid hostname,
-                    //         no resolved IP addresses, IPs unreachable,
-                    //         port 853 unreachable, port 853 is not running a
-                    //         DNS-over-TLS server, et cetera).
-                    sendMessage(CMD_EVALUATE_PRIVATE_DNS);
-                    break;
-                }
-                case EVENT_DNS_NOTIFICATION:
-                    mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
-                    break;
-                // Set mAcceptPartialConnectivity to true and if network start evaluating or
-                // re-evaluating and get the result of partial connectivity, ProbingState will
-                // disable HTTPS probe and transition to EvaluatingPrivateDnsState.
-                case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
-                    mAcceptPartialConnectivity = true;
-                    break;
-                case EVENT_LINK_PROPERTIES_CHANGED:
-                    mLinkProperties = (LinkProperties) message.obj;
-                    break;
-                case EVENT_NETWORK_CAPABILITIES_CHANGED:
-                    mNetworkCapabilities = (NetworkCapabilities) message.obj;
-                    break;
-                default:
-                    break;
-            }
-            return HANDLED;
-        }
-    }
-
-    // Being in the ValidatedState State indicates a Network is:
-    // - Successfully validated, or
-    // - Wanted "as is" by the user, or
-    // - Does not satisfy the default NetworkRequest and so validation has been skipped.
-    private class ValidatedState extends State {
-        @Override
-        public void enter() {
-            maybeLogEvaluationResult(
-                    networkEventType(validationStage(), EvaluationResult.VALIDATED));
-            notifyNetworkTested(INetworkMonitor.NETWORK_TEST_RESULT_VALID, null);
-            mValidations++;
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_NETWORK_CONNECTED:
-                    updateConnectedNetworkAttributes(message);
-                    transitionTo(mValidatedState);
-                    break;
-                case CMD_EVALUATE_PRIVATE_DNS:
-                    transitionTo(mEvaluatingPrivateDnsState);
-                    break;
-                case EVENT_DNS_NOTIFICATION:
-                    mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
-                    if (isDataStall()) {
-                        mCollectDataStallMetrics = true;
-                        validationLog("Suspecting data stall, reevaluate");
-                        transitionTo(mEvaluatingState);
-                    }
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
-    private void writeDataStallStats(@NonNull final CaptivePortalProbeResult result) {
-        /*
-         * Collect data stall detection level information for each transport type. Collect type
-         * specific information for cellular and wifi only currently. Generate
-         * DataStallDetectionStats for each transport type. E.g., if a network supports both
-         * TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated.
-         */
-        final int[] transports = mNetworkCapabilities.getTransportTypes();
-
-        for (int i = 0; i < transports.length; i++) {
-            DataStallStatsUtils.write(buildDataStallDetectionStats(transports[i]), result);
-        }
-        mCollectDataStallMetrics = false;
-    }
-
-    @VisibleForTesting
-    protected DataStallDetectionStats buildDataStallDetectionStats(int transport) {
-        final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder();
-        if (VDBG_STALL) log("collectDataStallMetrics: type=" + transport);
-        stats.setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
-        stats.setNetworkType(transport);
-        switch (transport) {
-            case NetworkCapabilities.TRANSPORT_WIFI:
-                // TODO: Update it if status query in dual wifi is supported.
-                final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
-                stats.setWiFiData(wifiInfo);
-                break;
-            case NetworkCapabilities.TRANSPORT_CELLULAR:
-                final boolean isRoaming = !mNetworkCapabilities.hasCapability(
-                        NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
-                final SignalStrength ss = mTelephonyManager.getSignalStrength();
-                // TODO(b/120452078): Support multi-sim.
-                stats.setCellData(
-                        mTelephonyManager.getDataNetworkType(),
-                        isRoaming,
-                        mTelephonyManager.getNetworkOperator(),
-                        mTelephonyManager.getSimOperator(),
-                        (ss != null)
-                        ? ss.getLevel() : CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
-                break;
-            default:
-                // No transport type specific information for the other types.
-                break;
-        }
-        addDnsEvents(stats);
-
-        return stats.build();
-    }
-
-    @VisibleForTesting
-    protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
-        final int size = mDnsStallDetector.mResultIndices.size();
-        for (int i = 1; i <= DEFAULT_DNS_LOG_SIZE && i <= size; i++) {
-            final int index = mDnsStallDetector.mResultIndices.indexOf(size - i);
-            stats.addDnsEvent(mDnsStallDetector.mDnsEvents[index].mReturnCode,
-                    mDnsStallDetector.mDnsEvents[index].mTimeStamp);
-        }
-    }
-
-
-    // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in
-    // is required.  This State takes care to clear the notification upon exit from the State.
-    private class MaybeNotifyState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_LAUNCH_CAPTIVE_PORTAL_APP:
-                    final Bundle appExtras = new Bundle();
-                    // OneAddressPerFamilyNetwork is not parcelable across processes.
-                    final Network network = new Network(mCleartextDnsNetwork);
-                    appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network);
-                    final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
-                    appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl);
-                    if (probeRes.probeSpec != null) {
-                        final String encodedSpec = probeRes.probeSpec.getEncodedSpec();
-                        appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec);
-                    }
-                    appExtras.putString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT,
-                            mCaptivePortalUserAgent);
-                    mCm.startCaptivePortalApp(network, appExtras);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-
-        @Override
-        public void exit() {
-            if (mLaunchCaptivePortalAppBroadcastReceiver != null) {
-                mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver);
-                mLaunchCaptivePortalAppBroadcastReceiver = null;
-            }
-            hideProvisioningNotification();
-        }
-    }
-
-    // Being in the EvaluatingState State indicates the Network is being evaluated for internet
-    // connectivity, or that the user has indicated that this network is unwanted.
-    private class EvaluatingState extends State {
-        @Override
-        public void enter() {
-            // If we have already started to track time spent in EvaluatingState
-            // don't reset the timer due simply to, say, commands or events that
-            // cause us to exit and re-enter EvaluatingState.
-            if (!mEvaluationTimer.isStarted()) {
-                mEvaluationTimer.start();
-            }
-            sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
-            if (mUidResponsibleForReeval != INVALID_UID) {
-                TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
-                mUidResponsibleForReeval = INVALID_UID;
-            }
-            mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
-            mEvaluateAttempts = 0;
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_REEVALUATE:
-                    if (message.arg1 != mReevaluateToken || mUserDoesNotWant) {
-                        return HANDLED;
-                    }
-                    // Don't bother validating networks that don't satisfy the default request.
-                    // This includes:
-                    //  - VPNs which can be considered explicitly desired by the user and the
-                    //    user's desire trumps whether the network validates.
-                    //  - Networks that don't provide Internet access.  It's unclear how to
-                    //    validate such networks.
-                    //  - Untrusted networks.  It's unsafe to prompt the user to sign-in to
-                    //    such networks and the user didn't express interest in connecting to
-                    //    such networks (an app did) so the user may be unhappily surprised when
-                    //    asked to sign-in to a network they didn't want to connect to in the
-                    //    first place.  Validation could be done to adjust the network scores
-                    //    however these networks are app-requested and may not be intended for
-                    //    general usage, in which case general validation may not be an accurate
-                    //    measure of the network's quality.  Only the app knows how to evaluate
-                    //    the network so don't bother validating here.  Furthermore sending HTTP
-                    //    packets over the network may be undesirable, for example an extremely
-                    //    expensive metered network, or unwanted leaking of the User Agent string.
-                    //
-                    // On networks that need to support private DNS in strict mode (e.g., VPNs, but
-                    // not networks that don't provide Internet access), we still need to perform
-                    // private DNS server resolution.
-                    if (!isValidationRequired()) {
-                        if (isPrivateDnsValidationRequired()) {
-                            validationLog("Network would not satisfy default request, "
-                                    + "resolving private DNS");
-                            transitionTo(mEvaluatingPrivateDnsState);
-                        } else {
-                            validationLog("Network would not satisfy default request, "
-                                    + "not validating");
-                            transitionTo(mValidatedState);
-                        }
-                        return HANDLED;
-                    }
-                    mEvaluateAttempts++;
-
-                    transitionTo(mProbingState);
-                    return HANDLED;
-                case CMD_FORCE_REEVALUATION:
-                    // Before IGNORE_REEVALUATE_ATTEMPTS attempts are made,
-                    // ignore any re-evaluation requests. After, restart the
-                    // evaluation process via EvaluatingState#enter.
-                    return (mEvaluateAttempts < IGNORE_REEVALUATE_ATTEMPTS) ? HANDLED : NOT_HANDLED;
-                // Disable HTTPS probe and transition to EvaluatingPrivateDnsState because:
-                // 1. Network is connected and finish the network validation.
-                // 2. NetworkMonitor detects network is partial connectivity and user accepts it.
-                case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
-                    mAcceptPartialConnectivity = true;
-                    mUseHttps = false;
-                    transitionTo(mEvaluatingPrivateDnsState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-
-        @Override
-        public void exit() {
-            TrafficStats.clearThreadStatsUid();
-        }
-    }
-
-    // BroadcastReceiver that waits for a particular Intent and then posts a message.
-    private class CustomIntentReceiver extends BroadcastReceiver {
-        private final int mToken;
-        private final int mWhat;
-        private final String mAction;
-        CustomIntentReceiver(String action, int token, int what) {
-            mToken = token;
-            mWhat = what;
-            mAction = action + "_" + mCleartextDnsNetwork.getNetworkHandle() + "_" + token;
-            mContext.registerReceiver(this, new IntentFilter(mAction));
-        }
-        public PendingIntent getPendingIntent() {
-            final Intent intent = new Intent(mAction);
-            intent.setPackage(mContext.getPackageName());
-            return PendingIntent.getBroadcast(mContext, 0, intent, 0);
-        }
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken));
-        }
-    }
-
-    // Being in the CaptivePortalState State indicates a captive portal was detected and the user
-    // has been shown a notification to sign-in.
-    private class CaptivePortalState extends State {
-        private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP =
-                "android.net.netmon.launchCaptivePortalApp";
-
-        @Override
-        public void enter() {
-            maybeLogEvaluationResult(
-                    networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL));
-            // Don't annoy user with sign-in notifications.
-            if (mDontDisplaySigninNotification) return;
-            // Create a CustomIntentReceiver that sends us a
-            // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user
-            // touches the notification.
-            if (mLaunchCaptivePortalAppBroadcastReceiver == null) {
-                // Wait for result.
-                mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver(
-                        ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(),
-                        CMD_LAUNCH_CAPTIVE_PORTAL_APP);
-                // Display the sign in notification.
-                // Only do this once for every time we enter MaybeNotifyState. b/122164725
-                showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
-            }
-            // Retest for captive portal occasionally.
-            sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
-                    CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
-            mValidations++;
-        }
-
-        @Override
-        public void exit() {
-            removeMessages(CMD_CAPTIVE_PORTAL_RECHECK);
-        }
-    }
-
-    private class EvaluatingPrivateDnsState extends State {
-        private int mPrivateDnsReevalDelayMs;
-        private PrivateDnsConfig mPrivateDnsConfig;
-
-        @Override
-        public void enter() {
-            mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS;
-            mPrivateDnsConfig = null;
-            sendMessage(CMD_EVALUATE_PRIVATE_DNS);
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_EVALUATE_PRIVATE_DNS:
-                    if (inStrictMode()) {
-                        if (!isStrictModeHostnameResolved()) {
-                            resolveStrictModeHostname();
-
-                            if (isStrictModeHostnameResolved()) {
-                                notifyPrivateDnsConfigResolved();
-                            } else {
-                                handlePrivateDnsEvaluationFailure();
-                                break;
-                            }
-                        }
-
-                        // Look up a one-time hostname, to bypass caching.
-                        //
-                        // Note that this will race with ConnectivityService
-                        // code programming the DNS-over-TLS server IP addresses
-                        // into netd (if invoked, above). If netd doesn't know
-                        // the IP addresses yet, or if the connections to the IP
-                        // addresses haven't yet been validated, netd will block
-                        // for up to a few seconds before failing the lookup.
-                        if (!sendPrivateDnsProbe()) {
-                            handlePrivateDnsEvaluationFailure();
-                            break;
-                        }
-                    }
-
-                    // All good!
-                    transitionTo(mValidatedState);
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-
-        private boolean inStrictMode() {
-            return !TextUtils.isEmpty(mPrivateDnsProviderHostname);
-        }
-
-        private boolean isStrictModeHostnameResolved() {
-            return (mPrivateDnsConfig != null)
-                    && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname)
-                    && (mPrivateDnsConfig.ips.length > 0);
-        }
-
-        private void resolveStrictModeHostname() {
-            try {
-                // Do a blocking DNS resolution using the network-assigned nameservers.
-                final InetAddress[] ips = DnsUtils.getAllByName(mDependencies.getDnsResolver(),
-                        mCleartextDnsNetwork, mPrivateDnsProviderHostname, getDnsProbeTimeout());
-                mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
-                validationLog("Strict mode hostname resolved: " + mPrivateDnsConfig);
-            } catch (UnknownHostException uhe) {
-                mPrivateDnsConfig = null;
-                validationLog("Strict mode hostname resolution failed: " + uhe.getMessage());
-            }
-        }
-
-        private void notifyPrivateDnsConfigResolved() {
-            try {
-                mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel());
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error sending private DNS config resolved notification", e);
-            }
-        }
-
-        private void handlePrivateDnsEvaluationFailure() {
-            notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
-
-            // Queue up a re-evaluation with backoff.
-            //
-            // TODO: Consider abandoning this state after a few attempts and
-            // transitioning back to EvaluatingState, to perhaps give ourselves
-            // the opportunity to (re)detect a captive portal or something.
-            sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs);
-            mPrivateDnsReevalDelayMs *= 2;
-            if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) {
-                mPrivateDnsReevalDelayMs = MAX_REEVALUATE_DELAY_MS;
-            }
-        }
-
-        private boolean sendPrivateDnsProbe() {
-            // q.v. system/netd/server/dns/DnsTlsTransport.cpp
-            final String oneTimeHostnameSuffix = "-dnsotls-ds.metric.gstatic.com";
-            final String host = UUID.randomUUID().toString().substring(0, 8)
-                    + oneTimeHostnameSuffix;
-            final Stopwatch watch = new Stopwatch().start();
-            try {
-                final InetAddress[] ips = mNetwork.getAllByName(host);
-                final long time = watch.stop();
-                final String strIps = Arrays.toString(ips);
-                final boolean success = (ips != null && ips.length > 0);
-                validationLog(PROBE_PRIVDNS, host, String.format("%dms: %s", time, strIps));
-                logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
-                return success;
-            } catch (UnknownHostException uhe) {
-                final long time = watch.stop();
-                validationLog(PROBE_PRIVDNS, host,
-                        String.format("%dms - Error: %s", time, uhe.getMessage()));
-                logValidationProbe(time, PROBE_PRIVDNS, DNS_FAILURE);
-            }
-            return false;
-        }
-    }
-
-    private class ProbingState extends State {
-        private Thread mThread;
-
-        @Override
-        public void enter() {
-            if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
-                //Don't continue to blame UID forever.
-                TrafficStats.clearThreadStatsUid();
-            }
-
-            final int token = ++mProbeToken;
-            mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
-                    isCaptivePortal())));
-            mThread.start();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_PROBE_COMPLETE:
-                    // Ensure that CMD_PROBE_COMPLETE from stale threads are ignored.
-                    if (message.arg1 != mProbeToken) {
-                        return HANDLED;
-                    }
-
-                    final CaptivePortalProbeResult probeResult =
-                            (CaptivePortalProbeResult) message.obj;
-                    mLastProbeTime = SystemClock.elapsedRealtime();
-
-                    if (mCollectDataStallMetrics) {
-                        writeDataStallStats(probeResult);
-                    }
-
-                    if (probeResult.isSuccessful()) {
-                        // Transit EvaluatingPrivateDnsState to get to Validated
-                        // state (even if no Private DNS validation required).
-                        transitionTo(mEvaluatingPrivateDnsState);
-                    } else if (probeResult.isPortal()) {
-                        notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
-                        mLastPortalProbeResult = probeResult;
-                        transitionTo(mCaptivePortalState);
-                    } else if (probeResult.isPartialConnectivity()) {
-                        logNetworkEvent(NetworkEvent.NETWORK_PARTIAL_CONNECTIVITY);
-                        notifyNetworkTested(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY,
-                                probeResult.redirectUrl);
-                        if (mAcceptPartialConnectivity) {
-                            mUseHttps = false;
-                            transitionTo(mEvaluatingPrivateDnsState);
-                        } else {
-                            transitionTo(mWaitingForNextProbeState);
-                        }
-                    } else {
-                        logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
-                        notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
-                        transitionTo(mWaitingForNextProbeState);
-                    }
-                    return HANDLED;
-                case EVENT_DNS_NOTIFICATION:
-                case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
-                    // Leave the event to DefaultState.
-                    return NOT_HANDLED;
-                default:
-                    // Wait for probe result and defer events to next state by default.
-                    deferMessage(message);
-                    return HANDLED;
-            }
-        }
-
-        @Override
-        public void exit() {
-            if (mThread.isAlive()) {
-                mThread.interrupt();
-            }
-            mThread = null;
-        }
-    }
-
-    // Being in the WaitingForNextProbeState indicates that evaluating probes failed and state is
-    // transited from ProbingState. This ensures that the state machine is only in ProbingState
-    // while a probe is in progress, not while waiting to perform the next probe. That allows
-    // ProbingState to defer most messages until the probe is complete, which keeps the code simple
-    // and matches the pre-Q behaviour where probes were a blocking operation performed on the state
-    // machine thread.
-    private class WaitingForNextProbeState extends State {
-        @Override
-        public void enter() {
-            scheduleNextProbe();
-        }
-
-        private void scheduleNextProbe() {
-            final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
-            sendMessageDelayed(msg, mReevaluateDelayMs);
-            mReevaluateDelayMs *= 2;
-            if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) {
-                mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            return NOT_HANDLED;
-        }
-    }
-
-    // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at
-    // most one per address family. This ensures we only wait up to 20 seconds for TCP connections
-    // to complete, regardless of how many IP addresses a host has.
-    private static class OneAddressPerFamilyNetwork extends Network {
-        OneAddressPerFamilyNetwork(Network network) {
-            // Always bypass Private DNS.
-            super(network.getPrivateDnsBypassingCopy());
-        }
-
-        @Override
-        public InetAddress[] getAllByName(String host) throws UnknownHostException {
-            final List<InetAddress> addrs = Arrays.asList(super.getAllByName(host));
-
-            // Ensure the address family of the first address is tried first.
-            LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>();
-            addressByFamily.put(addrs.get(0).getClass(), addrs.get(0));
-            Collections.shuffle(addrs);
-
-            for (InetAddress addr : addrs) {
-                addressByFamily.put(addr.getClass(), addr);
-            }
-
-            return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]);
-        }
-    }
-
-    private boolean getIsCaptivePortalCheckEnabled() {
-        String symbol = CAPTIVE_PORTAL_MODE;
-        int defaultValue = CAPTIVE_PORTAL_MODE_PROMPT;
-        int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
-        return mode != CAPTIVE_PORTAL_MODE_IGNORE;
-    }
-
-    private boolean getUseHttpsValidation() {
-        return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
-                CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
-    }
-
-    private String getCaptivePortalServerHttpsUrl() {
-        return getSettingFromResource(mContext, R.string.config_captive_portal_https_url,
-                R.string.default_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL);
-    }
-
-    private int getDnsProbeTimeout() {
-        return getIntSetting(mContext, R.integer.config_captive_portal_dns_probe_timeout,
-                CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
-                R.integer.default_captive_portal_dns_probe_timeout);
-    }
-
-    /**
-     * Gets an integer setting from resources or device config
-     *
-     * configResource is used if set, followed by device config if set, followed by defaultResource.
-     * If none of these are set then an exception is thrown.
-     *
-     * TODO: move to a common location such as a ConfigUtils class.
-     * TODO(b/130324939): test that the resources can be overlayed by an RRO package.
-     */
-    @VisibleForTesting
-    int getIntSetting(@NonNull final Context context, @StringRes int configResource,
-            @NonNull String symbol, @StringRes int defaultResource) {
-        final Resources res = context.getResources();
-        try {
-            return res.getInteger(configResource);
-        } catch (Resources.NotFoundException e) {
-            return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
-                    symbol, res.getInteger(defaultResource));
-        }
-    }
-
-    /**
-     * Get the captive portal server HTTP URL that is configured on the device.
-     *
-     * NetworkMonitor does not use {@link ConnectivityManager#getCaptivePortalServerUrl()} as
-     * it has its own updatable strategies to detect captive portals. The framework only advises
-     * on one URL that can be used, while NetworkMonitor may implement more complex logic.
-     */
-    public String getCaptivePortalServerHttpUrl() {
-        return getSettingFromResource(mContext, R.string.config_captive_portal_http_url,
-                R.string.default_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL);
-    }
-
-    private int getConsecutiveDnsTimeoutThreshold() {
-        return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
-                CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD,
-                DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD);
-    }
-
-    private int getDataStallMinEvaluateTime() {
-        return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
-                CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL,
-                DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS);
-    }
-
-    private int getDataStallValidDnsTimeThreshold() {
-        return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
-                CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD,
-                DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS);
-    }
-
-    private int getDataStallEvaluationType() {
-        return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
-                CONFIG_DATA_STALL_EVALUATION_TYPE,
-                DEFAULT_DATA_STALL_EVALUATION_TYPES);
-    }
-
-    private URL[] makeCaptivePortalFallbackUrls() {
-        try {
-            final String firstUrl = mDependencies.getSetting(mContext, CAPTIVE_PORTAL_FALLBACK_URL,
-                    null);
-
-            final URL[] settingProviderUrls;
-            if (!TextUtils.isEmpty(firstUrl)) {
-                final String otherUrls = mDependencies.getDeviceConfigProperty(
-                        NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_OTHER_FALLBACK_URLS, "");
-                // otherUrls may be empty, but .split() ignores trailing empty strings
-                final String separator = ",";
-                final String[] urls = (firstUrl + separator + otherUrls).split(separator);
-                settingProviderUrls = convertStrings(urls, this::makeURL, new URL[0]);
-            } else {
-                settingProviderUrls = new URL[0];
-            }
-
-            return getArrayConfig(settingProviderUrls, R.array.config_captive_portal_fallback_urls,
-                    R.array.default_captive_portal_fallback_urls, this::makeURL);
-        } catch (Exception e) {
-            // Don't let a misconfiguration bootloop the system.
-            Log.e(TAG, "Error parsing configured fallback URLs", e);
-            return new URL[0];
-        }
-    }
-
-    private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() {
-        try {
-            final String settingsValue = mDependencies.getDeviceConfigProperty(
-                    NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null);
-
-            final CaptivePortalProbeSpec[] emptySpecs = new CaptivePortalProbeSpec[0];
-            final CaptivePortalProbeSpec[] providerValue = TextUtils.isEmpty(settingsValue)
-                    ? emptySpecs
-                    : parseCaptivePortalProbeSpecs(settingsValue).toArray(emptySpecs);
-
-            return getArrayConfig(providerValue, R.array.config_captive_portal_fallback_probe_specs,
-                    R.array.default_captive_portal_fallback_probe_specs,
-                    CaptivePortalProbeSpec::parseSpecOrNull);
-        } catch (Exception e) {
-            // Don't let a misconfiguration bootloop the system.
-            Log.e(TAG, "Error parsing configured fallback probe specs", e);
-            return null;
-        }
-    }
-
-    /**
-     * Read a setting from a resource or the settings provider.
-     *
-     * <p>The configuration resource is prioritized, then the provider value, then the default
-     * resource value.
-     * @param context The context
-     * @param configResource The resource id for the configuration parameter
-     * @param defaultResource The resource id for the default value
-     * @param symbol The symbol in the settings provider
-     * @return The best available value
-     */
-    @NonNull
-    private String getSettingFromResource(@NonNull final Context context,
-            @StringRes int configResource, @StringRes int defaultResource,
-            @NonNull String symbol) {
-        final Resources res = context.getResources();
-        String setting = res.getString(configResource);
-
-        if (!TextUtils.isEmpty(setting)) return setting;
-
-        setting = mDependencies.getSetting(context, symbol, null);
-        if (!TextUtils.isEmpty(setting)) return setting;
-
-        return res.getString(defaultResource);
-    }
-
-    /**
-     * Get an array configuration from resources or the settings provider.
-     *
-     * <p>The configuration resource is prioritized, then the provider values, then the default
-     * resource values.
-     * @param providerValue Values obtained from the setting provider.
-     * @param configResId ID of the configuration resource.
-     * @param defaultResId ID of the default resource.
-     * @param resourceConverter Converter from the resource strings to stored setting class. Null
-     *                          return values are ignored.
-     */
-    private <T> T[] getArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId,
-            @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter) {
-        final Resources res = mContext.getResources();
-        String[] configValue = res.getStringArray(configResId);
-
-        if (configValue.length == 0) {
-            if (providerValue.length > 0) {
-                return providerValue;
-            }
-
-            configValue = res.getStringArray(defaultResId);
-        }
-
-        return convertStrings(configValue, resourceConverter, Arrays.copyOf(providerValue, 0));
-    }
-
-    /**
-     * Convert a String array to an array of some other type using the specified converter.
-     *
-     * <p>Any null value, or value for which the converter throws a {@link RuntimeException}, will
-     * not be added to the output array, so the output array may be smaller than the input.
-     */
-    private <T> T[] convertStrings(
-            @NonNull String[] strings, Function<String, T> converter, T[] emptyArray) {
-        final ArrayList<T> convertedValues = new ArrayList<>(strings.length);
-        for (String configString : strings) {
-            T convertedValue = null;
-            try {
-                convertedValue = converter.apply(configString);
-            } catch (Exception e) {
-                Log.e(TAG, "Error parsing configuration", e);
-                // Fall through
-            }
-            if (convertedValue != null) {
-                convertedValues.add(convertedValue);
-            }
-        }
-        return convertedValues.toArray(emptyArray);
-    }
-
-    private String getCaptivePortalUserAgent() {
-        return mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY,
-                CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
-    }
-
-    private URL nextFallbackUrl() {
-        if (mCaptivePortalFallbackUrls.length == 0) {
-            return null;
-        }
-        int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length;
-        mNextFallbackUrlIndex += mRandom.nextInt(); // randomly change url without memory.
-        return mCaptivePortalFallbackUrls[idx];
-    }
-
-    private CaptivePortalProbeSpec nextFallbackSpec() {
-        if (isEmpty(mCaptivePortalFallbackSpecs)) {
-            return null;
-        }
-        // Randomly change spec without memory. Also randomize the first attempt.
-        final int idx = Math.abs(mRandom.nextInt()) % mCaptivePortalFallbackSpecs.length;
-        return mCaptivePortalFallbackSpecs[idx];
-    }
-
-    @VisibleForTesting
-    protected CaptivePortalProbeResult isCaptivePortal() {
-        if (!mIsCaptivePortalCheckEnabled) {
-            validationLog("Validation disabled.");
-            return CaptivePortalProbeResult.SUCCESS;
-        }
-
-        URL pacUrl = null;
-        URL httpsUrl = mCaptivePortalHttpsUrl;
-        URL httpUrl = mCaptivePortalHttpUrl;
-
-        // On networks with a PAC instead of fetching a URL that should result in a 204
-        // response, we instead simply fetch the PAC script.  This is done for a few reasons:
-        // 1. At present our PAC code does not yet handle multiple PACs on multiple networks
-        //    until something like https://android-review.googlesource.com/#/c/115180/ lands.
-        //    Network.openConnection() will ignore network-specific PACs and instead fetch
-        //    using NO_PROXY.  If a PAC is in place, the only fetch we know will succeed with
-        //    NO_PROXY is the fetch of the PAC itself.
-        // 2. To proxy the generate_204 fetch through a PAC would require a number of things
-        //    happen before the fetch can commence, namely:
-        //        a) the PAC script be fetched
-        //        b) a PAC script resolver service be fired up and resolve the captive portal
-        //           server.
-        //    Network validation could be delayed until these prerequisities are satisifed or
-        //    could simply be left to race them.  Neither is an optimal solution.
-        // 3. PAC scripts are sometimes used to block or restrict Internet access and may in
-        //    fact block fetching of the generate_204 URL which would lead to false negative
-        //    results for network validation.
-        final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy();
-        if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
-            pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
-            if (pacUrl == null) {
-                return CaptivePortalProbeResult.FAILED;
-            }
-        }
-
-        if ((pacUrl == null) && (httpUrl == null || httpsUrl == null)) {
-            return CaptivePortalProbeResult.FAILED;
-        }
-
-        long startTime = SystemClock.elapsedRealtime();
-
-        final CaptivePortalProbeResult result;
-        if (pacUrl != null) {
-            result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
-        } else if (mUseHttps) {
-            result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl);
-        } else {
-            result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP);
-        }
-
-        long endTime = SystemClock.elapsedRealtime();
-
-        sendNetworkConditionsBroadcast(true /* response received */,
-                result.isPortal() /* isCaptivePortal */,
-                startTime, endTime);
-
-        log("isCaptivePortal: isSuccessful()=" + result.isSuccessful()
-                + " isPortal()=" + result.isPortal()
-                + " RedirectUrl=" + result.redirectUrl
-                + " Time=" + (endTime - startTime) + "ms");
-
-        return result;
-    }
-
-    /**
-     * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect.
-     * @return a CaptivePortalProbeResult inferred from the HTTP response.
-     */
-    private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) {
-        // Pre-resolve the captive portal server host so we can log it.
-        // Only do this if HttpURLConnection is about to, to avoid any potentially
-        // unnecessary resolution.
-        final String host = (proxy != null) ? proxy.getHost() : url.getHost();
-        sendDnsProbe(host);
-        return sendHttpProbe(url, probeType, null);
-    }
-
-    /** Do a DNS lookup for the given server, or throw UnknownHostException after timeoutMs */
-    @VisibleForTesting
-    protected InetAddress[] sendDnsProbeWithTimeout(String host, int timeoutMs)
-                throws UnknownHostException {
-        return DnsUtils.getAllByName(mDependencies.getDnsResolver(), mCleartextDnsNetwork, host,
-                TYPE_ADDRCONFIG, FLAG_EMPTY, timeoutMs);
-    }
-
-    /** Do a DNS resolution of the given server. */
-    private void sendDnsProbe(String host) {
-        if (TextUtils.isEmpty(host)) {
-            return;
-        }
-
-        final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS);
-        final Stopwatch watch = new Stopwatch().start();
-        int result;
-        String connectInfo;
-        try {
-            InetAddress[] addresses = sendDnsProbeWithTimeout(host, getDnsProbeTimeout());
-            StringBuffer buffer = new StringBuffer();
-            for (InetAddress address : addresses) {
-                buffer.append(',').append(address.getHostAddress());
-            }
-            result = ValidationProbeEvent.DNS_SUCCESS;
-            connectInfo = "OK " + buffer.substring(1);
-        } catch (UnknownHostException e) {
-            result = ValidationProbeEvent.DNS_FAILURE;
-            connectInfo = "FAIL";
-        }
-        final long latency = watch.stop();
-        validationLog(ValidationProbeEvent.PROBE_DNS, host,
-                String.format("%dms %s", latency, connectInfo));
-        logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
-    }
-
-    /**
-     * Do a URL fetch on a known web server to see if we get the data we expect.
-     * @return a CaptivePortalProbeResult inferred from the HTTP response.
-     */
-    @VisibleForTesting
-    protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType,
-            @Nullable CaptivePortalProbeSpec probeSpec) {
-        HttpURLConnection urlConnection = null;
-        int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
-        String redirectUrl = null;
-        final Stopwatch probeTimer = new Stopwatch().start();
-        final int oldTag = TrafficStats.getAndSetThreadStatsTag(
-                TrafficStatsConstants.TAG_SYSTEM_PROBE);
-        try {
-            urlConnection = (HttpURLConnection) mCleartextDnsNetwork.openConnection(url);
-            urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC);
-            urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
-            urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
-            urlConnection.setRequestProperty("Connection", "close");
-            urlConnection.setUseCaches(false);
-            if (mCaptivePortalUserAgent != null) {
-                urlConnection.setRequestProperty("User-Agent", mCaptivePortalUserAgent);
-            }
-            // cannot read request header after connection
-            String requestHeader = urlConnection.getRequestProperties().toString();
-
-            // Time how long it takes to get a response to our request
-            long requestTimestamp = SystemClock.elapsedRealtime();
-
-            httpResponseCode = urlConnection.getResponseCode();
-            redirectUrl = urlConnection.getHeaderField("location");
-
-            // Time how long it takes to get a response to our request
-            long responseTimestamp = SystemClock.elapsedRealtime();
-
-            validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms"
-                    + " ret=" + httpResponseCode
-                    + " request=" + requestHeader
-                    + " headers=" + urlConnection.getHeaderFields());
-            // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
-            // portal.  The only example of this seen so far was a captive portal.  For
-            // the time being go with prior behavior of assuming it's not a captive
-            // portal.  If it is considered a captive portal, a different sign-in URL
-            // is needed (i.e. can't browse a 204).  This could be the result of an HTTP
-            // proxy server.
-            if (httpResponseCode == 200) {
-                long contentLength = urlConnection.getContentLengthLong();
-                if (probeType == ValidationProbeEvent.PROBE_PAC) {
-                    validationLog(
-                            probeType, url, "PAC fetch 200 response interpreted as 204 response.");
-                    httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
-                } else if (contentLength == -1) {
-                    // When no Content-length (default value == -1), attempt to read a byte
-                    // from the response. Do not use available() as it is unreliable.
-                    // See http://b/33498325.
-                    if (urlConnection.getInputStream().read() == -1) {
-                        validationLog(probeType, url,
-                                "Empty 200 response interpreted as failed response.");
-                        httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
-                    }
-                } else if (contentLength <= 4) {
-                    // Consider 200 response with "Content-length <= 4" to not be a captive
-                    // portal. There's no point in considering this a captive portal as the
-                    // user cannot sign-in to an empty page. Probably the result of a broken
-                    // transparent proxy. See http://b/9972012 and http://b/122999481.
-                    validationLog(probeType, url, "200 response with Content-length <= 4"
-                            + " interpreted as failed response.");
-                    httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
-                }
-            }
-        } catch (IOException e) {
-            validationLog(probeType, url, "Probe failed with exception " + e);
-            if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) {
-                // TODO: Ping gateway and DNS server and log results.
-            }
-        } finally {
-            if (urlConnection != null) {
-                urlConnection.disconnect();
-            }
-            TrafficStats.setThreadStatsTag(oldTag);
-        }
-        logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
-
-        if (probeSpec == null) {
-            return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString());
-        } else {
-            return probeSpec.getResult(httpResponseCode, redirectUrl);
-        }
-    }
-
-    private CaptivePortalProbeResult sendParallelHttpProbes(
-            ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
-        // Number of probes to wait for. If a probe completes with a conclusive answer
-        // it shortcuts the latch immediately by forcing the count to 0.
-        final CountDownLatch latch = new CountDownLatch(2);
-
-        final class ProbeThread extends Thread {
-            private final boolean mIsHttps;
-            private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED;
-
-            ProbeThread(boolean isHttps) {
-                mIsHttps = isHttps;
-            }
-
-            public CaptivePortalProbeResult result() {
-                return mResult;
-            }
-
-            @Override
-            public void run() {
-                if (mIsHttps) {
-                    mResult =
-                            sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS);
-                } else {
-                    mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP);
-                }
-                if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) {
-                    // Stop waiting immediately if https succeeds or if http finds a portal.
-                    while (latch.getCount() > 0) {
-                        latch.countDown();
-                    }
-                }
-                // Signal this probe has completed.
-                latch.countDown();
-            }
-        }
-
-        final ProbeThread httpsProbe = new ProbeThread(true);
-        final ProbeThread httpProbe = new ProbeThread(false);
-
-        try {
-            httpsProbe.start();
-            httpProbe.start();
-            latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            validationLog("Error: probes wait interrupted!");
-            return CaptivePortalProbeResult.FAILED;
-        }
-
-        final CaptivePortalProbeResult httpsResult = httpsProbe.result();
-        final CaptivePortalProbeResult httpResult = httpProbe.result();
-
-        // Look for a conclusive probe result first.
-        if (httpResult.isPortal()) {
-            return httpResult;
-        }
-        // httpsResult.isPortal() is not expected, but check it nonetheless.
-        if (httpsResult.isPortal() || httpsResult.isSuccessful()) {
-            return httpsResult;
-        }
-        // If a fallback method exists, use it to retry portal detection.
-        // If we have new-style probe specs, use those. Otherwise, use the fallback URLs.
-        final CaptivePortalProbeSpec probeSpec = nextFallbackSpec();
-        final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl();
-        CaptivePortalProbeResult fallbackProbeResult = null;
-        if (fallbackUrl != null) {
-            fallbackProbeResult = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
-            if (fallbackProbeResult.isPortal()) {
-                return fallbackProbeResult;
-            }
-        }
-        // Otherwise wait until http and https probes completes and use their results.
-        try {
-            httpProbe.join();
-            if (httpProbe.result().isPortal()) {
-                return httpProbe.result();
-            }
-            httpsProbe.join();
-            final boolean isHttpSuccessful =
-                    (httpProbe.result().isSuccessful()
-                    || (fallbackProbeResult != null && fallbackProbeResult.isSuccessful()));
-            if (httpsProbe.result().isFailed() && isHttpSuccessful) {
-                return CaptivePortalProbeResult.PARTIAL;
-            }
-            return httpsProbe.result();
-        } catch (InterruptedException e) {
-            validationLog("Error: http or https probe wait interrupted!");
-            return CaptivePortalProbeResult.FAILED;
-        }
-    }
-
-    private URL makeURL(String url) {
-        if (url != null) {
-            try {
-                return new URL(url);
-            } catch (MalformedURLException e) {
-                validationLog("Bad URL: " + url);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * @param responseReceived - whether or not we received a valid HTTP response to our request.
-     * If false, isCaptivePortal and responseTimestampMs are ignored
-     * TODO: This should be moved to the transports.  The latency could be passed to the transports
-     * along with the captive portal result.  Currently the TYPE_MOBILE broadcasts appear unused so
-     * perhaps this could just be added to the WiFi transport only.
-     */
-    private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
-            long requestTimestampMs, long responseTimestampMs) {
-        Intent latencyBroadcast =
-                new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED);
-        if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) {
-            if (!mWifiManager.isScanAlwaysAvailable()) {
-                return;
-            }
-
-            WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
-            if (currentWifiInfo != null) {
-                // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
-                // surrounded by double quotation marks (thus violating the Javadoc), but this
-                // was changed to match the Javadoc in API 17. Since clients may have started
-                // sanitizing the output of this method since API 17 was released, we should
-                // not change it here as it would become impossible to tell whether the SSID is
-                // simply being surrounded by quotes due to the API, or whether those quotes
-                // are actually part of the SSID.
-                latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID,
-                        currentWifiInfo.getSSID());
-                latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID,
-                        currentWifiInfo.getBSSID());
-            } else {
-                if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
-                return;
-            }
-            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI);
-        } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
-            // TODO(b/123893112): Support multi-sim.
-            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE,
-                    mTelephonyManager.getNetworkType());
-            final ServiceState dataSs = mTelephonyManager.getServiceState();
-            if (dataSs == null) {
-                logw("failed to retrieve ServiceState");
-                return;
-            }
-            // See if the data sub is registered for PS services on cell.
-            final NetworkRegistrationInfo nri = dataSs.getNetworkRegistrationInfo(
-                    NetworkRegistrationInfo.DOMAIN_PS,
-                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-            latencyBroadcast.putExtra(
-                    NetworkMonitorUtils.EXTRA_CELL_ID,
-                    nri == null ? null : nri.getCellIdentity());
-            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
-        } else {
-            return;
-        }
-        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED,
-                responseReceived);
-        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS,
-                requestTimestampMs);
-
-        if (responseReceived) {
-            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL,
-                    isCaptivePortal);
-            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
-                    responseTimestampMs);
-        }
-        mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
-                NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
-    }
-
-    private void logNetworkEvent(int evtype) {
-        int[] transports = mNetworkCapabilities.getTransportTypes();
-        mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype));
-    }
-
-    private int networkEventType(ValidationStage s, EvaluationResult r) {
-        if (s.mIsFirstValidation) {
-            if (r.mIsValidated) {
-                return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS;
-            } else {
-                return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND;
-            }
-        } else {
-            if (r.mIsValidated) {
-                return NetworkEvent.NETWORK_REVALIDATION_SUCCESS;
-            } else {
-                return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND;
-            }
-        }
-    }
-
-    private void maybeLogEvaluationResult(int evtype) {
-        if (mEvaluationTimer.isRunning()) {
-            int[] transports = mNetworkCapabilities.getTransportTypes();
-            mMetricsLog.log(mCleartextDnsNetwork, transports,
-                    new NetworkEvent(evtype, mEvaluationTimer.stop()));
-            mEvaluationTimer.reset();
-        }
-    }
-
-    private void logValidationProbe(long durationMs, int probeType, int probeResult) {
-        int[] transports = mNetworkCapabilities.getTransportTypes();
-        boolean isFirstValidation = validationStage().mIsFirstValidation;
-        ValidationProbeEvent ev = new ValidationProbeEvent.Builder()
-                .setProbeType(probeType, isFirstValidation)
-                .setReturnCode(probeResult)
-                .setDurationMs(durationMs)
-                .build();
-        mMetricsLog.log(mCleartextDnsNetwork, transports, ev);
-    }
-
-    @VisibleForTesting
-    static class Dependencies {
-        public Network getPrivateDnsBypassNetwork(Network network) {
-            return new OneAddressPerFamilyNetwork(network);
-        }
-
-        public DnsResolver getDnsResolver() {
-            return DnsResolver.getInstance();
-        }
-
-        public Random getRandom() {
-            return new Random();
-        }
-
-        /**
-         * Get the value of a global integer setting.
-         * @param symbol Name of the setting
-         * @param defaultValue Value to return if the setting is not defined.
-         */
-        public int getSetting(Context context, String symbol, int defaultValue) {
-            return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue);
-        }
-
-        /**
-         * Get the value of a global String setting.
-         * @param symbol Name of the setting
-         * @param defaultValue Value to return if the setting is not defined.
-         */
-        public String getSetting(Context context, String symbol, String defaultValue) {
-            final String value = Settings.Global.getString(context.getContentResolver(), symbol);
-            return value != null ? value : defaultValue;
-        }
-
-        /**
-         * Look up the value of a property in DeviceConfig.
-         * @param namespace The namespace containing the property to look up.
-         * @param name The name of the property to look up.
-         * @param defaultValue The value to return if the property does not exist or has no non-null
-         *                     value.
-         * @return the corresponding value, or defaultValue if none exists.
-         */
-        @Nullable
-        public String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name,
-                @Nullable String defaultValue) {
-            return NetworkStackUtils.getDeviceConfigProperty(namespace, name, defaultValue);
-        }
-
-        /**
-         * Look up the value of a property in DeviceConfig.
-         * @param namespace The namespace containing the property to look up.
-         * @param name The name of the property to look up.
-         * @param defaultValue The value to return if the property does not exist or has no non-null
-         *                     value.
-         * @return the corresponding value, or defaultValue if none exists.
-         */
-        public int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name,
-                int defaultValue) {
-            return NetworkStackUtils.getDeviceConfigPropertyInt(namespace, name, defaultValue);
-        }
-
-        public static final Dependencies DEFAULT = new Dependencies();
-    }
-
-    /**
-     * Methods in this class perform no locking because all accesses are performed on the state
-     * machine's thread. Need to consider the thread safety if it ever could be accessed outside the
-     * state machine.
-     */
-    @VisibleForTesting
-    protected class DnsStallDetector {
-        private int mConsecutiveTimeoutCount = 0;
-        private int mSize;
-        final DnsResult[] mDnsEvents;
-        final RingBufferIndices mResultIndices;
-
-        DnsStallDetector(int size) {
-            mSize = Math.max(DEFAULT_DNS_LOG_SIZE, size);
-            mDnsEvents = new DnsResult[mSize];
-            mResultIndices = new RingBufferIndices(mSize);
-        }
-
-        @VisibleForTesting
-        protected void accumulateConsecutiveDnsTimeoutCount(int code) {
-            final DnsResult result = new DnsResult(code);
-            mDnsEvents[mResultIndices.add()] = result;
-            if (result.isTimeout()) {
-                mConsecutiveTimeoutCount++;
-            } else {
-                // Keep the event in mDnsEvents without clearing it so that there are logs to do the
-                // simulation and analysis.
-                mConsecutiveTimeoutCount = 0;
-            }
-        }
-
-        private boolean isDataStallSuspected(int timeoutCountThreshold, int validTime) {
-            if (timeoutCountThreshold <= 0) {
-                Log.wtf(TAG, "Timeout count threshold should be larger than 0.");
-                return false;
-            }
-
-            // Check if the consecutive timeout count reach the threshold or not.
-            if (mConsecutiveTimeoutCount < timeoutCountThreshold) {
-                return false;
-            }
-
-            // Check if the target dns event index is valid or not.
-            final int firstConsecutiveTimeoutIndex =
-                    mResultIndices.indexOf(mResultIndices.size() - timeoutCountThreshold);
-
-            // If the dns timeout events happened long time ago, the events are meaningless for
-            // data stall evaluation. Thus, check if the first consecutive timeout dns event
-            // considered in the evaluation happened in defined threshold time.
-            final long now = SystemClock.elapsedRealtime();
-            final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp;
-            return (firstTimeoutTime < validTime);
-        }
-
-        int getConsecutiveTimeoutCount() {
-            return mConsecutiveTimeoutCount;
-        }
-    }
-
-    private static class DnsResult {
-        // TODO: Need to move the DNS return code definition to a specific class once unify DNS
-        // response code is done.
-        private static final int RETURN_CODE_DNS_TIMEOUT = 255;
-
-        private final long mTimeStamp;
-        private final int mReturnCode;
-
-        DnsResult(int code) {
-            mTimeStamp = SystemClock.elapsedRealtime();
-            mReturnCode = code;
-        }
-
-        private boolean isTimeout() {
-            return mReturnCode == RETURN_CODE_DNS_TIMEOUT;
-        }
-    }
-
-
-    @VisibleForTesting
-    protected DnsStallDetector getDnsStallDetector() {
-        return mDnsStallDetector;
-    }
-
-    private boolean dataStallEvaluateTypeEnabled(int type) {
-        return (mDataStallEvaluationType & type) != 0;
-    }
-
-    @VisibleForTesting
-    protected long getLastProbeTime() {
-        return mLastProbeTime;
-    }
-
-    @VisibleForTesting
-    protected boolean isDataStall() {
-        boolean result = false;
-        // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
-        // possible traffic cost in metered network.
-        if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && (SystemClock.elapsedRealtime() - getLastProbeTime()
-                < mDataStallMinEvaluateTime)) {
-            return false;
-        }
-
-        // Check dns signal. Suspect it may be a data stall if both :
-        // 1. The number of consecutive DNS query timeouts >= mConsecutiveDnsTimeoutThreshold.
-        // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms.
-        if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) {
-            if (mDnsStallDetector.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold,
-                    mDataStallValidDnsTimeThreshold)) {
-                result = true;
-                logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND);
-            }
-        }
-
-        if (VDBG_STALL) {
-            log("isDataStall: result=" + result + ", consecutive dns timeout count="
-                    + mDnsStallDetector.getConsecutiveTimeoutCount());
-        }
-
-        return result;
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
deleted file mode 100644
index 764e2d0..0000000
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
+++ /dev/null
@@ -1,651 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity.ipmemorystore;
-
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteCursor;
-import android.database.sqlite.SQLiteCursorDriver;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQuery;
-import android.net.ipmemorystore.NetworkAttributes;
-import android.net.ipmemorystore.Status;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringJoiner;
-
-/**
- * Encapsulating class for using the SQLite database backing the memory store.
- *
- * This class groups together the contracts and the SQLite helper used to
- * use the database.
- *
- * @hide
- */
-public class IpMemoryStoreDatabase {
-    private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName();
-    // A pair of NetworkAttributes objects is group-close if the confidence that they are
-    // the same is above this cutoff. See NetworkAttributes and SameL3NetworkResponse.
-    private static final float GROUPCLOSE_CONFIDENCE = 0.5f;
-
-    /**
-     * Contract class for the Network Attributes table.
-     */
-    public static class NetworkAttributesContract {
-        public static final String TABLENAME = "NetworkAttributes";
-
-        public static final String COLNAME_L2KEY = "l2Key";
-        public static final String COLTYPE_L2KEY = "TEXT NOT NULL";
-
-        public static final String COLNAME_EXPIRYDATE = "expiryDate";
-        // Milliseconds since the Epoch, in true Java style
-        public static final String COLTYPE_EXPIRYDATE = "BIGINT";
-
-        public static final String COLNAME_ASSIGNEDV4ADDRESS = "assignedV4Address";
-        public static final String COLTYPE_ASSIGNEDV4ADDRESS = "INTEGER";
-
-        public static final String COLNAME_ASSIGNEDV4ADDRESSEXPIRY = "assignedV4AddressExpiry";
-        // The lease expiry timestamp in uint of milliseconds
-        public static final String COLTYPE_ASSIGNEDV4ADDRESSEXPIRY = "BIGINT";
-
-        // Please note that the group hint is only a *hint*, hence its name. The client can offer
-        // this information to nudge the grouping in the decision it thinks is right, but it can't
-        // decide for the memory store what is the same L3 network.
-        public static final String COLNAME_GROUPHINT = "groupHint";
-        public static final String COLTYPE_GROUPHINT = "TEXT";
-
-        public static final String COLNAME_DNSADDRESSES = "dnsAddresses";
-        // Stored in marshalled form as is
-        public static final String COLTYPE_DNSADDRESSES = "BLOB";
-
-        public static final String COLNAME_MTU = "mtu";
-        public static final String COLTYPE_MTU = "INTEGER DEFAULT -1";
-
-        public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
-                + TABLENAME                       + " ("
-                + COLNAME_L2KEY                   + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, "
-                + COLNAME_EXPIRYDATE              + " " + COLTYPE_EXPIRYDATE              + ", "
-                + COLNAME_ASSIGNEDV4ADDRESS       + " " + COLTYPE_ASSIGNEDV4ADDRESS       + ", "
-                + COLNAME_ASSIGNEDV4ADDRESSEXPIRY + " " + COLTYPE_ASSIGNEDV4ADDRESSEXPIRY + ", "
-                + COLNAME_GROUPHINT               + " " + COLTYPE_GROUPHINT               + ", "
-                + COLNAME_DNSADDRESSES            + " " + COLTYPE_DNSADDRESSES            + ", "
-                + COLNAME_MTU                     + " " + COLTYPE_MTU                     + ")";
-        public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
-    }
-
-    /**
-     * Contract class for the Private Data table.
-     */
-    public static class PrivateDataContract {
-        public static final String TABLENAME = "PrivateData";
-
-        public static final String COLNAME_L2KEY = "l2Key";
-        public static final String COLTYPE_L2KEY = "TEXT NOT NULL";
-
-        public static final String COLNAME_CLIENT = "client";
-        public static final String COLTYPE_CLIENT = "TEXT NOT NULL";
-
-        public static final String COLNAME_DATANAME = "dataName";
-        public static final String COLTYPE_DATANAME = "TEXT NOT NULL";
-
-        public static final String COLNAME_DATA = "data";
-        public static final String COLTYPE_DATA = "BLOB NOT NULL";
-
-        public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
-                + TABLENAME        + " ("
-                + COLNAME_L2KEY    + " " + COLTYPE_L2KEY    + ", "
-                + COLNAME_CLIENT   + " " + COLTYPE_CLIENT   + ", "
-                + COLNAME_DATANAME + " " + COLTYPE_DATANAME + ", "
-                + COLNAME_DATA     + " " + COLTYPE_DATA     + ", "
-                + "PRIMARY KEY ("
-                + COLNAME_L2KEY    + ", "
-                + COLNAME_CLIENT   + ", "
-                + COLNAME_DATANAME + "))";
-        public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
-    }
-
-    // To save memory when the DB is not used, close it after 30s of inactivity. This is
-    // determined manually based on what feels right.
-    private static final long IDLE_CONNECTION_TIMEOUT_MS = 30_000;
-
-    /** The SQLite DB helper */
-    public static class DbHelper extends SQLiteOpenHelper {
-        // Update this whenever changing the schema.
-        private static final int SCHEMA_VERSION = 4;
-        private static final String DATABASE_FILENAME = "IpMemoryStore.db";
-        private static final String TRIGGER_NAME = "delete_cascade_to_private";
-
-        public DbHelper(@NonNull final Context context) {
-            super(context, DATABASE_FILENAME, null, SCHEMA_VERSION);
-            setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
-        }
-
-        /** Called when the database is created */
-        @Override
-        public void onCreate(@NonNull final SQLiteDatabase db) {
-            db.execSQL(NetworkAttributesContract.CREATE_TABLE);
-            db.execSQL(PrivateDataContract.CREATE_TABLE);
-            createTrigger(db);
-        }
-
-        /** Called when the database is upgraded */
-        @Override
-        public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
-                final int newVersion) {
-            try {
-                if (oldVersion < 2) {
-                    // upgrade from version 1 to version 2
-                    // since we starts from version 2, do nothing here
-                }
-
-                if (oldVersion < 3) {
-                    // upgrade from version 2 to version 3
-                    final String sqlUpgradeAddressExpiry = "alter table"
-                            + " " + NetworkAttributesContract.TABLENAME + " ADD"
-                            + " " + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY
-                            + " " + NetworkAttributesContract.COLTYPE_ASSIGNEDV4ADDRESSEXPIRY;
-                    db.execSQL(sqlUpgradeAddressExpiry);
-                }
-
-                if (oldVersion < 4) {
-                    createTrigger(db);
-                }
-            } catch (SQLiteException e) {
-                Log.e(TAG, "Could not upgrade to the new version", e);
-                // create database with new version
-                db.execSQL(NetworkAttributesContract.DROP_TABLE);
-                db.execSQL(PrivateDataContract.DROP_TABLE);
-                onCreate(db);
-            }
-        }
-
-        /** Called when the database is downgraded */
-        @Override
-        public void onDowngrade(@NonNull final SQLiteDatabase db, final int oldVersion,
-                final int newVersion) {
-            // Downgrades always nuke all data and recreate an empty table.
-            db.execSQL(NetworkAttributesContract.DROP_TABLE);
-            db.execSQL(PrivateDataContract.DROP_TABLE);
-            db.execSQL("DROP TRIGGER " + TRIGGER_NAME);
-            onCreate(db);
-        }
-
-        private void createTrigger(@NonNull final SQLiteDatabase db) {
-            final String createTrigger = "CREATE TRIGGER " + TRIGGER_NAME
-                    + " DELETE ON " + NetworkAttributesContract.TABLENAME
-                    + " BEGIN"
-                    + " DELETE FROM " + PrivateDataContract.TABLENAME + " WHERE OLD."
-                    + NetworkAttributesContract.COLNAME_L2KEY
-                    + "=" + PrivateDataContract.COLNAME_L2KEY
-                    + "; END;";
-            db.execSQL(createTrigger);
-        }
-    }
-
-    @NonNull
-    private static byte[] encodeAddressList(@NonNull final List<InetAddress> addresses) {
-        final ByteArrayOutputStream os = new ByteArrayOutputStream();
-        for (final InetAddress address : addresses) {
-            final byte[] b = address.getAddress();
-            os.write(b.length);
-            os.write(b, 0, b.length);
-        }
-        return os.toByteArray();
-    }
-
-    @NonNull
-    private static ArrayList<InetAddress> decodeAddressList(@NonNull final byte[] encoded) {
-        final ByteArrayInputStream is = new ByteArrayInputStream(encoded);
-        final ArrayList<InetAddress> addresses = new ArrayList<>();
-        int d = -1;
-        while ((d = is.read()) != -1) {
-            final byte[] bytes = new byte[d];
-            is.read(bytes, 0, d);
-            try {
-                addresses.add(InetAddress.getByAddress(bytes));
-            } catch (UnknownHostException e) { /* Hopefully impossible */ }
-        }
-        return addresses;
-    }
-
-    @NonNull
-    private static ContentValues toContentValues(@Nullable final NetworkAttributes attributes) {
-        final ContentValues values = new ContentValues();
-        if (null == attributes) return values;
-        if (null != attributes.assignedV4Address) {
-            values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
-                    inet4AddressToIntHTH(attributes.assignedV4Address));
-        }
-        if (null != attributes.assignedV4AddressExpiry) {
-            values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY,
-                    attributes.assignedV4AddressExpiry);
-        }
-        if (null != attributes.groupHint) {
-            values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
-        }
-        if (null != attributes.dnsAddresses) {
-            values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
-                    encodeAddressList(attributes.dnsAddresses));
-        }
-        if (null != attributes.mtu) {
-            values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
-        }
-        return values;
-    }
-
-    // Convert a NetworkAttributes object to content values to store them in a table compliant
-    // with the contract defined in NetworkAttributesContract.
-    @NonNull
-    private static ContentValues toContentValues(@NonNull final String key,
-            @Nullable final NetworkAttributes attributes, final long expiry) {
-        final ContentValues values = toContentValues(attributes);
-        values.put(NetworkAttributesContract.COLNAME_L2KEY, key);
-        values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry);
-        return values;
-    }
-
-    // Convert a byte array into content values to store it in a table compliant with the
-    // contract defined in PrivateDataContract.
-    @NonNull
-    private static ContentValues toContentValues(@NonNull final String key,
-            @NonNull final String clientId, @NonNull final String name,
-            @NonNull final byte[] data) {
-        final ContentValues values = new ContentValues();
-        values.put(PrivateDataContract.COLNAME_L2KEY, key);
-        values.put(PrivateDataContract.COLNAME_CLIENT, clientId);
-        values.put(PrivateDataContract.COLNAME_DATANAME, name);
-        values.put(PrivateDataContract.COLNAME_DATA, data);
-        return values;
-    }
-
-    @Nullable
-    private static NetworkAttributes readNetworkAttributesLine(@NonNull final Cursor cursor) {
-        // Make sure the data hasn't expired
-        final long expiry = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, -1L);
-        if (expiry < System.currentTimeMillis()) return null;
-
-        final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
-        final int assignedV4AddressInt = getInt(cursor,
-                NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
-        final long assignedV4AddressExpiry = getLong(cursor,
-                NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY, 0);
-        final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
-        final byte[] dnsAddressesBlob =
-                getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
-        final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
-        if (0 != assignedV4AddressInt) {
-            builder.setAssignedV4Address(intToInet4AddressHTH(assignedV4AddressInt));
-        }
-        if (0 != assignedV4AddressExpiry) {
-            builder.setAssignedV4AddressExpiry(assignedV4AddressExpiry);
-        }
-        builder.setGroupHint(groupHint);
-        if (null != dnsAddressesBlob) {
-            builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
-        }
-        if (mtu >= 0) {
-            builder.setMtu(mtu);
-        }
-        return builder.build();
-    }
-
-    private static final String[] EXPIRY_COLUMN = new String[] {
-        NetworkAttributesContract.COLNAME_EXPIRYDATE
-    };
-    static final int EXPIRY_ERROR = -1; // Legal values for expiry are positive
-
-    static final String SELECT_L2KEY = NetworkAttributesContract.COLNAME_L2KEY + " = ?";
-
-    // Returns the expiry date of the specified row, or one of the error codes above if the
-    // row is not found or some other error
-    static long getExpiry(@NonNull final SQLiteDatabase db, @NonNull final String key) {
-        final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
-                EXPIRY_COLUMN, // columns
-                SELECT_L2KEY, // selection
-                new String[] { key }, // selectionArgs
-                null, // groupBy
-                null, // having
-                null // orderBy
-        );
-        // L2KEY is the primary key ; it should not be possible to get more than one
-        // result here. 0 results means the key was not found.
-        if (cursor.getCount() != 1) return EXPIRY_ERROR;
-        cursor.moveToFirst();
-        final long result = cursor.getLong(0); // index in the EXPIRY_COLUMN array
-        cursor.close();
-        return result;
-    }
-
-    static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive
-
-    // Returns the relevance of the specified row, or one of the error codes above if the
-    // row is not found or some other error
-    static int getRelevance(@NonNull final SQLiteDatabase db, @NonNull final String key) {
-        final long expiry = getExpiry(db, key);
-        return expiry < 0 ? (int) expiry : RelevanceUtils.computeRelevanceForNow(expiry);
-    }
-
-    // If the attributes are null, this will only write the expiry.
-    // Returns an int out of Status.{SUCCESS, ERROR_*}
-    static int storeNetworkAttributes(@NonNull final SQLiteDatabase db, @NonNull final String key,
-            final long expiry, @Nullable final NetworkAttributes attributes) {
-        final ContentValues cv = toContentValues(key, attributes, expiry);
-        db.beginTransaction();
-        try {
-            // Unfortunately SQLite does not have any way to do INSERT OR UPDATE. Options are
-            // to either insert with on conflict ignore then update (like done here), or to
-            // construct a custom SQL INSERT statement with nested select.
-            final long resultId = db.insertWithOnConflict(NetworkAttributesContract.TABLENAME,
-                    null, cv, SQLiteDatabase.CONFLICT_IGNORE);
-            if (resultId < 0) {
-                db.update(NetworkAttributesContract.TABLENAME, cv, SELECT_L2KEY, new String[]{key});
-            }
-            db.setTransactionSuccessful();
-            return Status.SUCCESS;
-        } catch (SQLiteException e) {
-            // No space left on disk or something
-            Log.e(TAG, "Could not write to the memory store", e);
-        } finally {
-            db.endTransaction();
-        }
-        return Status.ERROR_STORAGE;
-    }
-
-    // Returns an int out of Status.{SUCCESS, ERROR_*}
-    static int storeBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
-            @NonNull final String clientId, @NonNull final String name,
-            @NonNull final byte[] data) {
-        final long res = db.insertWithOnConflict(PrivateDataContract.TABLENAME, null,
-                toContentValues(key, clientId, name, data), SQLiteDatabase.CONFLICT_REPLACE);
-        return (res == -1) ? Status.ERROR_STORAGE : Status.SUCCESS;
-    }
-
-    @Nullable
-    static NetworkAttributes retrieveNetworkAttributes(@NonNull final SQLiteDatabase db,
-            @NonNull final String key) {
-        final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
-                null, // columns, null means everything
-                NetworkAttributesContract.COLNAME_L2KEY + " = ?", // selection
-                new String[] { key }, // selectionArgs
-                null, // groupBy
-                null, // having
-                null); // orderBy
-        // L2KEY is the primary key ; it should not be possible to get more than one
-        // result here. 0 results means the key was not found.
-        if (cursor.getCount() != 1) return null;
-        cursor.moveToFirst();
-        final NetworkAttributes attributes = readNetworkAttributesLine(cursor);
-        cursor.close();
-        return attributes;
-    }
-
-    private static final String[] DATA_COLUMN = new String[] {
-            PrivateDataContract.COLNAME_DATA
-    };
-    @Nullable
-    static byte[] retrieveBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
-            @NonNull final String clientId, @NonNull final String name) {
-        final Cursor cursor = db.query(PrivateDataContract.TABLENAME,
-                DATA_COLUMN, // columns
-                PrivateDataContract.COLNAME_L2KEY + " = ? AND " // selection
-                + PrivateDataContract.COLNAME_CLIENT + " = ? AND "
-                + PrivateDataContract.COLNAME_DATANAME + " = ?",
-                new String[] { key, clientId, name }, // selectionArgs
-                null, // groupBy
-                null, // having
-                null); // orderBy
-        // The query above is querying by (composite) primary key, so it should not be possible to
-        // get more than one result here. 0 results means the key was not found.
-        if (cursor.getCount() != 1) return null;
-        cursor.moveToFirst();
-        final byte[] result = cursor.getBlob(0); // index in the DATA_COLUMN array
-        cursor.close();
-        return result;
-    }
-
-    /**
-     * The following is a horrible hack that is necessary because the Android SQLite API does not
-     * have a way to query a binary blob. This, almost certainly, is an overlook.
-     *
-     * The Android SQLite API has two family of methods : one for query that returns data, and
-     * one for more general SQL statements that can execute any statement but may not return
-     * anything. All the query methods, however, take only String[] for the arguments.
-     *
-     * In principle it is simple to write a function that will encode the binary blob in the
-     * way SQLite expects it. However, because the API forces the argument to be coerced into a
-     * String, the SQLiteQuery object generated by the default query methods will bind all
-     * arguments as Strings and SQL will *sanitize* them. This works okay for numeric types,
-     * but the format for blobs is x'<hex string>'. Note the presence of quotes, which will
-     * be sanitized, changing the contents of the field, and the query will fail to match the
-     * blob.
-     *
-     * As far as I can tell, there are two possible ways around this problem. The first one
-     * is to put the data in the query string and eschew it being an argument. This would
-     * require doing the sanitizing by hand. The other is to call bindBlob directly on the
-     * generated SQLiteQuery object, which not only is a lot less dangerous than rolling out
-     * sanitizing, but also will do the right thing if the underlying format ever changes.
-     *
-     * But none of the methods that take an SQLiteQuery object can return data ; this *must*
-     * be called with SQLiteDatabase#query. This object is not accessible from outside.
-     * However, there is a #query version that accepts a CursorFactory and this is pretty
-     * straightforward to implement as all the arguments are coming in and the SQLiteCursor
-     * class is public API.
-     * With this, it's possible to intercept the SQLiteQuery object, and assuming the args
-     * are available, to bind them directly and work around the API's oblivious coercion into
-     * Strings.
-     *
-     * This is really sad, but I don't see another way of having this work than this or the
-     * hand-rolled sanitizing, and this is the lesser evil.
-     */
-    private static class CustomCursorFactory implements SQLiteDatabase.CursorFactory {
-        @NonNull
-        private final ArrayList<Object> mArgs;
-        CustomCursorFactory(@NonNull final ArrayList<Object> args) {
-            mArgs = args;
-        }
-        @Override
-        public Cursor newCursor(final SQLiteDatabase db, final SQLiteCursorDriver masterQuery,
-                final String editTable,
-                final SQLiteQuery query) {
-            int index = 1; // bind is 1-indexed
-            for (final Object arg : mArgs) {
-                if (arg instanceof String) {
-                    query.bindString(index++, (String) arg);
-                } else if (arg instanceof Long) {
-                    query.bindLong(index++, (Long) arg);
-                } else if (arg instanceof Integer) {
-                    query.bindLong(index++, Long.valueOf((Integer) arg));
-                } else if (arg instanceof byte[]) {
-                    query.bindBlob(index++, (byte[]) arg);
-                } else {
-                    throw new IllegalStateException("Unsupported type CustomCursorFactory "
-                            + arg.getClass().toString());
-                }
-            }
-            return new SQLiteCursor(masterQuery, editTable, query);
-        }
-    }
-
-    // Returns the l2key of the closest match, if and only if it matches
-    // closely enough (as determined by group-closeness).
-    @Nullable
-    static String findClosestAttributes(@NonNull final SQLiteDatabase db,
-            @NonNull final NetworkAttributes attr) {
-        if (attr.isEmpty()) return null;
-        final ContentValues values = toContentValues(attr);
-
-        // Build the selection and args. To cut down on the number of lines to search, limit
-        // the search to those with at least one argument equals to the requested attributes.
-        // This works only because null attributes match only will not result in group-closeness.
-        final StringJoiner sj = new StringJoiner(" OR ");
-        final ArrayList<Object> args = new ArrayList<>();
-        args.add(System.currentTimeMillis());
-        for (final String field : values.keySet()) {
-            sj.add(field + " = ?");
-            args.add(values.get(field));
-        }
-
-        final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND ("
-                + sj.toString() + ")";
-        final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args),
-                false, // distinct
-                NetworkAttributesContract.TABLENAME,
-                null, // columns, null means everything
-                selection, // selection
-                null, // selectionArgs, horrendously passed to the cursor factory instead
-                null, // groupBy
-                null, // having
-                null, // orderBy
-                null); // limit
-        if (cursor.getCount() <= 0) return null;
-        cursor.moveToFirst();
-        String bestKey = null;
-        float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this.
-        while (!cursor.isAfterLast()) {
-            final NetworkAttributes read = readNetworkAttributesLine(cursor);
-            final float confidence = read.getNetworkGroupSamenessConfidence(attr);
-            if (confidence > bestMatchConfidence) {
-                bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY);
-                bestMatchConfidence = confidence;
-            }
-            cursor.moveToNext();
-        }
-        cursor.close();
-        return bestKey;
-    }
-
-    // Drops all records that are expired. Relevance has decayed to zero of these records. Returns
-    // an int out of Status.{SUCCESS, ERROR_*}
-    static int dropAllExpiredRecords(@NonNull final SQLiteDatabase db) {
-        db.beginTransaction();
-        try {
-            // Deletes NetworkAttributes that have expired.
-            db.delete(NetworkAttributesContract.TABLENAME,
-                    NetworkAttributesContract.COLNAME_EXPIRYDATE + " < ?",
-                    new String[]{Long.toString(System.currentTimeMillis())});
-            db.setTransactionSuccessful();
-        } catch (SQLiteException e) {
-            Log.e(TAG, "Could not delete data from memory store", e);
-            return Status.ERROR_STORAGE;
-        } finally {
-            db.endTransaction();
-        }
-
-        // Execute vacuuming here if above operation has no exception. If above operation got
-        // exception, vacuuming can be ignored for reducing unnecessary consumption.
-        try {
-            db.execSQL("VACUUM");
-        } catch (SQLiteException e) {
-            // Do nothing.
-        }
-        return Status.SUCCESS;
-    }
-
-    // Drops number of records that start from the lowest expiryDate. Returns an int out of
-    // Status.{SUCCESS, ERROR_*}
-    static int dropNumberOfRecords(@NonNull final SQLiteDatabase db, int number) {
-        if (number <= 0) {
-            return Status.ERROR_ILLEGAL_ARGUMENT;
-        }
-
-        // Queries number of NetworkAttributes that start from the lowest expiryDate.
-        final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
-                new String[] {NetworkAttributesContract.COLNAME_EXPIRYDATE}, // columns
-                null, // selection
-                null, // selectionArgs
-                null, // groupBy
-                null, // having
-                NetworkAttributesContract.COLNAME_EXPIRYDATE, // orderBy
-                Integer.toString(number)); // limit
-        if (cursor == null || cursor.getCount() <= 0) return Status.ERROR_GENERIC;
-        cursor.moveToLast();
-
-        //Get the expiryDate from last record.
-        final long expiryDate = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, 0);
-        cursor.close();
-
-        db.beginTransaction();
-        try {
-            // Deletes NetworkAttributes that expiryDate are lower than given value.
-            db.delete(NetworkAttributesContract.TABLENAME,
-                    NetworkAttributesContract.COLNAME_EXPIRYDATE + " <= ?",
-                    new String[]{Long.toString(expiryDate)});
-            db.setTransactionSuccessful();
-        } catch (SQLiteException e) {
-            Log.e(TAG, "Could not delete data from memory store", e);
-            return Status.ERROR_STORAGE;
-        } finally {
-            db.endTransaction();
-        }
-
-        // Execute vacuuming here if above operation has no exception. If above operation got
-        // exception, vacuuming can be ignored for reducing unnecessary consumption.
-        try {
-            db.execSQL("VACUUM");
-        } catch (SQLiteException e) {
-            // Do nothing.
-        }
-        return Status.SUCCESS;
-    }
-
-    static int getTotalRecordNumber(@NonNull final SQLiteDatabase db) {
-        // Query the total number of NetworkAttributes
-        final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
-                new String[] {"COUNT(*)"}, // columns
-                null, // selection
-                null, // selectionArgs
-                null, // groupBy
-                null, // having
-                null); // orderBy
-        cursor.moveToFirst();
-        return cursor == null ? 0 : cursor.getInt(0);
-    }
-
-    // Helper methods
-    private static String getString(final Cursor cursor, final String columnName) {
-        final int columnIndex = cursor.getColumnIndex(columnName);
-        return (columnIndex >= 0) ? cursor.getString(columnIndex) : null;
-    }
-    private static byte[] getBlob(final Cursor cursor, final String columnName) {
-        final int columnIndex = cursor.getColumnIndex(columnName);
-        return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null;
-    }
-    private static int getInt(final Cursor cursor, final String columnName,
-            final int defaultValue) {
-        final int columnIndex = cursor.getColumnIndex(columnName);
-        return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue;
-    }
-    private static long getLong(final Cursor cursor, final String columnName,
-            final long defaultValue) {
-        final int columnIndex = cursor.getColumnIndex(columnName);
-        return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue;
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
deleted file mode 100644
index 8312dfe..0000000
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
+++ /dev/null
@@ -1,506 +0,0 @@
-/*
- * 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.connectivity.ipmemorystore;
-
-import static android.net.ipmemorystore.Status.ERROR_DATABASE_CANNOT_BE_OPENED;
-import static android.net.ipmemorystore.Status.ERROR_GENERIC;
-import static android.net.ipmemorystore.Status.ERROR_ILLEGAL_ARGUMENT;
-import static android.net.ipmemorystore.Status.SUCCESS;
-
-import static com.android.server.connectivity.ipmemorystore.IpMemoryStoreDatabase.EXPIRY_ERROR;
-import static com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService.InterruptMaintenance;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.IIpMemoryStore;
-import android.net.ipmemorystore.Blob;
-import android.net.ipmemorystore.IOnBlobRetrievedListener;
-import android.net.ipmemorystore.IOnL2KeyResponseListener;
-import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
-import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
-import android.net.ipmemorystore.IOnStatusListener;
-import android.net.ipmemorystore.NetworkAttributes;
-import android.net.ipmemorystore.NetworkAttributesParcelable;
-import android.net.ipmemorystore.SameL3NetworkResponse;
-import android.net.ipmemorystore.Status;
-import android.net.ipmemorystore.StatusParcelable;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.File;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * Implementation for the IP memory store.
- * This component offers specialized services for network components to store and retrieve
- * knowledge about networks, and provides intelligence that groups level 2 networks together
- * into level 3 networks.
- *
- * @hide
- */
-public class IpMemoryStoreService extends IIpMemoryStore.Stub {
-    private static final String TAG = IpMemoryStoreService.class.getSimpleName();
-    private static final int MAX_CONCURRENT_THREADS = 4;
-    private static final int DATABASE_SIZE_THRESHOLD = 10 * 1024 * 1024; //10MB
-    private static final int MAX_DROP_RECORD_TIMES = 500;
-    private static final int MIN_DELETE_NUM = 5;
-    private static final boolean DBG = true;
-
-    // Error codes below are internal and used for notifying status beteween IpMemoryStore modules.
-    static final int ERROR_INTERNAL_BASE = -1_000_000_000;
-    // This error code is used for maintenance only to notify RegularMaintenanceJobService that
-    // full maintenance job has been interrupted.
-    static final int ERROR_INTERNAL_INTERRUPTED = ERROR_INTERNAL_BASE - 1;
-
-    @NonNull
-    final Context mContext;
-    @Nullable
-    final SQLiteDatabase mDb;
-    @NonNull
-    final ExecutorService mExecutor;
-
-    /**
-     * Construct an IpMemoryStoreService object.
-     * This constructor will block on disk access to open the database.
-     * @param context the context to access storage with.
-     */
-    public IpMemoryStoreService(@NonNull final Context context) {
-        // Note that constructing the service will access the disk and block
-        // for some time, but it should make no difference to the clients. Because
-        // the interface is one-way, clients fire and forget requests, and the callback
-        // will get called eventually in any case, and the framework will wait for the
-        // service to be created to deliver subsequent requests.
-        // Avoiding this would mean the mDb member can't be final, which means the service would
-        // have to test for nullity, care for failure, and allow for a wait at every single access,
-        // which would make the code a lot more complex and require all methods to possibly block.
-        mContext = context;
-        SQLiteDatabase db;
-        final IpMemoryStoreDatabase.DbHelper helper = new IpMemoryStoreDatabase.DbHelper(context);
-        try {
-            db = helper.getWritableDatabase();
-            if (null == db) Log.e(TAG, "Unexpected null return of getWriteableDatabase");
-        } catch (final SQLException e) {
-            Log.e(TAG, "Can't open the Ip Memory Store database", e);
-            db = null;
-        } catch (final Exception e) {
-            Log.wtf(TAG, "Impossible exception Ip Memory Store database", e);
-            db = null;
-        }
-        mDb = db;
-        // The work-stealing thread pool executor will spawn threads as needed up to
-        // the max only when there is no free thread available. This generally behaves
-        // exactly like one would expect it intuitively :
-        // - When work arrives, it will spawn a new thread iff there are no available threads
-        // - When there is no work to do it will shutdown threads after a while (the while
-        //   being equal to 2 seconds (not configurable) when max threads are spun up and
-        //   twice as much for every one less thread)
-        // - When all threads are busy the work is enqueued and waits for any worker
-        //   to become available.
-        // Because the stealing pool is made for very heavily parallel execution of
-        // small tasks that spawn others, it creates a queue per thread that in this
-        // case is overhead. However, the three behaviors above make it a superior
-        // choice to cached or fixedThreadPoolExecutor, neither of which can actually
-        // enqueue a task waiting for a thread to be free. This can probably be solved
-        // with judicious subclassing of ThreadPoolExecutor, but that's a lot of dangerous
-        // complexity for little benefit in this case.
-        mExecutor = Executors.newWorkStealingPool(MAX_CONCURRENT_THREADS);
-        RegularMaintenanceJobService.schedule(mContext, this);
-    }
-
-    /**
-     * Shutdown the memory store service, cancelling running tasks and dropping queued tasks.
-     *
-     * This is provided to give a way to clean up, and is meant to be available in case of an
-     * emergency shutdown.
-     */
-    public void shutdown() {
-        // By contrast with ExecutorService#shutdown, ExecutorService#shutdownNow tries
-        // to cancel the existing tasks, and does not wait for completion. It does not
-        // guarantee the threads can be terminated in any given amount of time.
-        mExecutor.shutdownNow();
-        if (mDb != null) mDb.close();
-        RegularMaintenanceJobService.unschedule(mContext);
-    }
-
-    /** Helper function to make a status object */
-    private StatusParcelable makeStatus(final int code) {
-        return new Status(code).toParcelable();
-    }
-
-    /**
-     * Store network attributes for a given L2 key.
-     *
-     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
-     *              key and only care about grouping can pass a unique ID here like the ones
-     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
-     *              relevance of such a network will lead to it being evicted soon if it's not
-     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
-     * @param attributes The attributes for this network.
-     * @param listener A listener to inform of the completion of this call, or null if the client
-     *        is not interested in learning about success/failure.
-     * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
-     * If the call failed, the L2 key will be null.
-     */
-    // Note that while l2Key and attributes are non-null in spirit, they are received from
-    // another process. If the remote process decides to ignore everything and send null, this
-    // process should still not crash.
-    @Override
-    public void storeNetworkAttributes(@Nullable final String l2Key,
-            @Nullable final NetworkAttributesParcelable attributes,
-            @Nullable final IOnStatusListener listener) {
-        // Because the parcelable is 100% mutable, the thread may not see its members initialized.
-        // Therefore either an immutable object is created on this same thread before it's passed
-        // to the executor, or there need to be a write barrier here and a read barrier in the
-        // remote thread.
-        final NetworkAttributes na = null == attributes ? null : new NetworkAttributes(attributes);
-        mExecutor.execute(() -> {
-            try {
-                final int code = storeNetworkAttributesAndBlobSync(l2Key, na,
-                        null /* clientId */, null /* name */, null /* data */);
-                if (null != listener) listener.onComplete(makeStatus(code));
-            } catch (final RemoteException e) {
-                // Client at the other end died
-            }
-        });
-    }
-
-    /**
-     * Store a binary blob associated with an L2 key and a name.
-     *
-     * @param l2Key The L2 key for this network.
-     * @param clientId The ID of the client.
-     * @param name The name of this data.
-     * @param blob The data to store.
-     * @param listener The listener that will be invoked to return the answer, or null if the
-     *        is not interested in learning about success/failure.
-     * Through the listener, returns a status to indicate success or failure.
-     */
-    @Override
-    public void storeBlob(@Nullable final String l2Key, @Nullable final String clientId,
-            @Nullable final String name, @Nullable final Blob blob,
-            @Nullable final IOnStatusListener listener) {
-        final byte[] data = null == blob ? null : blob.data;
-        mExecutor.execute(() -> {
-            try {
-                final int code = storeNetworkAttributesAndBlobSync(l2Key,
-                        null /* NetworkAttributes */, clientId, name, data);
-                if (null != listener) listener.onComplete(makeStatus(code));
-            } catch (final RemoteException e) {
-                // Client at the other end died
-            }
-        });
-    }
-
-    /**
-     * Helper method for storeNetworkAttributes and storeBlob.
-     *
-     * Either attributes or none of clientId, name and data may be null. This will write the
-     * passed data if non-null, and will write attributes if non-null, but in any case it will
-     * bump the relevance up.
-     * Returns a success code from Status.
-     */
-    private int storeNetworkAttributesAndBlobSync(@Nullable final String l2Key,
-            @Nullable final NetworkAttributes attributes,
-            @Nullable final String clientId,
-            @Nullable final String name, @Nullable final byte[] data) {
-        if (null == l2Key) return ERROR_ILLEGAL_ARGUMENT;
-        if (null == attributes && null == data) return ERROR_ILLEGAL_ARGUMENT;
-        if (null != data && (null == clientId || null == name)) return ERROR_ILLEGAL_ARGUMENT;
-        if (null == mDb) return ERROR_DATABASE_CANNOT_BE_OPENED;
-        try {
-            final long oldExpiry = IpMemoryStoreDatabase.getExpiry(mDb, l2Key);
-            final long newExpiry = RelevanceUtils.bumpExpiryDate(
-                    oldExpiry == EXPIRY_ERROR ? System.currentTimeMillis() : oldExpiry);
-            final int errorCode =
-                    IpMemoryStoreDatabase.storeNetworkAttributes(mDb, l2Key, newExpiry, attributes);
-            // If no blob to store, the client is interested in the result of storing the attributes
-            if (null == data) return errorCode;
-            // Otherwise it's interested in the result of storing the blob
-            return IpMemoryStoreDatabase.storeBlob(mDb, l2Key, clientId, name, data);
-        } catch (Exception e) {
-            if (DBG) {
-                Log.e(TAG, "Exception while storing for key {" + l2Key
-                        + "} ; NetworkAttributes {" + (null == attributes ? "null" : attributes)
-                        + "} ; clientId {" + (null == clientId ? "null" : clientId)
-                        + "} ; name {" + (null == name ? "null" : name)
-                        + "} ; data {" + Utils.byteArrayToString(data) + "}", e);
-            }
-        }
-        return ERROR_GENERIC;
-    }
-
-    /**
-     * Returns the best L2 key associated with the attributes.
-     *
-     * This will find a record that would be in the same group as the passed attributes. This is
-     * useful to choose the key for storing a sample or private data when the L2 key is not known.
-     * If multiple records are group-close to these attributes, the closest match is returned.
-     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
-     * order) L2 key is returned.
-     * If no record matches these attributes, null is returned.
-     *
-     * @param attributes The attributes of the network to find.
-     * @param listener The listener that will be invoked to return the answer.
-     * Through the listener, returns the L2 key if one matched, or null.
-     */
-    @Override
-    public void findL2Key(@Nullable final NetworkAttributesParcelable attributes,
-            @Nullable final IOnL2KeyResponseListener listener) {
-        if (null == listener) return;
-        mExecutor.execute(() -> {
-            try {
-                if (null == attributes) {
-                    listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
-                    return;
-                }
-                if (null == mDb) {
-                    listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
-                    return;
-                }
-                final String key = IpMemoryStoreDatabase.findClosestAttributes(mDb,
-                        new NetworkAttributes(attributes));
-                listener.onL2KeyResponse(makeStatus(SUCCESS), key);
-            } catch (final RemoteException e) {
-                // Client at the other end died
-            }
-        });
-    }
-
-    /**
-     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
-     * to the same L3 network. Group-closeness is used to determine this.
-     *
-     * @param l2Key1 The key for the first network.
-     * @param l2Key2 The key for the second network.
-     * @param listener The listener that will be invoked to return the answer.
-     * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
-     */
-    @Override
-    public void isSameNetwork(@Nullable final String l2Key1, @Nullable final String l2Key2,
-            @Nullable final IOnSameL3NetworkResponseListener listener) {
-        if (null == listener) return;
-        mExecutor.execute(() -> {
-            try {
-                if (null == l2Key1 || null == l2Key2) {
-                    listener.onSameL3NetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
-                    return;
-                }
-                if (null == mDb) {
-                    listener.onSameL3NetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
-                    return;
-                }
-                try {
-                    final NetworkAttributes attr1 =
-                            IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key1);
-                    final NetworkAttributes attr2 =
-                            IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key2);
-                    if (null == attr1 || null == attr2) {
-                        listener.onSameL3NetworkResponse(makeStatus(SUCCESS),
-                                new SameL3NetworkResponse(l2Key1, l2Key2,
-                                        -1f /* never connected */).toParcelable());
-                        return;
-                    }
-                    final float confidence = attr1.getNetworkGroupSamenessConfidence(attr2);
-                    listener.onSameL3NetworkResponse(makeStatus(SUCCESS),
-                            new SameL3NetworkResponse(l2Key1, l2Key2, confidence).toParcelable());
-                } catch (Exception e) {
-                    listener.onSameL3NetworkResponse(makeStatus(ERROR_GENERIC), null);
-                }
-            } catch (final RemoteException e) {
-                // Client at the other end died
-            }
-        });
-    }
-
-    /**
-     * Retrieve the network attributes for a key.
-     * If no record is present for this key, this will return null attributes.
-     *
-     * @param l2Key The key of the network to query.
-     * @param listener The listener that will be invoked to return the answer.
-     * Through the listener, returns the network attributes and the L2 key associated with
-     *         the query.
-     */
-    @Override
-    public void retrieveNetworkAttributes(@Nullable final String l2Key,
-            @Nullable final IOnNetworkAttributesRetrievedListener listener) {
-        if (null == listener) return;
-        mExecutor.execute(() -> {
-            try {
-                if (null == l2Key) {
-                    listener.onNetworkAttributesRetrieved(
-                            makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
-                    return;
-                }
-                if (null == mDb) {
-                    listener.onNetworkAttributesRetrieved(
-                            makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, null);
-                    return;
-                }
-                try {
-                    final NetworkAttributes attributes =
-                            IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key);
-                    listener.onNetworkAttributesRetrieved(makeStatus(SUCCESS), l2Key,
-                            null == attributes ? null : attributes.toParcelable());
-                } catch (final Exception e) {
-                    listener.onNetworkAttributesRetrieved(makeStatus(ERROR_GENERIC), l2Key, null);
-                }
-            } catch (final RemoteException e) {
-                // Client at the other end died
-            }
-        });
-    }
-
-    /**
-     * Retrieve previously stored private data.
-     * If no data was stored for this L2 key and name this will return null.
-     *
-     * @param l2Key The L2 key.
-     * @param clientId The id of the client that stored this data.
-     * @param name The name of the data.
-     * @param listener The listener that will be invoked to return the answer.
-     * Through the listener, returns the private data if any or null if none, with the L2 key
-     *         and the name of the data associated with the query.
-     */
-    @Override
-    public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
-            @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
-        if (null == listener) return;
-        mExecutor.execute(() -> {
-            try {
-                if (null == l2Key) {
-                    listener.onBlobRetrieved(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, name, null);
-                    return;
-                }
-                if (null == mDb) {
-                    listener.onBlobRetrieved(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key,
-                            name, null);
-                    return;
-                }
-                try {
-                    final Blob b = new Blob();
-                    b.data = IpMemoryStoreDatabase.retrieveBlob(mDb, l2Key, clientId, name);
-                    listener.onBlobRetrieved(makeStatus(SUCCESS), l2Key, name, b);
-                } catch (final Exception e) {
-                    listener.onBlobRetrieved(makeStatus(ERROR_GENERIC), l2Key, name, null);
-                }
-            } catch (final RemoteException e) {
-                // Client at the other end died
-            }
-        });
-    }
-
-    @Override
-    public void factoryReset() {
-    }
-
-    /** Get db size threshold. */
-    @VisibleForTesting
-    protected int getDbSizeThreshold() {
-        return DATABASE_SIZE_THRESHOLD;
-    }
-
-    private long getDbSize() {
-        final File dbFile = new File(mDb.getPath());
-        try {
-            return dbFile.length();
-        } catch (final SecurityException e) {
-            if (DBG) Log.e(TAG, "Read db size access deny.", e);
-            // Return zero value if can't get disk usage exactly.
-            return 0;
-        }
-    }
-
-    /** Check if db size is over the threshold. */
-    @VisibleForTesting
-    boolean isDbSizeOverThreshold() {
-        return getDbSize() > getDbSizeThreshold();
-    }
-
-    /**
-     * Full maintenance.
-     *
-     * @param listener A listener to inform of the completion of this call.
-     */
-    void fullMaintenance(@NonNull final IOnStatusListener listener,
-            @NonNull final InterruptMaintenance interrupt) {
-        mExecutor.execute(() -> {
-            try {
-                if (null == mDb) {
-                    listener.onComplete(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED));
-                    return;
-                }
-
-                // Interrupt maintenance because the scheduling job has been canceled.
-                if (checkForInterrupt(listener, interrupt)) return;
-
-                int result = SUCCESS;
-                // Drop all records whose relevance has decayed to zero.
-                // This is the first step to decrease memory store size.
-                result = IpMemoryStoreDatabase.dropAllExpiredRecords(mDb);
-
-                if (checkForInterrupt(listener, interrupt)) return;
-
-                // Aggregate historical data in passes
-                // TODO : Waiting for historical data implement.
-
-                // Check if db size meets the storage goal(10MB). If not, keep dropping records and
-                // aggregate historical data until the storage goal is met. Use for loop with 500
-                // times restriction to prevent infinite loop (Deleting records always fail and db
-                // size is still over the threshold)
-                for (int i = 0; isDbSizeOverThreshold() && i < MAX_DROP_RECORD_TIMES; i++) {
-                    if (checkForInterrupt(listener, interrupt)) return;
-
-                    final int totalNumber = IpMemoryStoreDatabase.getTotalRecordNumber(mDb);
-                    final long dbSize = getDbSize();
-                    final float decreaseRate = (dbSize == 0)
-                            ? 0 : (float) (dbSize - getDbSizeThreshold()) / (float) dbSize;
-                    final int deleteNumber = Math.max(
-                            (int) (totalNumber * decreaseRate), MIN_DELETE_NUM);
-
-                    result = IpMemoryStoreDatabase.dropNumberOfRecords(mDb, deleteNumber);
-
-                    if (checkForInterrupt(listener, interrupt)) return;
-
-                    // Aggregate historical data
-                    // TODO : Waiting for historical data implement.
-                }
-                listener.onComplete(makeStatus(result));
-            } catch (final RemoteException e) {
-                // Client at the other end died
-            }
-        });
-    }
-
-    private boolean checkForInterrupt(@NonNull final IOnStatusListener listener,
-            @NonNull final InterruptMaintenance interrupt) throws RemoteException {
-        if (!interrupt.isInterrupted()) return false;
-        listener.onComplete(makeStatus(ERROR_INTERNAL_INTERRUPTED));
-        return true;
-    }
-
-    @Override
-    public int getInterfaceVersion() {
-        return this.VERSION;
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/RegularMaintenanceJobService.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/RegularMaintenanceJobService.java
deleted file mode 100644
index bea7052..0000000
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/RegularMaintenanceJobService.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity.ipmemorystore;
-
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.ipmemorystore.IOnStatusListener;
-import android.net.ipmemorystore.Status;
-import android.net.ipmemorystore.StatusParcelable;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Regular maintenance job service.
- * @hide
- */
-public final class RegularMaintenanceJobService extends JobService {
-    // Must be unique within the system server uid.
-    public static final int REGULAR_MAINTENANCE_ID = 3345678;
-
-    /**
-     * Class for interrupt check of maintenance job.
-     */
-    public static final class InterruptMaintenance {
-        private volatile boolean mIsInterrupted;
-        private final int mJobId;
-
-        public InterruptMaintenance(int jobId) {
-            mJobId = jobId;
-            mIsInterrupted = false;
-        }
-
-        public int getJobId() {
-            return mJobId;
-        }
-
-        public void setInterrupted(boolean interrupt) {
-            mIsInterrupted = interrupt;
-        }
-
-        public boolean isInterrupted() {
-            return mIsInterrupted;
-        }
-    }
-
-    private static final ArrayList<InterruptMaintenance> sInterruptList = new ArrayList<>();
-    private static IpMemoryStoreService sIpMemoryStoreService;
-
-    @Override
-    public boolean onStartJob(JobParameters params) {
-        if (sIpMemoryStoreService == null) {
-            Log.wtf("RegularMaintenanceJobService",
-                    "Can not start job because sIpMemoryStoreService is null.");
-            return false;
-        }
-        final InterruptMaintenance im = new InterruptMaintenance(params.getJobId());
-        sInterruptList.add(im);
-
-        sIpMemoryStoreService.fullMaintenance(new IOnStatusListener() {
-            @Override
-            public void onComplete(final StatusParcelable statusParcelable) throws RemoteException {
-                final Status result = new Status(statusParcelable);
-                if (!result.isSuccess()) {
-                    Log.e("RegularMaintenanceJobService", "Regular maintenance failed."
-                            + " Error is " + result.resultCode);
-                }
-                sInterruptList.remove(im);
-                jobFinished(params, !result.isSuccess());
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return null;
-            }
-        }, im);
-        return true;
-    }
-
-    @Override
-    public boolean onStopJob(JobParameters params) {
-        final int jobId = params.getJobId();
-        for (InterruptMaintenance im : sInterruptList) {
-            if (im.getJobId() == jobId) {
-                im.setInterrupted(true);
-            }
-        }
-        return true;
-    }
-
-    /** Schedule regular maintenance job */
-    static void schedule(Context context, IpMemoryStoreService ipMemoryStoreService) {
-        final JobScheduler jobScheduler =
-                (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
-
-        final ComponentName maintenanceJobName =
-                new ComponentName(context, RegularMaintenanceJobService.class);
-
-        // Regular maintenance is scheduled for when the device is idle with access power and a
-        // minimum interval of one day.
-        final JobInfo regularMaintenanceJob =
-                new JobInfo.Builder(REGULAR_MAINTENANCE_ID, maintenanceJobName)
-                        .setRequiresDeviceIdle(true)
-                        .setRequiresCharging(true)
-                        .setRequiresBatteryNotLow(true)
-                        .setPeriodic(TimeUnit.HOURS.toMillis(24)).build();
-
-        jobScheduler.schedule(regularMaintenanceJob);
-        sIpMemoryStoreService = ipMemoryStoreService;
-    }
-
-    /** Unschedule regular maintenance job */
-    static void unschedule(Context context) {
-        final JobScheduler jobScheduler =
-                (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
-        jobScheduler.cancel(REGULAR_MAINTENANCE_ID);
-        sIpMemoryStoreService = null;
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/RelevanceUtils.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/RelevanceUtils.java
deleted file mode 100644
index 38d5544..0000000
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/RelevanceUtils.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * 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.connectivity.ipmemorystore;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * A class containing the logic around the relevance value for
- * IP Memory Store.
- *
- * @hide
- */
-public class RelevanceUtils {
-    /**
-     * The relevance is a decaying value that gets lower and lower until it
-     * reaches 0 after some time passes. It follows an exponential decay law,
-     * dropping slowly at first then faster and faster, because a network is
-     * likely to be visited again if it was visited not long ago, and the longer
-     * it hasn't been visited the more likely it is that it won't be visited
-     * again. For example, a network visited on holiday should stay fresh for
-     * the duration of the holiday and persist for a while, but after the venue
-     * hasn't been visited for a while it should quickly be discarded. What
-     * should accelerate forgetting the network is extended periods without
-     * visits, so that occasional venues get discarded but regular visits keep
-     * the network relevant, even if the visits are infrequent.
-     *
-     * This function must be stable by iteration, meaning that adjusting the same value
-     * for different dates iteratively multiple times should give the same result.
-     * Formally, if f is the decay function that associates a relevance x at a date d1
-     * to the value at ulterior date d3, then for any date d2 between d1 and d3 :
-     * f(x, d3 - d1) = f(f(x, d3 - d2), d2 - d1). Intuitively, this property simply
-     * means it should be the same to compute and store back the value after two months,
-     * or to do it once after one month, store it back, and do it again after another
-     * months has passed.
-     * The pair of the relevance and date define the entire curve, so any pair
-     * of values on the curve will define the same curve. Setting one of them to a
-     * constant, so as not to have to store it, means the other one will always suffice
-     * to describe the curve. For example, only storing the date for a known, constant
-     * value of the relevance is an efficient way of remembering this information (and
-     * to compare relevances together, as f is monotonically decreasing).
-     *
-     *** Choosing the function :
-     * Functions of the kind described above are standard exponential decay functions
-     * like the ones that govern atomic decay where the value at any given date can be
-     * computed uniformly from the value at a previous date and the time elapsed since
-     * that date. It is simple to picture this kind of function as one where after a
-     * given period of time called the half-life, the relevance value will have been
-     * halved. Decay of this kind is expressed in function of the previous value by
-     * functions like
-     * f(x, t) = x * F ^ (t / L)
-     * ...where x is the value, t is the elapsed time, L is the half-life (or more
-     * generally the F-th-life) and F the decay factor (typically 0.5, hence why L is
-     * usually called the half-life). The ^ symbol here is used for exponentiation.
-     * Or, starting at a given M for t = 0 :
-     * f(t) = M * F ^ (t / L)
-     *
-     * Because a line in the store needs to become irrelevant at some point but
-     * this class of functions never go to 0, a minimum cutoff has to be chosen to
-     * represent irrelevance. The simpler way of doing this is to simply add this
-     * minimum cutoff to the computation before and removing it after.
-     * Thus the function becomes :
-     * f(x, t) = ((x + K) * F ^ (t / L)) - K
-     * ...where K is the minimum cutoff, L the half-life, and F the factor between
-     * the original x and x after its half-life. Strictly speaking using the word
-     * "half-life" implies that F = 0.5, but the relation works for any value of F.
-     *
-     * It is easy enough to check that this function satisfies the stability
-     * relation that was given above for any value of F, L and K, which become
-     * parameters that can be defined at will.
-     *
-     * relevance
-     *  1.0 |
-     *      |\
-     *      | \
-     *      |  \            (this graph rendered with L = 75 days and K = 1/40)
-     *  0.75|   ',
-     *      |     \
-     *      |      '.
-     *      |        \.
-     *      |          \
-     *  0.5 |           '\
-     *      |             ''.
-     *      |                ''.
-     *      |                   ''.
-     *  0.25|                      '''..
-     *      |                           '''..
-     *      |                                ''''....
-     *      |                                        '''''..........
-     *    0 +-------------------------------------------------------''''''''''----
-     *      0       50       100      150     200      250     300      350     400 days
-     *
-     *** Choosing the parameters
-     * The maximum M is an arbitrary parameter that simply scales the curve.
-     * The tradeoff for M is pretty simple : if the relevance is going to be an
-     * integer, the bigger M is the more precision there is in the relevance.
-     * However, values of M that are easy for humans to read are preferable to
-     * help debugging, and a suitably low value may be enough to ensure there
-     * won't be integer overflows in intermediate computations.
-     * A value of 1_000_000 probably is plenty for precision, while still in the
-     * low range of what ints can represent.
-     *
-     * F and L are parameters to be chosen arbitrarily and have an impact on how
-     * fast the relevance will be decaying at first, keeping in mind that
-     * the 400 days value and the cap stay the same. In simpler words, F and L
-     * define the steepness of the curve.
-     * To keep things simple (and familiar) F is arbitrarily chosen to be 0.5, and
-     * L is set to 200 days visually to achieve the desired effect. Refer to the
-     * illustration above to get a feel of how that feels.
-     *
-     * Moreover, the memory store works on an assumption that the relevance should
-     * be capped, and that an entry with capped relevance should decay in 400 days.
-     * This is on premises that the networks a device will need to remember the
-     * longest should be networks visited about once a year.
-     * For this reason, the relevance is at the maximum M 400 days before expiry :
-     * f(M, 400 days) = 0
-     * From replacing this with the value of the function, K can then be derived
-     * from the values of M, F and L :
-     * (M + K) * F ^ (t / L) - K = 0
-     * K = M * F ^ (400 days / L) / (1 - F ^ (400 days / L))
-     * Replacing with actual values this gives :
-     * K = 1_000_000 * 0.5 ^ (400 / 200) / (1 - 0.5 ^ (400 / 200))
-     *   = 1_000_000 / 3 ≈ 333_333.3
-     * This ensures the function has the desired profile, the desired value at
-     * cap, and the desired value at expiry.
-     *
-     *** Useful relations
-     * Let's define the expiry time for any given relevance x as the interval of
-     * time such as :
-     * f(x, expiry) = 0
-     * which can be rewritten
-     * ((x + K) * F ^ (expiry / L)) = K
-     * ...giving an expression of the expiry in function of the relevance x as
-     * expiry = L * logF(K / (x + K))
-     * Conversely the relevance x can be expressed in function of the expiry as
-     * x = K / F ^ (expiry / L) - K
-     * These relations are useful in utility functions.
-     *
-     *** Bumping things up
-     * The last issue therefore is to decide how to bump up the relevance. The
-     * simple approach is to simply lift up the curve a little bit by a constant
-     * normalized amount, delaying the time of expiry. For example increasing
-     * the relevance by an amount I gives :
-     * x2 = x1 + I
-     * x2 and x1 correspond to two different expiry times expiry2 and expiry1,
-     * and replacing x1 and x2 in the relation above with their expression in
-     * function of the expiry comes :
-     * K / F ^ (expiry2 / L) - K = K / F ^ (expiry1 / L) - K + I
-     * which resolves to :
-     * expiry2 = L * logF(K / (I + K / F ^ (expiry1 / L)))
-     *
-     * In this implementation, the bump is defined as 1/25th of the cap for
-     * the relevance. This means a network will be remembered for the maximum
-     * period of 400 days if connected 25 times in succession not accounting
-     * for decay. Of course decay actually happens so it will take more than 25
-     * connections for any given network to actually reach the cap, but because
-     * decay is slow at first, it is a good estimate of how fast cap happens.
-     *
-     * Specifically, it gives the following four results :
-     * - A network that a device connects to once hits irrelevance about 32.7 days after
-     *   it was first registered if never connected again.
-     * - A network that a device connects to once a day at a fixed hour will hit the cap
-     *   on the 27th connection.
-     * - A network that a device connects to once a week at a fixed hour will hit the cap
-     *   on the 57th connection.
-     * - A network that a device connects to every day for 7 straight days then never again
-     *   expires 144 days after the last connection.
-     * These metrics tend to match pretty well the requirements.
-     */
-
-    // TODO : make these constants configurable at runtime. Don't forget to build it so that
-    // changes will wipe the database, migrate the values, or otherwise make sure the relevance
-    // values are still meaningful.
-
-    // How long, in milliseconds, is a capped relevance valid for, or in other
-    // words how many milliseconds after its relevance was set to RELEVANCE_CAP does
-    // any given line expire. 400 days.
-    @VisibleForTesting
-    public static final long CAPPED_RELEVANCE_LIFETIME_MS = 400L * 24 * 60 * 60 * 1000;
-
-    // The constant that represents a normalized 1.0 value for the relevance. In other words,
-    // the cap for the relevance. This is referred to as M in the explanation above.
-    @VisibleForTesting
-    public static final int CAPPED_RELEVANCE = 1_000_000;
-
-    // The decay factor. After a half-life, the relevance will have decayed by this value.
-    // This is referred to as F in the explanation above.
-    private static final double DECAY_FACTOR = 0.5;
-
-    // The half-life. After this time, the relevance will have decayed by a factor DECAY_FACTOR.
-    // This is referred to as L in the explanation above.
-    private static final long HALF_LIFE_MS = 200L * 24 * 60 * 60 * 1000;
-
-    // The value of the frame change. This is referred to as K in the explanation above.
-    private static final double IRRELEVANCE_FLOOR =
-            CAPPED_RELEVANCE * powF((double) CAPPED_RELEVANCE_LIFETIME_MS / HALF_LIFE_MS)
-            / (1 - powF((double) CAPPED_RELEVANCE_LIFETIME_MS / HALF_LIFE_MS));
-
-    // How much to bump the relevance by every time a line is written to.
-    @VisibleForTesting
-    public static final int RELEVANCE_BUMP = CAPPED_RELEVANCE / 25;
-
-    // Java doesn't include a function for the logarithm in an arbitrary base, so implement it
-    private static final double LOG_DECAY_FACTOR = Math.log(DECAY_FACTOR);
-    private static double logF(final double value) {
-        return Math.log(value) / LOG_DECAY_FACTOR;
-    }
-
-    // Utility function to get a power of the decay factor, to simplify the code.
-    private static double powF(final double value) {
-        return Math.pow(DECAY_FACTOR, value);
-    }
-
-    /**
-     * Compute the value of the relevance now given an expiry date.
-     *
-     * @param expiry the date at which the column in the database expires.
-     * @return the adjusted value of the relevance for this moment in time.
-     */
-    public static int computeRelevanceForNow(final long expiry) {
-        return computeRelevanceForTargetDate(expiry, System.currentTimeMillis());
-    }
-
-    /**
-     * Compute the value of the relevance at a given date from an expiry date.
-     *
-     * Because relevance decays with time, a relevance in the past corresponds to
-     * a different relevance later.
-     *
-     * Relevance is always a positive value. 0 means not relevant at all.
-     *
-     * See the explanation at the top of this file to get the justification for this
-     * computation.
-     *
-     * @param expiry the date at which the column in the database expires.
-     * @param target the target date to adjust the relevance to.
-     * @return the adjusted value of the relevance for the target moment.
-     */
-    public static int computeRelevanceForTargetDate(final long expiry, final long target) {
-        final long delay = expiry - target;
-        if (delay >= CAPPED_RELEVANCE_LIFETIME_MS) return CAPPED_RELEVANCE;
-        if (delay <= 0) return 0;
-        return (int) (IRRELEVANCE_FLOOR / powF((float) delay / HALF_LIFE_MS) - IRRELEVANCE_FLOOR);
-    }
-
-    /**
-     * Compute the expiry duration adjusted up for a new fresh write.
-     *
-     * Every time data is written to the memory store for a given line, the
-     * relevance is bumped up by a certain amount, which will boost the priority
-     * of this line for computation of group attributes, and delay (possibly
-     * indefinitely, if the line is accessed regularly) forgetting the data stored
-     * in that line.
-     * As opposed to bumpExpiryDate, this function uses a duration from now to expiry.
-     *
-     * See the explanation at the top of this file for a justification of this computation.
-     *
-     * @param oldExpiryDuration the old expiry duration in milliseconds from now.
-     * @return the expiry duration representing a bumped up relevance value.
-     */
-    public static long bumpExpiryDuration(final long oldExpiryDuration) {
-        // L * logF(K / (I + K / F ^ (expiry1 / L))), as documented above
-        final double divisionFactor = powF(((double) oldExpiryDuration) / HALF_LIFE_MS);
-        final double oldRelevance = IRRELEVANCE_FLOOR / divisionFactor;
-        final long newDuration =
-                (long) (HALF_LIFE_MS * logF(IRRELEVANCE_FLOOR / (RELEVANCE_BUMP + oldRelevance)));
-        return Math.min(newDuration, CAPPED_RELEVANCE_LIFETIME_MS);
-    }
-
-    /**
-     * Compute the new expiry date adjusted up for a new fresh write.
-     *
-     * Every time data is written to the memory store for a given line, the
-     * relevance is bumped up by a certain amount, which will boost the priority
-     * of this line for computation of group attributes, and delay (possibly
-     * indefinitely, if the line is accessed regularly) forgetting the data stored
-     * in that line.
-     * As opposed to bumpExpiryDuration, this function takes the old timestamp and returns the
-     * new timestamp.
-     *
-     * {@see bumpExpiryDuration}, and keep in mind that the bump depends on when this is called,
-     * because the relevance decays exponentially, therefore bumping up a high relevance (for a
-     * date far in the future) is less potent than bumping up a low relevance (for a date in
-     * a close future).
-     *
-     * @param oldExpiryDate the old date of expiration.
-     * @return the new expiration date after the relevance bump.
-     */
-    public static long bumpExpiryDate(final long oldExpiryDate) {
-        final long now = System.currentTimeMillis();
-        final long newDuration = bumpExpiryDuration(oldExpiryDate - now);
-        return now + newDuration;
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/Utils.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/Utils.java
deleted file mode 100644
index 9cbf490..0000000
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/Utils.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.connectivity.ipmemorystore;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.ipmemorystore.Blob;
-
-/** {@hide} */
-public class Utils {
-    /** Pretty print */
-    public static String blobToString(@Nullable final Blob blob) {
-        return "Blob : " + byteArrayToString(null == blob ? null : blob.data);
-    }
-
-    /** Pretty print */
-    public static String byteArrayToString(@Nullable final byte[] data) {
-        if (null == data) return "null";
-        final StringBuilder sb = new StringBuilder("[");
-        if (data.length <= 24) {
-            appendByteArray(sb, data, 0, data.length);
-        } else {
-            appendByteArray(sb, data, 0, 16);
-            sb.append("...");
-            appendByteArray(sb, data, data.length - 8, data.length);
-        }
-        sb.append("]");
-        return sb.toString();
-    }
-
-    // Adds the hex representation of the array between the specified indices (inclusive, exclusive)
-    private static void appendByteArray(@NonNull final StringBuilder sb, @NonNull final byte[] ar,
-            final int from, final int to) {
-        for (int i = from; i < to; ++i) {
-            sb.append(String.format("%02X", ar[i]));
-        }
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
deleted file mode 100644
index 804765e..0000000
--- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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.util;
-
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-
-import java.net.Inet4Address;
-
-/**
- * Network constants used by the network stack.
- */
-public final class NetworkStackConstants {
-
-    /**
-     * IPv4 constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc791
-     */
-    public static final int IPV4_ADDR_BITS = 32;
-    public static final int IPV4_MIN_MTU = 68;
-    public static final int IPV4_MAX_MTU = 65_535;
-
-    /**
-     * Ethernet constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc894
-     *     - https://tools.ietf.org/html/rfc2464
-     *     - https://tools.ietf.org/html/rfc7042
-     *     - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
-     *     - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
-     */
-    public static final int ETHER_DST_ADDR_OFFSET = 0;
-    public static final int ETHER_SRC_ADDR_OFFSET = 6;
-    public static final int ETHER_ADDR_LEN = 6;
-    public static final int ETHER_TYPE_OFFSET = 12;
-    public static final int ETHER_TYPE_LENGTH = 2;
-    public static final int ETHER_TYPE_ARP  = 0x0806;
-    public static final int ETHER_TYPE_IPV4 = 0x0800;
-    public static final int ETHER_TYPE_IPV6 = 0x86dd;
-    public static final int ETHER_HEADER_LEN = 14;
-
-    /**
-     * ARP constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc826
-     *     - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
-     */
-    public static final int ARP_PAYLOAD_LEN = 28;  // For Ethernet+IPv4.
-    public static final int ARP_REQUEST = 1;
-    public static final int ARP_REPLY   = 2;
-    public static final int ARP_HWTYPE_RESERVED_LO = 0;
-    public static final int ARP_HWTYPE_ETHER       = 1;
-    public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
-
-    /**
-     * IPv4 constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc791
-     */
-    public static final int IPV4_HEADER_MIN_LEN = 20;
-    public static final int IPV4_IHL_MASK = 0xf;
-    public static final int IPV4_FLAGS_OFFSET = 6;
-    public static final int IPV4_FRAGMENT_MASK = 0x1fff;
-    public static final int IPV4_PROTOCOL_OFFSET = 9;
-    public static final int IPV4_SRC_ADDR_OFFSET = 12;
-    public static final int IPV4_DST_ADDR_OFFSET = 16;
-    public static final int IPV4_ADDR_LEN = 4;
-    public static final Inet4Address IPV4_ADDR_ALL = intToInet4AddressHTH(0xffffffff);
-    public static final Inet4Address IPV4_ADDR_ANY = intToInet4AddressHTH(0x0);
-
-    /**
-     * IPv6 constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc2460
-     */
-    public static final int IPV6_ADDR_LEN = 16;
-    public static final int IPV6_HEADER_LEN = 40;
-    public static final int IPV6_PROTOCOL_OFFSET = 6;
-    public static final int IPV6_SRC_ADDR_OFFSET = 8;
-    public static final int IPV6_DST_ADDR_OFFSET = 24;
-
-    /**
-     * ICMPv6 constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc4443
-     *     - https://tools.ietf.org/html/rfc4861
-     */
-    public static final int ICMPV6_HEADER_MIN_LEN = 4;
-    public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
-    public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
-    public static final int ICMPV6_ROUTER_SOLICITATION    = 133;
-    public static final int ICMPV6_ROUTER_ADVERTISEMENT   = 134;
-    public static final int ICMPV6_NEIGHBOR_SOLICITATION  = 135;
-    public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
-    public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
-    public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
-    public static final int ICMPV6_ND_OPTION_SLLA = 1;
-    public static final int ICMPV6_ND_OPTION_TLLA = 2;
-    public static final int ICMPV6_ND_OPTION_MTU  = 5;
-
-    /**
-     * UDP constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc768
-     */
-    public static final int UDP_HEADER_LEN = 8;
-
-
-    /**
-     * DHCP constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc2131
-     */
-    public static final int INFINITE_LEASE = 0xffffffff;
-    public static final int DHCP4_CLIENT_PORT = 68;
-
-    private NetworkStackConstants() {
-        throw new UnsupportedOperationException("This class is not to be instantiated");
-    }
-}
diff --git a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
deleted file mode 100644
index 6fbeead..0000000
--- a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.util;
-
-import static android.os.Binder.getCallingUid;
-
-import android.os.Process;
-import android.os.UserHandle;
-
-/**
- * Utility class to check calling permissions on the network stack.
- */
-public final class PermissionUtil {
-
-    /**
-     * Check that the caller is allowed to communicate with the network stack.
-     * @throws SecurityException The caller is not allowed to communicate with the network stack.
-     */
-    public static void checkNetworkStackCallingPermission() {
-        // TODO: check that the calling PID is the system server.
-        final int caller = getCallingUid();
-        if (caller != Process.SYSTEM_UID
-                && UserHandle.getAppId(caller) != Process.BLUETOOTH_UID
-                && UserHandle.getAppId(caller) != Process.PHONE_UID) {
-            throw new SecurityException("Invalid caller: " + caller);
-        }
-    }
-
-    /**
-     * Check that the caller is allowed to dump the network stack, e.g. dumpsys.
-     * @throws SecurityException The caller is not allowed to dump the network stack.
-     */
-    public static void checkDumpPermission() {
-        final int caller = getCallingUid();
-        if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID
-                && caller != Process.SHELL_UID) {
-            throw new SecurityException("No dump permissions for caller: " + caller);
-        }
-    }
-
-    private PermissionUtil() {
-        throw new UnsupportedOperationException("This class is not to be instantiated");
-    }
-}
diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp
deleted file mode 100644
index 039f6bf..0000000
--- a/packages/NetworkStack/tests/Android.bp
+++ /dev/null
@@ -1,103 +0,0 @@
-//
-// 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.
-//
-
-android_test {
-    name: "NetworkStackTests",
-    certificate: "platform",
-    srcs: ["src/**/*.java"],
-    test_suites: ["device-tests"],
-    resource_dirs: ["res"],
-    static_libs: [
-        "androidx.test.rules",
-        "mockito-target-extended-minus-junit4",
-        "NetworkStackBase",
-        "testables",
-    ],
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-        "android.test.mock",
-    ],
-    jni_libs: [
-        // For mockito extended
-        "libdexmakerjvmtiagent",
-        "libstaticjvmtiagent",
-        // For ApfTest
-        "libartbase",
-        "libbacktrace",
-        "libbase",
-        "libbinder",
-        "libbinderthreadstate",
-        "libc++",
-        "libcgrouprc",
-        "libcrypto",
-        "libcutils",
-        "libdexfile",
-        "ld-android",
-        "libdl_android",
-        "libhidl-gen-utils",
-        "libhidlbase",
-        "libhidltransport",
-        "libhwbinder",
-        "libjsoncpp",
-        "liblog",
-        "liblzma",
-        "libnativehelper",
-        "libnativehelper_compat_libc++",
-        "libnetworkstacktestsjni",
-        "libnetworkstackutilsjni",
-        "libpackagelistparser",
-        "libpcre2",
-        "libprocessgroup",
-        "libselinux",
-        "libui",
-        "libutils",
-        "libvintf",
-        "libvndksupport",
-        "libtinyxml2",
-        "libunwindstack",
-        "libutilscallstack",
-        "libziparchive",
-        "libz",
-        "netd_aidl_interface-cpp",
-    ],
-}
-
-cc_library_shared {
-    name: "libnetworkstacktestsjni",
-    srcs: [
-        "jni/**/*.cpp"
-    ],
-    cflags: [
-        "-Wall",
-        "-Wextra",
-        "-Werror",
-    ],
-    include_dirs: [
-        "hardware/google/apf",
-    ],
-    shared_libs: [
-        "libbinder",
-        "liblog",
-        "libcutils",
-        "libnativehelper",
-        "netd_aidl_interface-cpp",
-    ],
-    static_libs: [
-        "libapf",
-        "libpcap",
-    ],
-}
diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml
deleted file mode 100644
index 5dcf6ff..0000000
--- a/packages/NetworkStack/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.server.networkstack.tests">
-
-    <uses-permission android:name="android.permission.READ_LOGS" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
-    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
-    <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
-    <uses-permission android:name="android.permission.GET_DETAILED_TASKS" />
-    <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
-    <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
-    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.MANAGE_USERS" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
-    <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
-    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
-    <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
-    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
-    <uses-permission android:name="android.permission.NETWORK_STACK" />
-
-    <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
-    </application>
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.server.networkstack.tests"
-        android:label="Networking service tests">
-    </instrumentation>
-</manifest>
\ No newline at end of file
diff --git a/packages/NetworkStack/tests/AndroidTest.xml b/packages/NetworkStack/tests/AndroidTest.xml
deleted file mode 100644
index 047bc2e..0000000
--- a/packages/NetworkStack/tests/AndroidTest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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 Tests for NetworkStack">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
-        <option name="test-file-name" value="NetworkStackTests.apk" />
-    </target_preparer>
-
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-suite-tag" value="framework-base-presubmit" />
-    <option name="test-tag" value="NetworkStackTests" />
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="com.android.server.networkstack.tests" />
-        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
-        <option name="hidden-api-checks" value="false"/>
-    </test>
-</configuration>
\ No newline at end of file
diff --git a/packages/NetworkStack/tests/jni/apf_jni.cpp b/packages/NetworkStack/tests/jni/apf_jni.cpp
deleted file mode 100644
index 4222adf..0000000
--- a/packages/NetworkStack/tests/jni/apf_jni.cpp
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * 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.
- */
-
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedUtfChars.h>
-#include <jni.h>
-#include <pcap.h>
-#include <stdlib.h>
-#include <string>
-#include <utils/Log.h>
-#include <vector>
-
-#include "apf_interpreter.h"
-#include "nativehelper/scoped_primitive_array.h"
-
-#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
-
-// JNI function acting as simply call-through to native APF interpreter.
-static jint com_android_server_ApfTest_apfSimulate(
-        JNIEnv* env, jclass, jbyteArray jprogram, jbyteArray jpacket,
-        jbyteArray jdata, jint filter_age) {
-
-    ScopedByteArrayRO packet(env, jpacket);
-    uint32_t packet_len = (uint32_t)packet.size();
-    uint32_t program_len = env->GetArrayLength(jprogram);
-    uint32_t data_len = jdata ? env->GetArrayLength(jdata) : 0;
-    std::vector<uint8_t> buf(program_len + data_len, 0);
-
-    env->GetByteArrayRegion(jprogram, 0, program_len, reinterpret_cast<jbyte*>(buf.data()));
-    if (jdata) {
-        // Merge program and data into a single buffer.
-        env->GetByteArrayRegion(jdata, 0, data_len,
-                                reinterpret_cast<jbyte*>(buf.data() + program_len));
-    }
-
-    jint result =
-        accept_packet(buf.data(), program_len, program_len + data_len,
-                        reinterpret_cast<const uint8_t*>(packet.get()), packet_len, filter_age);
-
-    if (jdata) {
-        env->SetByteArrayRegion(jdata, 0, data_len,
-                                reinterpret_cast<jbyte*>(buf.data() + program_len));
-    }
-
-    return result;
-}
-
-class ScopedPcap {
-  public:
-    explicit ScopedPcap(pcap_t* pcap) : pcap_ptr(pcap) {}
-    ~ScopedPcap() {
-        pcap_close(pcap_ptr);
-    }
-
-    pcap_t* get() const { return pcap_ptr; };
-  private:
-    pcap_t* const pcap_ptr;
-};
-
-class ScopedFILE {
-  public:
-    explicit ScopedFILE(FILE* fp) : file(fp) {}
-    ~ScopedFILE() {
-        fclose(file);
-    }
-
-    FILE* get() const { return file; };
-  private:
-    FILE* const file;
-};
-
-static void throwException(JNIEnv* env, const std::string& error) {
-    jclass newExcCls = env->FindClass("java/lang/IllegalStateException");
-    if (newExcCls == 0) {
-      abort();
-      return;
-    }
-    env->ThrowNew(newExcCls, error.c_str());
-}
-
-static jstring com_android_server_ApfTest_compileToBpf(JNIEnv* env, jclass, jstring jfilter) {
-    ScopedUtfChars filter(env, jfilter);
-    std::string bpf_string;
-    ScopedPcap pcap(pcap_open_dead(DLT_EN10MB, 65535));
-    if (pcap.get() == NULL) {
-        throwException(env, "pcap_open_dead failed");
-        return NULL;
-    }
-
-    // Compile "filter" to a BPF program
-    bpf_program bpf;
-    if (pcap_compile(pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) {
-        throwException(env, "pcap_compile failed");
-        return NULL;
-    }
-
-    // Translate BPF program to human-readable format
-    const struct bpf_insn* insn = bpf.bf_insns;
-    for (uint32_t i = 0; i < bpf.bf_len; i++) {
-        bpf_string += bpf_image(insn++, i);
-        bpf_string += "\n";
-    }
-
-    return env->NewStringUTF(bpf_string.c_str());
-}
-
-static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, jstring jfilter,
-        jstring jpcap_filename, jbyteArray japf_program) {
-    ScopedUtfChars filter(env, jfilter);
-    ScopedUtfChars pcap_filename(env, jpcap_filename);
-    ScopedByteArrayRO apf_program(env, japf_program);
-
-    // Open pcap file for BPF filtering
-    ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb"));
-    char pcap_error[PCAP_ERRBUF_SIZE];
-    ScopedPcap bpf_pcap(pcap_fopen_offline(bpf_fp.get(), pcap_error));
-    if (bpf_pcap.get() == NULL) {
-        throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
-        return false;
-    }
-
-    // Open pcap file for APF filtering
-    ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
-    ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error));
-    if (apf_pcap.get() == NULL) {
-        throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
-        return false;
-    }
-
-    // Compile "filter" to a BPF program
-    bpf_program bpf;
-    if (pcap_compile(bpf_pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) {
-        throwException(env, "pcap_compile failed");
-        return false;
-    }
-
-    // Install BPF filter on bpf_pcap
-    if (pcap_setfilter(bpf_pcap.get(), &bpf)) {
-        throwException(env, "pcap_setfilter failed");
-        return false;
-    }
-
-    while (1) {
-        pcap_pkthdr bpf_header, apf_header;
-        // Run BPF filter to the next matching packet.
-        const uint8_t* bpf_packet = pcap_next(bpf_pcap.get(), &bpf_header);
-
-        // Run APF filter to the next matching packet.
-        const uint8_t* apf_packet;
-        do {
-            apf_packet = pcap_next(apf_pcap.get(), &apf_header);
-        } while (apf_packet != NULL && !accept_packet(
-                reinterpret_cast<uint8_t*>(const_cast<int8_t*>(apf_program.get())),
-                apf_program.size(), 0 /* data_len */,
-                apf_packet, apf_header.len, 0 /* filter_age */));
-
-        // Make sure both filters matched the same packet.
-        if (apf_packet == NULL && bpf_packet == NULL)
-            break;
-        if (apf_packet == NULL || bpf_packet == NULL)
-            return false;
-        if (apf_header.len != bpf_header.len ||
-                apf_header.ts.tv_sec != bpf_header.ts.tv_sec ||
-                apf_header.ts.tv_usec != bpf_header.ts.tv_usec ||
-                memcmp(apf_packet, bpf_packet, apf_header.len))
-            return false;
-    }
-    return true;
-}
-
-static jboolean com_android_server_ApfTest_dropsAllPackets(JNIEnv* env, jclass, jbyteArray jprogram,
-        jbyteArray jdata, jstring jpcap_filename) {
-    ScopedUtfChars pcap_filename(env, jpcap_filename);
-    ScopedByteArrayRO apf_program(env, jprogram);
-    uint32_t apf_program_len = (uint32_t)apf_program.size();
-    uint32_t data_len = env->GetArrayLength(jdata);
-    pcap_pkthdr apf_header;
-    const uint8_t* apf_packet;
-    char pcap_error[PCAP_ERRBUF_SIZE];
-    std::vector<uint8_t> buf(apf_program_len + data_len, 0);
-
-    // Merge program and data into a single buffer.
-    env->GetByteArrayRegion(jprogram, 0, apf_program_len, reinterpret_cast<jbyte*>(buf.data()));
-    env->GetByteArrayRegion(jdata, 0, data_len,
-                            reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
-
-    // Open pcap file
-    ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
-    ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error));
-
-    if (apf_pcap.get() == NULL) {
-        throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
-        return false;
-    }
-
-    while ((apf_packet = pcap_next(apf_pcap.get(), &apf_header)) != NULL) {
-        int result = accept_packet(buf.data(), apf_program_len,
-                                    apf_program_len + data_len, apf_packet, apf_header.len, 0);
-
-        // Return false once packet passes the filter
-        if (result) {
-            env->SetByteArrayRegion(jdata, 0, data_len,
-                                    reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
-            return false;
-         }
-    }
-
-    env->SetByteArrayRegion(jdata, 0, data_len,
-                            reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
-    return true;
-}
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
-    JNIEnv *env;
-    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        ALOGE("ERROR: GetEnv failed");
-        return -1;
-    }
-
-    static JNINativeMethod gMethods[] = {
-            { "apfSimulate", "([B[B[BI)I",
-                    (void*)com_android_server_ApfTest_apfSimulate },
-            { "compileToBpf", "(Ljava/lang/String;)Ljava/lang/String;",
-                    (void*)com_android_server_ApfTest_compileToBpf },
-            { "compareBpfApf", "(Ljava/lang/String;Ljava/lang/String;[B)Z",
-                    (void*)com_android_server_ApfTest_compareBpfApf },
-            { "dropsAllPackets", "([B[BLjava/lang/String;)Z",
-                    (void*)com_android_server_ApfTest_dropsAllPackets },
-    };
-
-    jniRegisterNativeMethods(env, "android/net/apf/ApfTest",
-            gMethods, ARRAY_SIZE(gMethods));
-
-    return JNI_VERSION_1_6;
-}
diff --git a/packages/NetworkStack/tests/res/raw/apf.pcap b/packages/NetworkStack/tests/res/raw/apf.pcap
deleted file mode 100644
index 963165f..0000000
--- a/packages/NetworkStack/tests/res/raw/apf.pcap
+++ /dev/null
Binary files differ
diff --git a/packages/NetworkStack/tests/res/raw/apfPcap.pcap b/packages/NetworkStack/tests/res/raw/apfPcap.pcap
deleted file mode 100644
index 6f69c4a..0000000
--- a/packages/NetworkStack/tests/res/raw/apfPcap.pcap
+++ /dev/null
Binary files differ
diff --git a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
deleted file mode 100644
index 8f2b968..0000000
--- a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
+++ /dev/null
@@ -1,2110 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.apf;
-
-import static android.system.OsConstants.AF_UNIX;
-import static android.system.OsConstants.ARPHRD_ETHER;
-import static android.system.OsConstants.ETH_P_ARP;
-import static android.system.OsConstants.ETH_P_IP;
-import static android.system.OsConstants.ETH_P_IPV6;
-import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_TCP;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_STREAM;
-
-import static com.android.internal.util.BitUtils.bytesToBEInt;
-import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NattKeepalivePacketDataParcelable;
-import android.net.TcpKeepalivePacketDataParcelable;
-import android.net.apf.ApfFilter.ApfConfiguration;
-import android.net.apf.ApfGenerator.IllegalInstructionException;
-import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IIpClientCallbacks;
-import android.net.ip.IpClient.IpClientCallbacksWrapper;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.RaEvent;
-import android.net.util.InterfaceParams;
-import android.net.util.SharedLog;
-import android.os.ConditionVariable;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.HexDump;
-import com.android.server.networkstack.tests.R;
-import com.android.server.util.NetworkStackConstants;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-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.io.File;
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Random;
-
-/**
- * Tests for APF program generator and interpreter.
- *
- * Build, install and run with:
- *  runtest frameworks-net -c android.net.apf.ApfTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ApfTest {
-    private static final int TIMEOUT_MS = 500;
-    private static final int MIN_APF_VERSION = 2;
-
-    @Mock IpConnectivityLog mLog;
-    @Mock Context mContext;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        // Load up native shared library containing APF interpreter exposed via JNI.
-        System.loadLibrary("networkstacktestsjni");
-    }
-
-    private static final String TAG = "ApfTest";
-    // Expected return codes from APF interpreter.
-    private static final int PASS = 1;
-    private static final int DROP = 0;
-    // Interpreter will just accept packets without link layer headers, so pad fake packet to at
-    // least the minimum packet size.
-    private static final int MIN_PKT_SIZE = 15;
-
-    private static final ApfCapabilities MOCK_APF_CAPABILITIES =
-      new ApfCapabilities(2, 1700, ARPHRD_ETHER);
-
-    private static final boolean DROP_MULTICAST = true;
-    private static final boolean ALLOW_MULTICAST = false;
-
-    private static final boolean DROP_802_3_FRAMES = true;
-    private static final boolean ALLOW_802_3_FRAMES = false;
-
-    // Constants for opcode encoding
-    private static final byte LI_OP   = (byte)(13 << 3);
-    private static final byte LDDW_OP = (byte)(22 << 3);
-    private static final byte STDW_OP = (byte)(23 << 3);
-    private static final byte SIZE0   = (byte)(0 << 1);
-    private static final byte SIZE8   = (byte)(1 << 1);
-    private static final byte SIZE16  = (byte)(2 << 1);
-    private static final byte SIZE32  = (byte)(3 << 1);
-    private static final byte R1 = 1;
-
-    private static ApfConfiguration getDefaultConfig() {
-        ApfFilter.ApfConfiguration config = new ApfConfiguration();
-        config.apfCapabilities = MOCK_APF_CAPABILITIES;
-        config.multicastFilter = ALLOW_MULTICAST;
-        config.ieee802_3Filter = ALLOW_802_3_FRAMES;
-        config.ethTypeBlackList = new int[0];
-        return config;
-    }
-
-    private static String label(int code) {
-        switch (code) {
-            case PASS: return "PASS";
-            case DROP: return "DROP";
-            default:   return "UNKNOWN";
-        }
-    }
-
-    private static void assertReturnCodesEqual(int expected, int got) {
-        assertEquals(label(expected), label(got));
-    }
-
-    private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
-        assertReturnCodesEqual(expected, apfSimulate(program, packet, null, filterAge));
-    }
-
-    private void assertVerdict(int expected, byte[] program, byte[] packet) {
-        assertReturnCodesEqual(expected, apfSimulate(program, packet, null, 0));
-    }
-
-    private void assertPass(byte[] program, byte[] packet, int filterAge) {
-        assertVerdict(PASS, program, packet, filterAge);
-    }
-
-    private void assertPass(byte[] program, byte[] packet) {
-        assertVerdict(PASS, program, packet);
-    }
-
-    private void assertDrop(byte[] program, byte[] packet, int filterAge) {
-        assertVerdict(DROP, program, packet, filterAge);
-    }
-
-    private void assertDrop(byte[] program, byte[] packet) {
-        assertVerdict(DROP, program, packet);
-    }
-
-    private void assertProgramEquals(byte[] expected, byte[] program) throws AssertionError {
-        // assertArrayEquals() would only print one byte, making debugging difficult.
-        if (!java.util.Arrays.equals(expected, program)) {
-            throw new AssertionError(
-                    "\nexpected: " + HexDump.toHexString(expected) +
-                    "\nactual:   " + HexDump.toHexString(program));
-        }
-    }
-
-    private void assertDataMemoryContents(
-            int expected, byte[] program, byte[] packet, byte[] data, byte[] expected_data)
-            throws IllegalInstructionException, Exception {
-        assertReturnCodesEqual(expected, apfSimulate(program, packet, data, 0 /* filterAge */));
-
-        // assertArrayEquals() would only print one byte, making debugging difficult.
-        if (!java.util.Arrays.equals(expected_data, data)) {
-            throw new Exception(
-                    "\nprogram:     " + HexDump.toHexString(program) +
-                    "\ndata memory: " + HexDump.toHexString(data) +
-                    "\nexpected:    " + HexDump.toHexString(expected_data));
-        }
-    }
-
-    private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
-            throws IllegalInstructionException {
-        assertReturnCodesEqual(expected, apfSimulate(gen.generate(), packet, null,
-              filterAge));
-    }
-
-    private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
-            throws IllegalInstructionException {
-        assertVerdict(PASS, gen, packet, filterAge);
-    }
-
-    private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
-            throws IllegalInstructionException {
-        assertVerdict(DROP, gen, packet, filterAge);
-    }
-
-    private void assertPass(ApfGenerator gen)
-            throws IllegalInstructionException {
-        assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
-    }
-
-    private void assertDrop(ApfGenerator gen)
-            throws IllegalInstructionException {
-        assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
-    }
-
-    /**
-     * Test each instruction by generating a program containing the instruction,
-     * generating bytecode for that program and running it through the
-     * interpreter to verify it functions correctly.
-     */
-    @Test
-    public void testApfInstructions() throws IllegalInstructionException {
-        // Empty program should pass because having the program counter reach the
-        // location immediately after the program indicates the packet should be
-        // passed to the AP.
-        ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION);
-        assertPass(gen);
-
-        // Test jumping to pass label.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJump(gen.PASS_LABEL);
-        byte[] program = gen.generate();
-        assertEquals(1, program.length);
-        assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
-        assertPass(program, new byte[MIN_PKT_SIZE], 0);
-
-        // Test jumping to drop label.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJump(gen.DROP_LABEL);
-        program = gen.generate();
-        assertEquals(2, program.length);
-        assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
-        assertEquals(1, program[1]);
-        assertDrop(program, new byte[15], 15);
-
-        // Test jumping if equal to 0.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if not equal to 0.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if registers equal.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if registers not equal.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test load immediate.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test add.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addAdd(1234567890);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test subtract.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addAdd(-1234567890);
-        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test or.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addOr(1234567890);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test and.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addAnd(123456789);
-        gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test left shift.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLeftShift(1);
-        gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test right shift.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addRightShift(1);
-        gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test multiply.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 123456789);
-        gen.addMul(2);
-        gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test divide.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addDiv(2);
-        gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test divide by zero.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addDiv(0);
-        gen.addJump(gen.DROP_LABEL);
-        assertPass(gen);
-
-        // Test add.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 1234567890);
-        gen.addAddR1();
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test subtract.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, -1234567890);
-        gen.addAddR1();
-        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test or.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 1234567890);
-        gen.addOrR1();
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test and.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLoadImmediate(Register.R1, 123456789);
-        gen.addAndR1();
-        gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test left shift.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addLeftShiftR1();
-        gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test right shift.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLoadImmediate(Register.R1, -1);
-        gen.addLeftShiftR1();
-        gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test multiply.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 123456789);
-        gen.addLoadImmediate(Register.R1, 2);
-        gen.addMulR1();
-        gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test divide.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLoadImmediate(Register.R1, 2);
-        gen.addDivR1();
-        gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test divide by zero.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addDivR1();
-        gen.addJump(gen.DROP_LABEL);
-        assertPass(gen);
-
-        // Test byte load.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoad8(Register.R0, 1);
-        gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test out of bounds load.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoad8(Register.R0, 16);
-        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
-        assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test half-word load.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoad16(Register.R0, 1);
-        gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test word load.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoad32(Register.R0, 1);
-        gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test byte indexed load.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addLoad8Indexed(Register.R0, 0);
-        gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test out of bounds indexed load.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 8);
-        gen.addLoad8Indexed(Register.R0, 8);
-        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
-        assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test half-word indexed load.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addLoad16Indexed(Register.R0, 0);
-        gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test word indexed load.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addLoad32Indexed(Register.R0, 0);
-        gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test jumping if greater than.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if less than.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if any bits set.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
-        assertDrop(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 3);
-        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if register greater than.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 2);
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if register less than.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if any bits set in register.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 3);
-        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 3);
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
-        assertDrop(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 3);
-        gen.addLoadImmediate(Register.R0, 3);
-        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test load from memory.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadFromMemory(Register.R0, 0);
-        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test store to memory.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 1234567890);
-        gen.addStoreToMemory(Register.R1, 12);
-        gen.addLoadFromMemory(Register.R0, 12);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test filter age pre-filled memory.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
-
-        // Test packet size pre-filled memory.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
-        gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test IPv4 header size pre-filled memory.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-        gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
-
-        // Test not.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addNot(Register.R0);
-        gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test negate.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addNeg(Register.R0);
-        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test move.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 1234567890);
-        gen.addMove(Register.R0);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addMove(Register.R1);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test swap.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R1, 1234567890);
-        gen.addSwap();
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addSwap();
-        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jump if bytes not equal.
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
-        program = gen.generate();
-        assertEquals(6, program.length);
-        assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
-        assertEquals(1, program[1]);
-        assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
-        assertEquals(1, program[3]);
-        assertEquals(1, program[4]);
-        assertEquals(123, program[5]);
-        assertDrop(program, new byte[MIN_PKT_SIZE], 0);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
-        byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
-        assertPass(gen, packet123, 0);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
-        assertDrop(gen, packet123, 0);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
-        byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
-        assertDrop(gen, packet12345, 0);
-        gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
-        assertPass(gen, packet12345, 0);
-    }
-
-    @Test(expected = ApfGenerator.IllegalInstructionException.class)
-    public void testApfGeneratorWantsV2OrGreater() throws Exception {
-        // The minimum supported APF version is 2.
-        new ApfGenerator(1);
-    }
-
-    @Test
-    public void testApfDataOpcodesWantApfV3() throws IllegalInstructionException, Exception {
-        ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION);
-        try {
-            gen.addStoreData(Register.R0, 0);
-            fail();
-        } catch (IllegalInstructionException expected) {
-            /* pass */
-        }
-        try {
-            gen.addLoadData(Register.R0, 0);
-            fail();
-        } catch (IllegalInstructionException expected) {
-            /* pass */
-        }
-    }
-
-    /**
-     * Test that the generator emits immediates using the shortest possible encoding.
-     */
-    @Test
-    public void testImmediateEncoding() throws IllegalInstructionException {
-        ApfGenerator gen;
-
-        // 0-byte immediate: li R0, 0
-        gen = new ApfGenerator(4);
-        gen.addLoadImmediate(Register.R0, 0);
-        assertProgramEquals(new byte[]{LI_OP | SIZE0}, gen.generate());
-
-        // 1-byte immediate: li R0, 42
-        gen = new ApfGenerator(4);
-        gen.addLoadImmediate(Register.R0, 42);
-        assertProgramEquals(new byte[]{LI_OP | SIZE8, 42}, gen.generate());
-
-        // 2-byte immediate: li R1, 0x1234
-        gen = new ApfGenerator(4);
-        gen.addLoadImmediate(Register.R1, 0x1234);
-        assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1, 0x12, 0x34}, gen.generate());
-
-        // 4-byte immediate: li R0, 0x12345678
-        gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R0, 0x12345678);
-        assertProgramEquals(
-                new byte[]{LI_OP | SIZE32, 0x12, 0x34, 0x56, 0x78},
-                gen.generate());
-    }
-
-    /**
-     * Test that the generator emits negative immediates using the shortest possible encoding.
-     */
-    @Test
-    public void testNegativeImmediateEncoding() throws IllegalInstructionException {
-        ApfGenerator gen;
-
-        // 1-byte negative immediate: li R0, -42
-        gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R0, -42);
-        assertProgramEquals(new byte[]{LI_OP | SIZE8, -42}, gen.generate());
-
-        // 2-byte negative immediate: li R1, -0x1122
-        gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R1, -0x1122);
-        assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1, (byte)0xEE, (byte)0xDE},
-                gen.generate());
-
-        // 4-byte negative immediate: li R0, -0x11223344
-        gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R0, -0x11223344);
-        assertProgramEquals(
-                new byte[]{LI_OP | SIZE32, (byte)0xEE, (byte)0xDD, (byte)0xCC, (byte)0xBC},
-                gen.generate());
-    }
-
-    /**
-     * Test that the generator correctly emits positive and negative immediates for LDDW/STDW.
-     */
-    @Test
-    public void testLoadStoreDataEncoding() throws IllegalInstructionException {
-        ApfGenerator gen;
-
-        // Load data with no offset: lddw R0, [0 + r1]
-        gen = new ApfGenerator(3);
-        gen.addLoadData(Register.R0, 0);
-        assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate());
-
-        // Store data with 8bit negative offset: lddw r0, [-42 + r1]
-        gen = new ApfGenerator(3);
-        gen.addStoreData(Register.R0, -42);
-        assertProgramEquals(new byte[]{STDW_OP | SIZE8, -42}, gen.generate());
-
-        // Store data to R1 with 16bit negative offset: stdw r1, [-0x1122 + r0]
-        gen = new ApfGenerator(3);
-        gen.addStoreData(Register.R1, -0x1122);
-        assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1, (byte)0xEE, (byte)0xDE},
-                gen.generate());
-
-        // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0]
-        gen = new ApfGenerator(3);
-        gen.addLoadData(Register.R1, 0xDEADBEEF);
-        assertProgramEquals(
-                new byte[]{LDDW_OP | SIZE32 | R1, (byte)0xDE, (byte)0xAD, (byte)0xBE, (byte)0xEF},
-                gen.generate());
-    }
-
-    /**
-     * Test that the interpreter correctly executes STDW with a negative 8bit offset
-     */
-    @Test
-    public void testApfDataWrite() throws IllegalInstructionException, Exception {
-        byte[] packet = new byte[MIN_PKT_SIZE];
-        byte[] data = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
-        byte[] expected_data = data.clone();
-
-        // No memory access instructions: should leave the data segment untouched.
-        ApfGenerator gen = new ApfGenerator(3);
-        assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
-
-        // Expect value 0x87654321 to be stored starting from address -11 from the end of the
-        // data buffer, in big-endian order.
-        gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R0, 0x87654321);
-        gen.addLoadImmediate(Register.R1, -5);
-        gen.addStoreData(Register.R0, -6);  // -5 + -6 = -11 (offset +5 with data_len=16)
-        expected_data[5] = (byte)0x87;
-        expected_data[6] = (byte)0x65;
-        expected_data[7] = (byte)0x43;
-        expected_data[8] = (byte)0x21;
-        assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
-    }
-
-    /**
-     * Test that the interpreter correctly executes LDDW with a negative 16bit offset
-     */
-    @Test
-    public void testApfDataRead() throws IllegalInstructionException, Exception {
-        // Program that DROPs if address 10 (-6) contains 0x87654321.
-        ApfGenerator gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R1, 1000);
-        gen.addLoadData(Register.R0, -1006);  // 1000 + -1006 = -6 (offset +10 with data_len=16)
-        gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL);
-        byte[] program = gen.generate();
-        byte[] packet = new byte[MIN_PKT_SIZE];
-
-        // Content is incorrect (last byte does not match) -> PASS
-        byte[] data = new byte[16];
-        data[10] = (byte)0x87;
-        data[11] = (byte)0x65;
-        data[12] = (byte)0x43;
-        data[13] = (byte)0x00;  // != 0x21
-        byte[] expected_data = data.clone();
-        assertDataMemoryContents(PASS, program, packet, data, expected_data);
-
-        // Fix the last byte -> conditional jump taken -> DROP
-        data[13] = (byte)0x21;
-        expected_data = data;
-        assertDataMemoryContents(DROP, program, packet, data, expected_data);
-    }
-
-    /**
-     * Test that the interpreter correctly executes LDDW followed by a STDW.
-     * To cover a few more edge cases, LDDW has a 0bit offset, while STDW has a positive 8bit
-     * offset.
-     */
-    @Test
-    public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception {
-        ApfGenerator gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R1, -22);
-        gen.addLoadData(Register.R0, 0);  // Load from address 32 -22 + 0 = 10
-        gen.addAdd(0x78453412);  // 87654321 + 78453412 = FFAA7733
-        gen.addStoreData(Register.R0, 4);  // Write back to address 32 -22 + 4 = 14
-
-        byte[] packet = new byte[MIN_PKT_SIZE];
-        byte[] data = new byte[32];
-        data[10] = (byte)0x87;
-        data[11] = (byte)0x65;
-        data[12] = (byte)0x43;
-        data[13] = (byte)0x21;
-        byte[] expected_data = data.clone();
-        expected_data[14] = (byte)0xFF;
-        expected_data[15] = (byte)0xAA;
-        expected_data[16] = (byte)0x77;
-        expected_data[17] = (byte)0x33;
-        assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
-    }
-
-    @Test
-    public void testApfDataBoundChecking() throws IllegalInstructionException, Exception {
-        byte[] packet = new byte[MIN_PKT_SIZE];
-        byte[] data = new byte[32];
-        byte[] expected_data = data;
-
-        // Program that DROPs unconditionally. This is our the baseline.
-        ApfGenerator gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R0, 3);
-        gen.addLoadData(Register.R1, 7);
-        gen.addJump(gen.DROP_LABEL);
-        assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
-
-        // Same program as before, but this time we're trying to load past the end of the data.
-        gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R0, 20);
-        gen.addLoadData(Register.R1, 15);  // 20 + 15 > 32
-        gen.addJump(gen.DROP_LABEL);  // Not reached.
-        assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
-
-        // Subtracting an immediate should work...
-        gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R0, 20);
-        gen.addLoadData(Register.R1, -4);
-        gen.addJump(gen.DROP_LABEL);
-        assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
-
-        // ...and underflowing simply wraps around to the end of the buffer...
-        gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R0, 20);
-        gen.addLoadData(Register.R1, -30);
-        gen.addJump(gen.DROP_LABEL);
-        assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
-
-        // ...but doesn't allow accesses before the start of the buffer
-        gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R0, 20);
-        gen.addLoadData(Register.R1, -1000);
-        gen.addJump(gen.DROP_LABEL);  // Not reached.
-        assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
-    }
-
-    /**
-     * Generate some BPF programs, translate them to APF, then run APF and BPF programs
-     * over packet traces and verify both programs filter out the same packets.
-     */
-    @Test
-    public void testApfAgainstBpf() throws Exception {
-        String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
-                "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
-                "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
-                "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
-        String pcap_filename = stageFile(R.raw.apf);
-        for (String tcpdump_filter : tcpdump_filters) {
-            byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
-            assertTrue("Failed to match for filter: " + tcpdump_filter,
-                    compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
-        }
-    }
-
-    /**
-     * Generate APF program, run pcap file though APF filter, then check all the packets in the file
-     * should be dropped.
-     */
-    @Test
-    public void testApfFilterPcapFile() throws Exception {
-        final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151};
-        String pcapFilename = stageFile(R.raw.apfPcap);
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16);
-        LinkProperties lp = new LinkProperties();
-        lp.addLinkAddress(link);
-
-        ApfConfiguration config = getDefaultConfig();
-        ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER);
-        config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES;
-        config.multicastFilter = DROP_MULTICAST;
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
-        apfFilter.setLinkProperties(lp);
-        byte[] program = ipClientCallback.getApfProgram();
-        byte[] data = new byte[ApfFilter.Counter.totalSize()];
-        final boolean result;
-
-        result = dropsAllPackets(program, data, pcapFilename);
-        Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false));
-
-        assertTrue("Failed to drop all packets by filter. \nAPF counters:" +
-            HexDump.toHexString(data, false), result);
-    }
-
-    private class MockIpClientCallback extends IpClientCallbacksWrapper {
-        private final ConditionVariable mGotApfProgram = new ConditionVariable();
-        private byte[] mLastApfProgram;
-
-        MockIpClientCallback() {
-            super(mock(IIpClientCallbacks.class), mock(SharedLog.class));
-        }
-
-        @Override
-        public void installPacketFilter(byte[] filter) {
-            mLastApfProgram = filter;
-            mGotApfProgram.open();
-        }
-
-        public void resetApfProgramWait() {
-            mGotApfProgram.close();
-        }
-
-        public byte[] getApfProgram() {
-            assertTrue(mGotApfProgram.block(TIMEOUT_MS));
-            return mLastApfProgram;
-        }
-
-        public void assertNoProgramUpdate() {
-            assertFalse(mGotApfProgram.block(TIMEOUT_MS));
-        }
-    }
-
-    private static class TestApfFilter extends ApfFilter {
-        public static final byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6};
-
-        private FileDescriptor mWriteSocket;
-        private final long mFixedTimeMs = SystemClock.elapsedRealtime();
-
-        public TestApfFilter(Context context, ApfConfiguration config,
-                IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) throws Exception {
-            super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log);
-        }
-
-        // Pretend an RA packet has been received and show it to ApfFilter.
-        public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
-            // ApfFilter's ReceiveThread will be waiting to read this.
-            Os.write(mWriteSocket, packet, 0, packet.length);
-        }
-
-        @Override
-        protected long currentTimeSeconds() {
-            return mFixedTimeMs / DateUtils.SECOND_IN_MILLIS;
-        }
-
-        @Override
-        void maybeStartFilter() {
-            mHardwareAddress = MOCK_MAC_ADDR;
-            installNewProgramLocked();
-
-            // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
-            FileDescriptor readSocket = new FileDescriptor();
-            mWriteSocket = new FileDescriptor();
-            try {
-                Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
-            } catch (ErrnoException e) {
-                fail();
-                return;
-            }
-            // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
-            // This allows us to pretend RA packets have been recieved via pretendPacketReceived().
-            mReceiveThread = new ReceiveThread(readSocket);
-            mReceiveThread.start();
-        }
-
-        @Override
-        public void shutdown() {
-            super.shutdown();
-            IoUtils.closeQuietly(mWriteSocket);
-        }
-    }
-
-    private static final int ETH_HEADER_LEN               = 14;
-    private static final int ETH_DEST_ADDR_OFFSET         = 0;
-    private static final int ETH_ETHERTYPE_OFFSET         = 12;
-    private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
-            {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
-
-    private static final int IPV4_HEADER_LEN          = 20;
-    private static final int IPV4_VERSION_IHL_OFFSET  = ETH_HEADER_LEN + 0;
-    private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2;
-    private static final int IPV4_PROTOCOL_OFFSET     = ETH_HEADER_LEN + 9;
-    private static final int IPV4_SRC_ADDR_OFFSET     = ETH_HEADER_LEN + 12;
-    private static final int IPV4_DEST_ADDR_OFFSET    = ETH_HEADER_LEN + 16;
-
-    private static final int IPV4_TCP_HEADER_LEN           = 20;
-    private static final int IPV4_TCP_HEADER_OFFSET        = ETH_HEADER_LEN + IPV4_HEADER_LEN;
-    private static final int IPV4_TCP_SRC_PORT_OFFSET      = IPV4_TCP_HEADER_OFFSET + 0;
-    private static final int IPV4_TCP_DEST_PORT_OFFSET     = IPV4_TCP_HEADER_OFFSET + 2;
-    private static final int IPV4_TCP_SEQ_NUM_OFFSET       = IPV4_TCP_HEADER_OFFSET + 4;
-    private static final int IPV4_TCP_ACK_NUM_OFFSET       = IPV4_TCP_HEADER_OFFSET + 8;
-    private static final int IPV4_TCP_HEADER_LENGTH_OFFSET = IPV4_TCP_HEADER_OFFSET + 12;
-    private static final int IPV4_TCP_HEADER_FLAG_OFFSET   = IPV4_TCP_HEADER_OFFSET + 13;
-
-    private static final int IPV4_UDP_HEADER_OFFSET    = ETH_HEADER_LEN + IPV4_HEADER_LEN;;
-    private static final int IPV4_UDP_SRC_PORT_OFFSET  = IPV4_UDP_HEADER_OFFSET + 0;
-    private static final int IPV4_UDP_DEST_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 2;
-    private static final int IPV4_UDP_LENGTH_OFFSET    = IPV4_UDP_HEADER_OFFSET + 4;
-    private static final int IPV4_UDP_PAYLOAD_OFFSET   = IPV4_UDP_HEADER_OFFSET + 8;
-    private static final byte[] IPV4_BROADCAST_ADDRESS =
-            {(byte) 255, (byte) 255, (byte) 255, (byte) 255};
-
-    private static final int IPV6_HEADER_LEN             = 40;
-    private static final int IPV6_NEXT_HEADER_OFFSET     = ETH_HEADER_LEN + 6;
-    private static final int IPV6_SRC_ADDR_OFFSET        = ETH_HEADER_LEN + 8;
-    private static final int IPV6_DEST_ADDR_OFFSET       = ETH_HEADER_LEN + 24;
-    private static final int IPV6_TCP_HEADER_OFFSET      = ETH_HEADER_LEN + IPV6_HEADER_LEN;
-    private static final int IPV6_TCP_SRC_PORT_OFFSET    = IPV6_TCP_HEADER_OFFSET + 0;
-    private static final int IPV6_TCP_DEST_PORT_OFFSET   = IPV6_TCP_HEADER_OFFSET + 2;
-    private static final int IPV6_TCP_SEQ_NUM_OFFSET     = IPV6_TCP_HEADER_OFFSET + 4;
-    private static final int IPV6_TCP_ACK_NUM_OFFSET     = IPV6_TCP_HEADER_OFFSET + 8;
-    // The IPv6 all nodes address ff02::1
-    private static final byte[] IPV6_ALL_NODES_ADDRESS   =
-            { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
-    private static final byte[] IPV6_ALL_ROUTERS_ADDRESS =
-            { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
-
-    private static final int ICMP6_TYPE_OFFSET           = ETH_HEADER_LEN + IPV6_HEADER_LEN;
-    private static final int ICMP6_ROUTER_SOLICITATION   = 133;
-    private static final int ICMP6_ROUTER_ADVERTISEMENT  = 134;
-    private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
-    private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
-
-    private static final int ICMP6_RA_HEADER_LEN = 16;
-    private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
-            ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
-    private static final int ICMP6_RA_CHECKSUM_OFFSET =
-            ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
-    private static final int ICMP6_RA_OPTION_OFFSET =
-            ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
-
-    private static final int ICMP6_PREFIX_OPTION_TYPE                      = 3;
-    private static final int ICMP6_PREFIX_OPTION_LEN                       = 32;
-    private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET     = 4;
-    private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
-
-    // From RFC6106: Recursive DNS Server option
-    private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
-    // From RFC6106: DNS Search List option
-    private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
-
-    // From RFC4191: Route Information option
-    private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
-    // Above three options all have the same format:
-    private static final int ICMP6_4_BYTE_OPTION_LEN      = 8;
-    private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
-    private static final int ICMP6_4_BYTE_LIFETIME_LEN    = 4;
-
-    private static final int UDP_HEADER_LEN              = 8;
-    private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22;
-
-    private static final int DHCP_CLIENT_PORT       = 68;
-    private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
-
-    private static final int ARP_HEADER_OFFSET          = ETH_HEADER_LEN;
-    private static final byte[] ARP_IPV4_REQUEST_HEADER = {
-            0, 1, // Hardware type: Ethernet (1)
-            8, 0, // Protocol type: IP (0x0800)
-            6,    // Hardware size: 6
-            4,    // Protocol size: 4
-            0, 1  // Opcode: request (1)
-    };
-    private static final byte[] ARP_IPV4_REPLY_HEADER = {
-            0, 1, // Hardware type: Ethernet (1)
-            8, 0, // Protocol type: IP (0x0800)
-            6,    // Hardware size: 6
-            4,    // Protocol size: 4
-            0, 2  // Opcode: reply (2)
-    };
-    private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
-    private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
-
-    private static final byte[] MOCK_IPV4_ADDR           = {10, 0, 0, 1};
-    private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19
-    private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1};
-    private static final byte[] ANOTHER_IPV4_ADDR        = {10, 0, 0, 2};
-    private static final byte[] IPV4_SOURCE_ADDR         = {10, 0, 0, 3};
-    private static final byte[] ANOTHER_IPV4_SOURCE_ADDR = {(byte) 192, 0, 2, 1};
-    private static final byte[] BUG_PROBE_SOURCE_ADDR1   = {0, 0, 1, 2};
-    private static final byte[] BUG_PROBE_SOURCE_ADDR2   = {3, 4, 0, 0};
-    private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
-
-    // Helper to initialize a default apfFilter.
-    private ApfFilter setupApfFilter(
-            IpClientCallbacksWrapper ipClientCallback, ApfConfiguration config) throws Exception {
-        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
-        LinkProperties lp = new LinkProperties();
-        lp.addLinkAddress(link);
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
-        apfFilter.setLinkProperties(lp);
-        return apfFilter;
-    }
-
-    @Test
-    public void testApfFilterIPv4() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
-        LinkProperties lp = new LinkProperties();
-        lp.addLinkAddress(link);
-
-        ApfConfiguration config = getDefaultConfig();
-        config.multicastFilter = DROP_MULTICAST;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
-        apfFilter.setLinkProperties(lp);
-
-        byte[] program = ipClientCallback.getApfProgram();
-
-        // Verify empty packet of 100 zero bytes is passed
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        assertPass(program, packet.array());
-
-        // Verify unicast IPv4 packet is passed
-        put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
-        put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR);
-        assertPass(program, packet.array());
-
-        // Verify L2 unicast to IPv4 broadcast addresses is dropped (b/30231088)
-        put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
-        assertDrop(program, packet.array());
-        put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
-        assertDrop(program, packet.array());
-
-        // Verify multicast/broadcast IPv4, not DHCP to us, is dropped
-        put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
-        assertDrop(program, packet.array());
-        packet.put(IPV4_VERSION_IHL_OFFSET, (byte)0x45);
-        assertDrop(program, packet.array());
-        packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
-        assertDrop(program, packet.array());
-        packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
-        assertDrop(program, packet.array());
-        put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_MULTICAST_IPV4_ADDR);
-        assertDrop(program, packet.array());
-        put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
-        assertDrop(program, packet.array());
-        put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
-        assertDrop(program, packet.array());
-
-        // Verify broadcast IPv4 DHCP to us is passed
-        put(packet, DHCP_CLIENT_MAC_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
-        assertPass(program, packet.array());
-
-        // Verify unicast IPv4 DHCP to us is passed
-        put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
-        assertPass(program, packet.array());
-
-        apfFilter.shutdown();
-    }
-
-    @Test
-    public void testApfFilterIPv6() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        ApfConfiguration config = getDefaultConfig();
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
-        byte[] program = ipClientCallback.getApfProgram();
-
-        // Verify empty IPv6 packet is passed
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        assertPass(program, packet.array());
-
-        // Verify empty ICMPv6 packet is passed
-        packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
-        assertPass(program, packet.array());
-
-        // Verify empty ICMPv6 NA packet is passed
-        packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
-        assertPass(program, packet.array());
-
-        // Verify ICMPv6 NA to ff02::1 is dropped
-        put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
-        assertDrop(program, packet.array());
-
-        // Verify ICMPv6 RS to any is dropped
-        packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION);
-        assertDrop(program, packet.array());
-        put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
-        assertDrop(program, packet.array());
-
-        apfFilter.shutdown();
-    }
-
-    @Test
-    public void testApfFilterMulticast() throws Exception {
-        final byte[] unicastIpv4Addr   = {(byte)192,0,2,63};
-        final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
-        final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
-        final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
-
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
-        LinkProperties lp = new LinkProperties();
-        lp.addLinkAddress(link);
-
-        ApfConfiguration config = getDefaultConfig();
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
-        apfFilter.setLinkProperties(lp);
-
-        byte[] program = ipClientCallback.getApfProgram();
-
-        // Construct IPv4 and IPv6 multicast packets.
-        ByteBuffer mcastv4packet = ByteBuffer.wrap(new byte[100]);
-        mcastv4packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
-        put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
-
-        ByteBuffer mcastv6packet = ByteBuffer.wrap(new byte[100]);
-        mcastv6packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_UDP);
-        put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
-
-        // Construct IPv4 broadcast packet.
-        ByteBuffer bcastv4packet1 = ByteBuffer.wrap(new byte[100]);
-        bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS);
-        bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
-        put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
-
-        ByteBuffer bcastv4packet2 = ByteBuffer.wrap(new byte[100]);
-        bcastv4packet2.put(ETH_BROADCAST_MAC_ADDRESS);
-        bcastv4packet2.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
-        put(bcastv4packet2, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
-
-        // Construct IPv4 broadcast with L2 unicast address packet (b/30231088).
-        ByteBuffer bcastv4unicastl2packet = ByteBuffer.wrap(new byte[100]);
-        bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR);
-        bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
-        put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr);
-
-        // Verify initially disabled multicast filter is off
-        assertPass(program, mcastv4packet.array());
-        assertPass(program, mcastv6packet.array());
-        assertPass(program, bcastv4packet1.array());
-        assertPass(program, bcastv4packet2.array());
-        assertPass(program, bcastv4unicastl2packet.array());
-
-        // Turn on multicast filter and verify it works
-        ipClientCallback.resetApfProgramWait();
-        apfFilter.setMulticastFilter(true);
-        program = ipClientCallback.getApfProgram();
-        assertDrop(program, mcastv4packet.array());
-        assertDrop(program, mcastv6packet.array());
-        assertDrop(program, bcastv4packet1.array());
-        assertDrop(program, bcastv4packet2.array());
-        assertDrop(program, bcastv4unicastl2packet.array());
-
-        // Turn off multicast filter and verify it's off
-        ipClientCallback.resetApfProgramWait();
-        apfFilter.setMulticastFilter(false);
-        program = ipClientCallback.getApfProgram();
-        assertPass(program, mcastv4packet.array());
-        assertPass(program, mcastv6packet.array());
-        assertPass(program, bcastv4packet1.array());
-        assertPass(program, bcastv4packet2.array());
-        assertPass(program, bcastv4unicastl2packet.array());
-
-        // Verify it can be initialized to on
-        ipClientCallback.resetApfProgramWait();
-        apfFilter.shutdown();
-        config.multicastFilter = DROP_MULTICAST;
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
-        apfFilter.setLinkProperties(lp);
-        program = ipClientCallback.getApfProgram();
-        assertDrop(program, mcastv4packet.array());
-        assertDrop(program, mcastv6packet.array());
-        assertDrop(program, bcastv4packet1.array());
-        assertDrop(program, bcastv4unicastl2packet.array());
-
-        // Verify that ICMPv6 multicast is not dropped.
-        mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
-        assertPass(program, mcastv6packet.array());
-
-        apfFilter.shutdown();
-    }
-
-    @Test
-    public void testApfFilterMulticastPingWhileDozing() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        ApfFilter apfFilter = setupApfFilter(ipClientCallback, getDefaultConfig());
-
-        // Construct a multicast ICMPv6 ECHO request.
-        final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
-        packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE);
-        put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
-
-        // Normally, we let multicast pings alone...
-        assertPass(ipClientCallback.getApfProgram(), packet.array());
-
-        // ...and even while dozing...
-        apfFilter.setDozeMode(true);
-        assertPass(ipClientCallback.getApfProgram(), packet.array());
-
-        // ...but when the multicast filter is also enabled, drop the multicast pings to save power.
-        apfFilter.setMulticastFilter(true);
-        assertDrop(ipClientCallback.getApfProgram(), packet.array());
-
-        // However, we should still let through all other ICMPv6 types.
-        ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
-        raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT);
-        assertPass(ipClientCallback.getApfProgram(), raPacket.array());
-
-        // Now wake up from doze mode to ensure that we no longer drop the packets.
-        // (The multicast filter is still enabled at this point).
-        apfFilter.setDozeMode(false);
-        assertPass(ipClientCallback.getApfProgram(), packet.array());
-
-        apfFilter.shutdown();
-    }
-
-    @Test
-    public void testApfFilter802_3() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        ApfConfiguration config = getDefaultConfig();
-        ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
-        byte[] program = ipClientCallback.getApfProgram();
-
-        // Verify empty packet of 100 zero bytes is passed
-        // Note that eth-type = 0 makes it an IEEE802.3 frame
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        assertPass(program, packet.array());
-
-        // Verify empty packet with IPv4 is passed
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
-        assertPass(program, packet.array());
-
-        // Verify empty IPv6 packet is passed
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        assertPass(program, packet.array());
-
-        // Now turn on the filter
-        ipClientCallback.resetApfProgramWait();
-        apfFilter.shutdown();
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        apfFilter = setupApfFilter(ipClientCallback, config);
-        program = ipClientCallback.getApfProgram();
-
-        // Verify that IEEE802.3 frame is dropped
-        // In this case ethtype is used for payload length
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)(100 - 14));
-        assertDrop(program, packet.array());
-
-        // Verify that IPv4 (as example of Ethernet II) frame will pass
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
-        assertPass(program, packet.array());
-
-        // Verify that IPv6 (as example of Ethernet II) frame will pass
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        assertPass(program, packet.array());
-
-        apfFilter.shutdown();
-    }
-
-    @Test
-    public void testApfFilterEthTypeBL() throws Exception {
-        final int[] emptyBlackList = {};
-        final int[] ipv4BlackList = {ETH_P_IP};
-        final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};
-
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        ApfConfiguration config = getDefaultConfig();
-        ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
-        byte[] program = ipClientCallback.getApfProgram();
-
-        // Verify empty packet of 100 zero bytes is passed
-        // Note that eth-type = 0 makes it an IEEE802.3 frame
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        assertPass(program, packet.array());
-
-        // Verify empty packet with IPv4 is passed
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
-        assertPass(program, packet.array());
-
-        // Verify empty IPv6 packet is passed
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        assertPass(program, packet.array());
-
-        // Now add IPv4 to the black list
-        ipClientCallback.resetApfProgramWait();
-        apfFilter.shutdown();
-        config.ethTypeBlackList = ipv4BlackList;
-        apfFilter = setupApfFilter(ipClientCallback, config);
-        program = ipClientCallback.getApfProgram();
-
-        // Verify that IPv4 frame will be dropped
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
-        assertDrop(program, packet.array());
-
-        // Verify that IPv6 frame will pass
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        assertPass(program, packet.array());
-
-        // Now let us have both IPv4 and IPv6 in the black list
-        ipClientCallback.resetApfProgramWait();
-        apfFilter.shutdown();
-        config.ethTypeBlackList = ipv4Ipv6BlackList;
-        apfFilter = setupApfFilter(ipClientCallback, config);
-        program = ipClientCallback.getApfProgram();
-
-        // Verify that IPv4 frame will be dropped
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
-        assertDrop(program, packet.array());
-
-        // Verify that IPv6 frame will be dropped
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        assertDrop(program, packet.array());
-
-        apfFilter.shutdown();
-    }
-
-    private byte[] getProgram(MockIpClientCallback cb, ApfFilter filter, LinkProperties lp) {
-        cb.resetApfProgramWait();
-        filter.setLinkProperties(lp);
-        return cb.getApfProgram();
-    }
-
-    private void verifyArpFilter(byte[] program, int filterResult) {
-        // Verify ARP request packet
-        assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR));
-        assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR));
-        assertDrop(program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR));
-
-        // Verify ARP reply packets from different source ip
-        assertDrop(program, arpReply(IPV4_ANY_HOST_ADDR, IPV4_ANY_HOST_ADDR));
-        assertPass(program, arpReply(ANOTHER_IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
-        assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR1, IPV4_ANY_HOST_ADDR));
-        assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR2, IPV4_ANY_HOST_ADDR));
-
-        // Verify unicast ARP reply packet is always accepted.
-        assertPass(program, arpReply(IPV4_SOURCE_ADDR, MOCK_IPV4_ADDR));
-        assertPass(program, arpReply(IPV4_SOURCE_ADDR, ANOTHER_IPV4_ADDR));
-        assertPass(program, arpReply(IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
-
-        // Verify GARP reply packets are always filtered
-        assertDrop(program, garpReply());
-    }
-
-    @Test
-    public void testApfFilterArp() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        ApfConfiguration config = getDefaultConfig();
-        config.multicastFilter = DROP_MULTICAST;
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
-
-        // Verify initially ARP request filter is off, and GARP filter is on.
-        verifyArpFilter(ipClientCallback.getApfProgram(), PASS);
-
-        // Inform ApfFilter of our address and verify ARP filtering is on
-        LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24);
-        LinkProperties lp = new LinkProperties();
-        assertTrue(lp.addLinkAddress(linkAddress));
-        verifyArpFilter(getProgram(ipClientCallback, apfFilter, lp), DROP);
-
-        // Inform ApfFilter of loss of IP and verify ARP filtering is off
-        verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS);
-
-        apfFilter.shutdown();
-    }
-
-    private static byte[] arpReply(byte[] sip, byte[] tip) {
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
-        put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
-        put(packet, ARP_SOURCE_IP_ADDRESS_OFFSET, sip);
-        put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
-        return packet.array();
-    }
-
-    private static byte[] arpRequestBroadcast(byte[] tip) {
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
-        put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
-        put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REQUEST_HEADER);
-        put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
-        return packet.array();
-    }
-
-    private static byte[] garpReply() {
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
-        put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
-        put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
-        put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, IPV4_ANY_HOST_ADDR);
-        return packet.array();
-    }
-
-    private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 5};
-    private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 6};
-    private static final byte[] IPV4_ANOTHER_ADDR = {10, 0 , 0, 7};
-    private static final byte[] IPV6_KEEPALIVE_SRC_ADDR =
-            {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf1};
-    private static final byte[] IPV6_KEEPALIVE_DST_ADDR =
-            {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf2};
-    private static final byte[] IPV6_ANOTHER_ADDR =
-            {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf5};
-
-    @Test
-    public void testApfFilterKeepaliveAck() throws Exception {
-        final MockIpClientCallback cb = new MockIpClientCallback();
-        final ApfConfiguration config = getDefaultConfig();
-        config.multicastFilter = DROP_MULTICAST;
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
-        byte[] program;
-        final int srcPort = 12345;
-        final int dstPort = 54321;
-        final int seqNum = 2123456789;
-        final int ackNum = 1234567890;
-        final int anotherSrcPort = 23456;
-        final int anotherDstPort = 65432;
-        final int anotherSeqNum = 2123456780;
-        final int anotherAckNum = 1123456789;
-        final int slot1 = 1;
-        final int slot2 = 2;
-        final int window = 14480;
-        final int windowScale = 4;
-
-        // src: 10.0.0.5, port: 12345
-        // dst: 10.0.0.6, port: 54321
-        InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR);
-        InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR);
-
-        final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable();
-        parcel.srcAddress = srcAddr.getAddress();
-        parcel.srcPort = srcPort;
-        parcel.dstAddress = dstAddr.getAddress();
-        parcel.dstPort = dstPort;
-        parcel.seq = seqNum;
-        parcel.ack = ackNum;
-
-        apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
-        program = cb.getApfProgram();
-
-        // Verify IPv4 keepalive ack packet is dropped
-        // src: 10.0.0.6, port: 54321
-        // dst: 10.0.0.5, port: 12345
-        assertDrop(program,
-                ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
-                        dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
-        // Verify IPv4 non-keepalive ack packet from the same source address is passed
-        assertPass(program,
-                ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
-                        dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */));
-        assertPass(program,
-                ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
-                        dstPort, srcPort, ackNum, seqNum + 1, 10 /* dataLength */));
-        // Verify IPv4 packet from another address is passed
-        assertPass(program,
-                ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
-                        anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
-
-        // Remove IPv4 keepalive filter
-        apfFilter.removeKeepalivePacketFilter(slot1);
-
-        try {
-            // src: 2404:0:0:0:0:0:faf1, port: 12345
-            // dst: 2404:0:0:0:0:0:faf2, port: 54321
-            srcAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_SRC_ADDR);
-            dstAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_DST_ADDR);
-
-            final TcpKeepalivePacketDataParcelable ipv6Parcel =
-                    new TcpKeepalivePacketDataParcelable();
-            ipv6Parcel.srcAddress = srcAddr.getAddress();
-            ipv6Parcel.srcPort = srcPort;
-            ipv6Parcel.dstAddress = dstAddr.getAddress();
-            ipv6Parcel.dstPort = dstPort;
-            ipv6Parcel.seq = seqNum;
-            ipv6Parcel.ack = ackNum;
-
-            apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel);
-            program = cb.getApfProgram();
-
-            // Verify IPv6 keepalive ack packet is dropped
-            // src: 2404:0:0:0:0:0:faf2, port: 54321
-            // dst: 2404:0:0:0:0:0:faf1, port: 12345
-            assertDrop(program,
-                    ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
-                            dstPort, srcPort, ackNum, seqNum + 1));
-            // Verify IPv6 non-keepalive ack packet from the same source address is passed
-            assertPass(program,
-                    ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
-                            dstPort, srcPort, ackNum + 100, seqNum));
-            // Verify IPv6 packet from another address is passed
-            assertPass(program,
-                    ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
-                            anotherDstPort, anotherSeqNum, anotherAckNum));
-
-            // Remove IPv6 keepalive filter
-            apfFilter.removeKeepalivePacketFilter(slot1);
-
-            // Verify multiple filters
-            apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
-            apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel);
-            program = cb.getApfProgram();
-
-            // Verify IPv4 keepalive ack packet is dropped
-            // src: 10.0.0.6, port: 54321
-            // dst: 10.0.0.5, port: 12345
-            assertDrop(program,
-                    ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
-                            dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
-            // Verify IPv4 non-keepalive ack packet from the same source address is passed
-            assertPass(program,
-                    ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
-                            dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */));
-            // Verify IPv4 packet from another address is passed
-            assertPass(program,
-                    ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
-                            anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
-
-            // Verify IPv6 keepalive ack packet is dropped
-            // src: 2404:0:0:0:0:0:faf2, port: 54321
-            // dst: 2404:0:0:0:0:0:faf1, port: 12345
-            assertDrop(program,
-                    ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
-                            dstPort, srcPort, ackNum, seqNum + 1));
-            // Verify IPv6 non-keepalive ack packet from the same source address is passed
-            assertPass(program,
-                    ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
-                            dstPort, srcPort, ackNum + 100, seqNum));
-            // Verify IPv6 packet from another address is passed
-            assertPass(program,
-                    ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
-                            anotherDstPort, anotherSeqNum, anotherAckNum));
-
-            // Remove keepalive filters
-            apfFilter.removeKeepalivePacketFilter(slot1);
-            apfFilter.removeKeepalivePacketFilter(slot2);
-        } catch (UnsupportedOperationException e) {
-            // TODO: support V6 packets
-        }
-
-        program = cb.getApfProgram();
-
-        // Verify IPv4, IPv6 packets are passed
-        assertPass(program,
-                ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
-                        dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
-        assertPass(program,
-                ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
-                        dstPort, srcPort, ackNum, seqNum + 1));
-        assertPass(program,
-                ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, srcPort,
-                        dstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
-        assertPass(program,
-                ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, srcPort,
-                        dstPort, anotherSeqNum, anotherAckNum));
-
-        apfFilter.shutdown();
-    }
-
-    private static byte[] ipv4Packet(byte[] sip, byte[] dip, int sport,
-            int dport, int seq, int ack, int dataLength) {
-        final int totalLength = dataLength + IPV4_HEADER_LEN + IPV4_TCP_HEADER_LEN;
-
-        ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]);
-
-        // ether type
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP);
-
-        // IPv4 header
-        packet.put(IPV4_VERSION_IHL_OFFSET, (byte) 0x45);
-        packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength);
-        packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_TCP);
-        put(packet, IPV4_SRC_ADDR_OFFSET, sip);
-        put(packet, IPV4_DEST_ADDR_OFFSET, dip);
-        packet.putShort(IPV4_TCP_SRC_PORT_OFFSET, (short) sport);
-        packet.putShort(IPV4_TCP_DEST_PORT_OFFSET, (short) dport);
-        packet.putInt(IPV4_TCP_SEQ_NUM_OFFSET, seq);
-        packet.putInt(IPV4_TCP_ACK_NUM_OFFSET, ack);
-
-        // TCP header length 5(20 bytes), reserved 3 bits, NS=0
-        packet.put(IPV4_TCP_HEADER_LENGTH_OFFSET, (byte) 0x50);
-        // TCP flags: ACK set
-        packet.put(IPV4_TCP_HEADER_FLAG_OFFSET, (byte) 0x10);
-        return packet.array();
-    }
-
-    private static byte[] ipv6Packet(byte[] sip, byte[] tip, int sport,
-            int dport, int seq, int ack) {
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
-        put(packet, IPV6_SRC_ADDR_OFFSET, sip);
-        put(packet, IPV6_DEST_ADDR_OFFSET, tip);
-        packet.putShort(IPV6_TCP_SRC_PORT_OFFSET, (short) sport);
-        packet.putShort(IPV6_TCP_DEST_PORT_OFFSET, (short) dport);
-        packet.putInt(IPV6_TCP_SEQ_NUM_OFFSET, seq);
-        packet.putInt(IPV6_TCP_ACK_NUM_OFFSET, ack);
-        return packet.array();
-    }
-
-    @Test
-    public void testApfFilterNattKeepalivePacket() throws Exception {
-        final MockIpClientCallback cb = new MockIpClientCallback();
-        final ApfConfiguration config = getDefaultConfig();
-        config.multicastFilter = DROP_MULTICAST;
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
-        byte[] program;
-        final int srcPort = 1024;
-        final int dstPort = 4500;
-        final int slot1 = 1;
-        // NAT-T keepalive
-        final byte[] kaPayload = {(byte) 0xff};
-        final byte[] nonKaPayload = {(byte) 0xfe};
-
-        // src: 10.0.0.5, port: 1024
-        // dst: 10.0.0.6, port: 4500
-        InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR);
-        InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR);
-
-        final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable();
-        parcel.srcAddress = srcAddr.getAddress();
-        parcel.srcPort = srcPort;
-        parcel.dstAddress = dstAddr.getAddress();
-        parcel.dstPort = dstPort;
-
-        apfFilter.addNattKeepalivePacketFilter(slot1, parcel);
-        program = cb.getApfProgram();
-
-        // Verify IPv4 keepalive packet is dropped
-        // src: 10.0.0.6, port: 4500
-        // dst: 10.0.0.5, port: 1024
-        byte[] pkt = ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR,
-                    IPV4_KEEPALIVE_SRC_ADDR, dstPort, srcPort, 1 /* dataLength */);
-        System.arraycopy(kaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, kaPayload.length);
-        assertDrop(program, pkt);
-
-        // Verify a packet with payload length 1 byte but it is not 0xff will pass the filter.
-        System.arraycopy(nonKaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, nonKaPayload.length);
-        assertPass(program, pkt);
-
-        // Verify IPv4 non-keepalive response packet from the same source address is passed
-        assertPass(program,
-                ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
-                        dstPort, srcPort, 10 /* dataLength */));
-
-        // Verify IPv4 non-keepalive response packet from other source address is passed
-        assertPass(program,
-                ipv4UdpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
-                        dstPort, srcPort, 10 /* dataLength */));
-
-        apfFilter.removeKeepalivePacketFilter(slot1);
-        apfFilter.shutdown();
-    }
-
-    private static byte[] ipv4UdpPacket(byte[] sip, byte[] dip, int sport,
-            int dport, int dataLength) {
-        final int totalLength = dataLength + IPV4_HEADER_LEN + UDP_HEADER_LEN;
-        final int udpLength = UDP_HEADER_LEN + dataLength;
-        ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]);
-
-        // ether type
-        packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP);
-
-        // IPv4 header
-        packet.put(IPV4_VERSION_IHL_OFFSET, (byte) 0x45);
-        packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength);
-        packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_UDP);
-        put(packet, IPV4_SRC_ADDR_OFFSET, sip);
-        put(packet, IPV4_DEST_ADDR_OFFSET, dip);
-        packet.putShort(IPV4_UDP_SRC_PORT_OFFSET, (short) sport);
-        packet.putShort(IPV4_UDP_DEST_PORT_OFFSET, (short) dport);
-        packet.putShort(IPV4_UDP_LENGTH_OFFSET, (short) udpLength);
-
-        return packet.array();
-    }
-
-    // Verify that the last program pushed to the IpClient.Callback properly filters the
-    // given packet for the given lifetime.
-    private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) {
-        final int FRACTION_OF_LIFETIME = 6;
-        final int ageLimit = lifetime / FRACTION_OF_LIFETIME;
-
-        // Verify new program should drop RA for 1/6th its lifetime and pass afterwards.
-        assertDrop(program, packet.array());
-        assertDrop(program, packet.array(), ageLimit);
-        assertPass(program, packet.array(), ageLimit + 1);
-        assertPass(program, packet.array(), lifetime);
-        // Verify RA checksum is ignored
-        final short originalChecksum = packet.getShort(ICMP6_RA_CHECKSUM_OFFSET);
-        packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
-        assertDrop(program, packet.array());
-        packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
-        assertDrop(program, packet.array());
-        packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, originalChecksum);
-
-        // Verify other changes to RA make it not match filter
-        final byte originalFirstByte = packet.get(0);
-        packet.put(0, (byte)-1);
-        assertPass(program, packet.array());
-        packet.put(0, (byte)0);
-        assertDrop(program, packet.array());
-        packet.put(0, originalFirstByte);
-    }
-
-    // Test that when ApfFilter is shown the given packet, it generates a program to filter it
-    // for the given lifetime.
-    private void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
-            ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
-        // Verify new program generated if ApfFilter witnesses RA
-        ipClientCallback.resetApfProgramWait();
-        apfFilter.pretendPacketReceived(packet.array());
-        byte[] program = ipClientCallback.getApfProgram();
-        verifyRaLifetime(program, packet, lifetime);
-    }
-
-    private void verifyRaEvent(RaEvent expected) {
-        ArgumentCaptor<IpConnectivityLog.Event> captor =
-                ArgumentCaptor.forClass(IpConnectivityLog.Event.class);
-        verify(mLog, atLeastOnce()).log(captor.capture());
-        RaEvent got = lastRaEvent(captor.getAllValues());
-        if (!raEventEquals(expected, got)) {
-            assertEquals(expected, got);  // fail for printing an assertion error message.
-        }
-    }
-
-    private RaEvent lastRaEvent(List<IpConnectivityLog.Event> events) {
-        RaEvent got = null;
-        for (Parcelable ev : events) {
-            if (ev instanceof RaEvent) {
-                got = (RaEvent) ev;
-            }
-        }
-        return got;
-    }
-
-    private boolean raEventEquals(RaEvent ev1, RaEvent ev2) {
-        return (ev1 != null) && (ev2 != null)
-                && (ev1.routerLifetime == ev2.routerLifetime)
-                && (ev1.prefixValidLifetime == ev2.prefixValidLifetime)
-                && (ev1.prefixPreferredLifetime == ev2.prefixPreferredLifetime)
-                && (ev1.routeInfoLifetime == ev2.routeInfoLifetime)
-                && (ev1.rdnssLifetime == ev2.rdnssLifetime)
-                && (ev1.dnsslLifetime == ev2.dnsslLifetime);
-    }
-
-    private void assertInvalidRa(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
-            ByteBuffer packet) throws IOException, ErrnoException {
-        ipClientCallback.resetApfProgramWait();
-        apfFilter.pretendPacketReceived(packet.array());
-        ipClientCallback.assertNoProgramUpdate();
-    }
-
-    @Test
-    public void testApfFilterRa() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        ApfConfiguration config = getDefaultConfig();
-        config.multicastFilter = DROP_MULTICAST;
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
-        byte[] program = ipClientCallback.getApfProgram();
-
-        final int ROUTER_LIFETIME = 1000;
-        final int PREFIX_VALID_LIFETIME = 200;
-        final int PREFIX_PREFERRED_LIFETIME = 100;
-        final int RDNSS_LIFETIME  = 300;
-        final int ROUTE_LIFETIME  = 400;
-        // Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000.
-        final int DNSSL_LIFETIME  = 2000;
-        final int VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET = ETH_HEADER_LEN;
-        // IPv6, traffic class = 0, flow label = 0x12345
-        final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345;
-
-        // Verify RA is passed the first time
-        ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
-        basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        basePacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
-                VERSION_TRAFFIC_CLASS_FLOW_LABEL);
-        basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
-        basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
-        basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)ROUTER_LIFETIME);
-        basePacket.position(IPV6_DEST_ADDR_OFFSET);
-        basePacket.put(IPV6_ALL_NODES_ADDRESS);
-        assertPass(program, basePacket.array());
-
-        verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME);
-        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, -1));
-
-        ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
-        basePacket.clear();
-        newFlowLabelPacket.put(basePacket);
-        // Check that changes are ignored in every byte of the flow label.
-        newFlowLabelPacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
-                VERSION_TRAFFIC_CLASS_FLOW_LABEL + 0x11111);
-
-        // Ensure zero-length options cause the packet to be silently skipped.
-        // Do this before we test other packets. http://b/29586253
-        ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(
-                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
-        basePacket.clear();
-        zeroLengthOptionPacket.put(basePacket);
-        zeroLengthOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
-        zeroLengthOptionPacket.put((byte)0);
-        assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket);
-
-        // Generate several RAs with different options and lifetimes, and verify when
-        // ApfFilter is shown these packets, it generates programs to filter them for the
-        // appropriate lifetime.
-        ByteBuffer prefixOptionPacket = ByteBuffer.wrap(
-                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_LEN]);
-        basePacket.clear();
-        prefixOptionPacket.put(basePacket);
-        prefixOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
-        prefixOptionPacket.put((byte)(ICMP6_PREFIX_OPTION_LEN / 8));
-        prefixOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
-                PREFIX_PREFERRED_LIFETIME);
-        prefixOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
-                PREFIX_VALID_LIFETIME);
-        verifyRaLifetime(
-                apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
-        verifyRaEvent(new RaEvent(
-                ROUTER_LIFETIME, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, -1, -1, -1));
-
-        ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(
-                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
-        basePacket.clear();
-        rdnssOptionPacket.put(basePacket);
-        rdnssOptionPacket.put((byte)ICMP6_RDNSS_OPTION_TYPE);
-        rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
-        rdnssOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, RDNSS_LIFETIME);
-        verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME);
-        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1));
-
-        ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
-                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
-        basePacket.clear();
-        routeInfoOptionPacket.put(basePacket);
-        routeInfoOptionPacket.put((byte)ICMP6_ROUTE_INFO_OPTION_TYPE);
-        routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
-        routeInfoOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, ROUTE_LIFETIME);
-        verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
-        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1));
-
-        ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
-                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
-        basePacket.clear();
-        dnsslOptionPacket.put(basePacket);
-        dnsslOptionPacket.put((byte)ICMP6_DNSSL_OPTION_TYPE);
-        dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
-        dnsslOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, DNSSL_LIFETIME);
-        verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME);
-        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, DNSSL_LIFETIME));
-
-        // Verify that current program filters all five RAs:
-        program = ipClientCallback.getApfProgram();
-        verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
-        verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
-        verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
-        verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
-        verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
-        verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME);
-
-        apfFilter.shutdown();
-    }
-
-    /**
-     * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
-     * copy that resource into the app's data directory and return the path to it.
-     */
-    private String stageFile(int rawId) throws Exception {
-        File file = new File(InstrumentationRegistry.getContext().getFilesDir(), "staged_file");
-        new File(file.getParent()).mkdirs();
-        InputStream in = null;
-        OutputStream out = null;
-        try {
-            in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
-            out = new FileOutputStream(file);
-            Streams.copy(in, out);
-        } finally {
-            if (in != null) in.close();
-            if (out != null) out.close();
-        }
-        return file.getAbsolutePath();
-    }
-
-    private static void put(ByteBuffer buffer, int position, byte[] bytes) {
-        final int original = buffer.position();
-        buffer.position(position);
-        buffer.put(bytes);
-        buffer.position(original);
-    }
-
-    @Test
-    public void testRaParsing() throws Exception {
-        final int maxRandomPacketSize = 512;
-        final Random r = new Random();
-        MockIpClientCallback cb = new MockIpClientCallback();
-        ApfConfiguration config = getDefaultConfig();
-        config.multicastFilter = DROP_MULTICAST;
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
-        for (int i = 0; i < 1000; i++) {
-            byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
-            r.nextBytes(packet);
-            try {
-                apfFilter.new Ra(packet, packet.length);
-            } catch (ApfFilter.InvalidRaException e) {
-            } catch (Exception e) {
-                throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
-            }
-        }
-    }
-
-    @Test
-    public void testRaProcessing() throws Exception {
-        final int maxRandomPacketSize = 512;
-        final Random r = new Random();
-        MockIpClientCallback cb = new MockIpClientCallback();
-        ApfConfiguration config = getDefaultConfig();
-        config.multicastFilter = DROP_MULTICAST;
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
-        for (int i = 0; i < 1000; i++) {
-            byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
-            r.nextBytes(packet);
-            try {
-                apfFilter.processRa(packet, packet.length);
-            } catch (Exception e) {
-                throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
-            }
-        }
-    }
-
-    /**
-     * Call the APF interpreter to run {@code program} on {@code packet} with persistent memory
-     * segment {@data} pretending the filter was installed {@code filter_age} seconds ago.
-     */
-    private native static int apfSimulate(byte[] program, byte[] packet, byte[] data,
-        int filter_age);
-
-    /**
-     * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
-     * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
-     */
-    private native static String compileToBpf(String filter);
-
-    /**
-     * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
-     * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
-     * at the same time using APF program {@code apf_program}.  Return {@code true} if
-     * both APF and BPF programs filter out exactly the same packets.
-     */
-    private native static boolean compareBpfApf(String filter, String pcap_filename,
-            byte[] apf_program);
-
-
-    /**
-     * Open packet capture file {@code pcapFilename} and run it through APF filter. Then
-     * checks whether all the packets are dropped and populates data[] {@code data} with
-     * the APF counters.
-     */
-    private native static boolean dropsAllPackets(byte[] program, byte[] data, String pcapFilename);
-
-    @Test
-    public void testBroadcastAddress() throws Exception {
-        assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
-        assertEqualsIp("0.0.0.0", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 32));
-        assertEqualsIp("0.0.3.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 22));
-        assertEqualsIp("0.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 8));
-
-        assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 0));
-        assertEqualsIp("10.0.0.1", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 32));
-        assertEqualsIp("10.0.0.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 24));
-        assertEqualsIp("10.0.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 16));
-    }
-
-    public void assertEqualsIp(String expected, int got) throws Exception {
-        int want = bytesToBEInt(InetAddress.getByName(expected).getAddress());
-        assertEquals(want, got);
-    }
-}
diff --git a/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java
deleted file mode 100644
index 5d57cde..0000000
--- a/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.apf;
-
-import android.net.apf.ApfGenerator;
-import android.net.apf.ApfGenerator.IllegalInstructionException;
-import android.net.apf.ApfGenerator.Register;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-
-/**
- * BPF to APF translator.
- *
- * Note: This is for testing purposes only and is not guaranteed to support
- *       translation of all BPF programs.
- *
- * Example usage:
- *   javac net/java/android/net/apf/ApfGenerator.java \
- *         tests/servicestests/src/android/net/apf/Bpf2Apf.java
- *   sudo tcpdump -i em1 -d icmp | java -classpath tests/servicestests/src:net/java \
- *                                      android.net.apf.Bpf2Apf
- */
-public class Bpf2Apf {
-    private static int parseImm(String line, String arg) {
-        if (!arg.startsWith("#0x")) {
-            throw new IllegalArgumentException("Unhandled instruction: " + line);
-        }
-        final long val_long = Long.parseLong(arg.substring(3), 16);
-        if (val_long < 0 || val_long > Long.parseLong("ffffffff", 16)) {
-            throw new IllegalArgumentException("Unhandled instruction: " + line);
-        }
-        return new Long((val_long << 32) >> 32).intValue();
-    }
-
-    /**
-     * Convert a single line of "tcpdump -d" (human readable BPF program dump) {@code line} into
-     * APF instruction(s) and append them to {@code gen}. Here's an example line:
-     * (001) jeq      #0x86dd          jt 2    jf 7
-     */
-    private static void convertLine(String line, ApfGenerator gen)
-            throws IllegalInstructionException {
-        if (line.indexOf("(") != 0 || line.indexOf(")") != 4 || line.indexOf(" ") != 5) {
-            throw new IllegalArgumentException("Unhandled instruction: " + line);
-        }
-        int label = Integer.parseInt(line.substring(1, 4));
-        gen.defineLabel(Integer.toString(label));
-        String opcode = line.substring(6, 10).trim();
-        String arg = line.substring(15, Math.min(32, line.length())).trim();
-        switch (opcode) {
-            case "ld":
-            case "ldh":
-            case "ldb":
-            case "ldx":
-            case "ldxb":
-            case "ldxh":
-                Register dest = opcode.contains("x") ? Register.R1 : Register.R0;
-                if (arg.equals("4*([14]&0xf)")) {
-                    if (!opcode.equals("ldxb")) {
-                        throw new IllegalArgumentException("Unhandled instruction: " + line);
-                    }
-                    gen.addLoadFromMemory(dest, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-                    break;
-                }
-                if (arg.equals("#pktlen")) {
-                    if (!opcode.equals("ld")) {
-                        throw new IllegalArgumentException("Unhandled instruction: " + line);
-                    }
-                    gen.addLoadFromMemory(dest, gen.PACKET_SIZE_MEMORY_SLOT);
-                    break;
-                }
-                if (arg.startsWith("#0x")) {
-                    if (!opcode.equals("ld")) {
-                        throw new IllegalArgumentException("Unhandled instruction: " + line);
-                    }
-                    gen.addLoadImmediate(dest, parseImm(line, arg));
-                    break;
-                }
-                if (arg.startsWith("M[")) {
-                    if (!opcode.startsWith("ld")) {
-                        throw new IllegalArgumentException("Unhandled instruction: " + line);
-                    }
-                    int memory_slot = Integer.parseInt(arg.substring(2, arg.length() - 1));
-                    if (memory_slot < 0 || memory_slot >= gen.MEMORY_SLOTS ||
-                            // Disallow use of pre-filled slots as BPF programs might
-                            // wrongfully assume they're initialized to 0.
-                            (memory_slot >= gen.FIRST_PREFILLED_MEMORY_SLOT &&
-                                    memory_slot <= gen.LAST_PREFILLED_MEMORY_SLOT)) {
-                        throw new IllegalArgumentException("Unhandled instruction: " + line);
-                    }
-                    gen.addLoadFromMemory(dest, memory_slot);
-                    break;
-                }
-                if (arg.startsWith("[x + ")) {
-                    int offset = Integer.parseInt(arg.substring(5, arg.length() - 1));
-                    switch (opcode) {
-                        case "ld":
-                        case "ldx":
-                            gen.addLoad32Indexed(dest, offset);
-                            break;
-                        case "ldh":
-                        case "ldxh":
-                            gen.addLoad16Indexed(dest, offset);
-                            break;
-                        case "ldb":
-                        case "ldxb":
-                            gen.addLoad8Indexed(dest, offset);
-                            break;
-                    }
-                } else {
-                    int offset = Integer.parseInt(arg.substring(1, arg.length() - 1));
-                    switch (opcode) {
-                        case "ld":
-                        case "ldx":
-                            gen.addLoad32(dest, offset);
-                            break;
-                        case "ldh":
-                        case "ldxh":
-                            gen.addLoad16(dest, offset);
-                            break;
-                        case "ldb":
-                        case "ldxb":
-                            gen.addLoad8(dest, offset);
-                            break;
-                    }
-                }
-                break;
-            case "st":
-            case "stx":
-                Register src = opcode.contains("x") ? Register.R1 : Register.R0;
-                if (!arg.startsWith("M[")) {
-                    throw new IllegalArgumentException("Unhandled instruction: " + line);
-                }
-                int memory_slot = Integer.parseInt(arg.substring(2, arg.length() - 1));
-                if (memory_slot < 0 || memory_slot >= gen.MEMORY_SLOTS ||
-                        // Disallow overwriting pre-filled slots
-                        (memory_slot >= gen.FIRST_PREFILLED_MEMORY_SLOT &&
-                                memory_slot <= gen.LAST_PREFILLED_MEMORY_SLOT)) {
-                    throw new IllegalArgumentException("Unhandled instruction: " + line);
-                }
-                gen.addStoreToMemory(src, memory_slot);
-                break;
-            case "add":
-            case "and":
-            case "or":
-            case "sub":
-                if (arg.equals("x")) {
-                    switch(opcode) {
-                        case "add":
-                            gen.addAddR1();
-                            break;
-                        case "and":
-                            gen.addAndR1();
-                            break;
-                        case "or":
-                            gen.addOrR1();
-                            break;
-                        case "sub":
-                            gen.addNeg(Register.R1);
-                            gen.addAddR1();
-                            gen.addNeg(Register.R1);
-                            break;
-                    }
-                } else {
-                    int imm = parseImm(line, arg);
-                    switch(opcode) {
-                        case "add":
-                            gen.addAdd(imm);
-                            break;
-                        case "and":
-                            gen.addAnd(imm);
-                            break;
-                        case "or":
-                            gen.addOr(imm);
-                            break;
-                        case "sub":
-                            gen.addAdd(-imm);
-                            break;
-                    }
-                }
-                break;
-            case "jeq":
-            case "jset":
-            case "jgt":
-            case "jge":
-                int val = 0;
-                boolean reg_compare;
-                if (arg.startsWith("x")) {
-                    reg_compare = true;
-                } else {
-                    reg_compare = false;
-                    val = parseImm(line, arg);
-                }
-                int jt_offset = line.indexOf("jt");
-                int jf_offset = line.indexOf("jf");
-                String true_label = line.substring(jt_offset + 2, jf_offset).trim();
-                String false_label = line.substring(jf_offset + 2).trim();
-                boolean true_label_is_fallthrough = Integer.parseInt(true_label) == label + 1;
-                boolean false_label_is_fallthrough = Integer.parseInt(false_label) == label + 1;
-                if (true_label_is_fallthrough && false_label_is_fallthrough)
-                    break;
-                switch (opcode) {
-                    case "jeq":
-                        if (!true_label_is_fallthrough) {
-                            if (reg_compare) {
-                                gen.addJumpIfR0EqualsR1(true_label);
-                            } else {
-                                gen.addJumpIfR0Equals(val, true_label);
-                            }
-                        }
-                        if (!false_label_is_fallthrough) {
-                            if (!true_label_is_fallthrough) {
-                                gen.addJump(false_label);
-                            } else if (reg_compare) {
-                                gen.addJumpIfR0NotEqualsR1(false_label);
-                            } else {
-                                gen.addJumpIfR0NotEquals(val, false_label);
-                            }
-                        }
-                        break;
-                    case "jset":
-                        if (reg_compare) {
-                            gen.addJumpIfR0AnyBitsSetR1(true_label);
-                        } else {
-                            gen.addJumpIfR0AnyBitsSet(val, true_label);
-                        }
-                        if (!false_label_is_fallthrough) {
-                            gen.addJump(false_label);
-                        }
-                        break;
-                    case "jgt":
-                        if (!true_label_is_fallthrough ||
-                                // We have no less-than-or-equal-to register to register
-                                // comparison instruction, so in this case we'll jump
-                                // around an unconditional jump.
-                                (!false_label_is_fallthrough && reg_compare)) {
-                            if (reg_compare) {
-                                gen.addJumpIfR0GreaterThanR1(true_label);
-                            } else {
-                                gen.addJumpIfR0GreaterThan(val, true_label);
-                            }
-                        }
-                        if (!false_label_is_fallthrough) {
-                            if (!true_label_is_fallthrough || reg_compare) {
-                                gen.addJump(false_label);
-                            } else {
-                                gen.addJumpIfR0LessThan(val + 1, false_label);
-                            }
-                        }
-                        break;
-                    case "jge":
-                        if (!false_label_is_fallthrough ||
-                                // We have no greater-than-or-equal-to register to register
-                                // comparison instruction, so in this case we'll jump
-                                // around an unconditional jump.
-                                (!true_label_is_fallthrough && reg_compare)) {
-                            if (reg_compare) {
-                                gen.addJumpIfR0LessThanR1(false_label);
-                            } else {
-                                gen.addJumpIfR0LessThan(val, false_label);
-                            }
-                        }
-                        if (!true_label_is_fallthrough) {
-                            if (!false_label_is_fallthrough || reg_compare) {
-                                gen.addJump(true_label);
-                            } else {
-                                gen.addJumpIfR0GreaterThan(val - 1, true_label);
-                            }
-                        }
-                        break;
-                }
-                break;
-            case "ret":
-                if (arg.equals("#0")) {
-                    gen.addJump(gen.DROP_LABEL);
-                } else {
-                    gen.addJump(gen.PASS_LABEL);
-                }
-                break;
-            case "tax":
-                gen.addMove(Register.R1);
-                break;
-            case "txa":
-                gen.addMove(Register.R0);
-                break;
-            default:
-                throw new IllegalArgumentException("Unhandled instruction: " + line);
-        }
-    }
-
-    /**
-     * Convert the output of "tcpdump -d" (human readable BPF program dump) {@code bpf} into an APF
-     * program and return it.
-     */
-    public static byte[] convert(String bpf) throws IllegalInstructionException {
-        ApfGenerator gen = new ApfGenerator(3);
-        for (String line : bpf.split("\\n")) convertLine(line, gen);
-        return gen.generate();
-    }
-
-    /**
-     * Convert the output of "tcpdump -d" (human readable BPF program dump) piped in stdin into an
-     * APF program and output it via stdout.
-     */
-    public static void main(String[] args) throws Exception {
-        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
-        String line = null;
-        StringBuilder responseData = new StringBuilder();
-        ApfGenerator gen = new ApfGenerator(3);
-        while ((line = in.readLine()) != null) convertLine(line, gen);
-        System.out.write(gen.generate());
-    }
-}
diff --git a/packages/NetworkStack/tests/src/android/net/captiveportal/CaptivePortalProbeSpecTest.java b/packages/NetworkStack/tests/src/android/net/captiveportal/CaptivePortalProbeSpecTest.java
deleted file mode 100644
index f948086..0000000
--- a/packages/NetworkStack/tests/src/android/net/captiveportal/CaptivePortalProbeSpecTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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.captiveportal;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.MalformedURLException;
-import java.text.ParseException;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class CaptivePortalProbeSpecTest {
-
-    @Test
-    public void testGetResult_Regex() throws MalformedURLException, ParseException {
-        // 2xx status or 404, with an empty (match everything) location regex
-        CaptivePortalProbeSpec statusRegexSpec = CaptivePortalProbeSpec.parseSpec(
-                "http://www.google.com@@/@@2[0-9]{2}|404@@/@@");
-
-        // 404, or 301/302 redirect to some HTTPS page under google.com
-        CaptivePortalProbeSpec redirectSpec = CaptivePortalProbeSpec.parseSpec(
-                "http://google.com@@/@@404|30[12]@@/@@https://([0-9a-z]+\\.)*google\\.com.*");
-
-        assertSuccess(statusRegexSpec.getResult(200, null));
-        assertSuccess(statusRegexSpec.getResult(299, "qwer"));
-        assertSuccess(statusRegexSpec.getResult(404, null));
-        assertSuccess(statusRegexSpec.getResult(404, ""));
-
-        assertPortal(statusRegexSpec.getResult(300, null));
-        assertPortal(statusRegexSpec.getResult(399, "qwer"));
-        assertPortal(statusRegexSpec.getResult(500, null));
-
-        assertSuccess(redirectSpec.getResult(404, null));
-        assertSuccess(redirectSpec.getResult(404, ""));
-        assertSuccess(redirectSpec.getResult(301, "https://www.google.com"));
-        assertSuccess(redirectSpec.getResult(301, "https://www.google.com/test?q=3"));
-        assertSuccess(redirectSpec.getResult(302, "https://google.com/test?q=3"));
-
-        assertPortal(redirectSpec.getResult(299, "https://google.com/test?q=3"));
-        assertPortal(redirectSpec.getResult(299, ""));
-        assertPortal(redirectSpec.getResult(499, null));
-        assertPortal(redirectSpec.getResult(301, "http://login.portal.example.com/loginpage"));
-        assertPortal(redirectSpec.getResult(302, "http://www.google.com/test?q=3"));
-    }
-
-    @Test(expected = ParseException.class)
-    public void testParseSpec_Empty() throws MalformedURLException, ParseException {
-        CaptivePortalProbeSpec.parseSpec("");
-    }
-
-    @Test(expected = ParseException.class)
-    public void testParseSpec_Null() throws MalformedURLException, ParseException {
-        CaptivePortalProbeSpec.parseSpec(null);
-    }
-
-    @Test(expected = ParseException.class)
-    public void testParseSpec_MissingParts() throws MalformedURLException, ParseException {
-        CaptivePortalProbeSpec.parseSpec("http://google.com/@@/@@123");
-    }
-
-    @Test(expected = ParseException.class)
-    public void testParseSpec_TooManyParts() throws MalformedURLException, ParseException {
-        CaptivePortalProbeSpec.parseSpec("http://google.com/@@/@@123@@/@@456@@/@@extra");
-    }
-
-    @Test(expected = ParseException.class)
-    public void testParseSpec_InvalidStatusRegex() throws MalformedURLException, ParseException {
-        CaptivePortalProbeSpec.parseSpec("http://google.com/@@/@@unmatched(parenthesis@@/@@456");
-    }
-
-    @Test(expected = ParseException.class)
-    public void testParseSpec_InvalidLocationRegex() throws MalformedURLException, ParseException {
-        CaptivePortalProbeSpec.parseSpec("http://google.com/@@/@@123@@/@@unmatched[[]bracket");
-    }
-
-    @Test(expected = MalformedURLException.class)
-    public void testParseSpec_EmptyURL() throws MalformedURLException, ParseException {
-        CaptivePortalProbeSpec.parseSpec("@@/@@123@@/@@123");
-    }
-
-    @Test(expected = ParseException.class)
-    public void testParseSpec_NoParts() throws MalformedURLException, ParseException {
-        CaptivePortalProbeSpec.parseSpec("invalid");
-    }
-
-    @Test(expected = MalformedURLException.class)
-    public void testParseSpec_RegexInvalidUrl() throws MalformedURLException, ParseException {
-        CaptivePortalProbeSpec.parseSpec("notaurl@@/@@123@@/@@123");
-    }
-
-    @Test
-    public void testParseSpecOrNull_UsesSpec() {
-        final String specUrl = "http://google.com/probe";
-        final String redirectUrl = "https://google.com/probe";
-        CaptivePortalProbeSpec spec = CaptivePortalProbeSpec.parseSpecOrNull(
-                specUrl + "@@/@@302@@/@@" + redirectUrl);
-        assertEquals(specUrl, spec.getUrl().toString());
-
-        assertPortal(spec.getResult(302, "http://portal.example.com"));
-        assertSuccess(spec.getResult(302, redirectUrl));
-    }
-
-    @Test
-    public void testParseSpecOrNull_UsesFallback() throws MalformedURLException {
-        CaptivePortalProbeSpec spec = CaptivePortalProbeSpec.parseSpecOrNull(null);
-        assertNull(spec);
-
-        spec = CaptivePortalProbeSpec.parseSpecOrNull("");
-        assertNull(spec);
-
-        spec = CaptivePortalProbeSpec.parseSpecOrNull("@@/@@ @@/@@ @@/@@");
-        assertNull(spec);
-
-        spec = CaptivePortalProbeSpec.parseSpecOrNull("invalid@@/@@123@@/@@456");
-        assertNull(spec);
-    }
-
-    @Test
-    public void testParseSpecOrUseStatusCodeFallback_EmptySpec() throws MalformedURLException {
-        CaptivePortalProbeSpec spec = CaptivePortalProbeSpec.parseSpecOrNull("");
-        assertNull(spec);
-    }
-
-    private void assertIsStatusSpec(CaptivePortalProbeSpec spec) {
-        assertSuccess(spec.getResult(204, null));
-        assertSuccess(spec.getResult(204, "1234"));
-
-        assertPortal(spec.getResult(200, null));
-        assertPortal(spec.getResult(301, null));
-        assertPortal(spec.getResult(302, "1234"));
-        assertPortal(spec.getResult(399, ""));
-
-        assertFailed(spec.getResult(404, null));
-        assertFailed(spec.getResult(500, "1234"));
-    }
-
-    private void assertPortal(CaptivePortalProbeResult result) {
-        assertTrue(result.isPortal());
-    }
-
-    private void assertSuccess(CaptivePortalProbeResult result) {
-        assertTrue(result.isSuccessful());
-    }
-
-    private void assertFailed(CaptivePortalProbeResult result) {
-        assertTrue(result.isFailed());
-    }
-}
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
deleted file mode 100644
index 27d7255..0000000
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
- * 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.dhcp;
-
-import static android.net.InetAddresses.parseNumericAddress;
-import static android.net.dhcp.DhcpLease.HOSTNAME_NONE;
-import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC;
-import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC;
-
-import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.IpPrefix;
-import android.net.MacAddress;
-import android.net.dhcp.DhcpServer.Clock;
-import android.net.util.SharedLog;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import static java.lang.String.format;
-
-import java.net.Inet4Address;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class DhcpLeaseRepositoryTest {
-    private static final Inet4Address TEST_DEF_ROUTER = parseAddr4("192.168.42.247");
-    private static final Inet4Address TEST_SERVER_ADDR = parseAddr4("192.168.42.241");
-    private static final Inet4Address TEST_RESERVED_ADDR = parseAddr4("192.168.42.243");
-    private static final MacAddress TEST_MAC_1 = MacAddress.fromBytes(
-            new byte[] { 5, 4, 3, 2, 1, 0 });
-    private static final MacAddress TEST_MAC_2 = MacAddress.fromBytes(
-            new byte[] { 0, 1, 2, 3, 4, 5 });
-    private static final MacAddress TEST_MAC_3 = MacAddress.fromBytes(
-            new byte[] { 0, 1, 2, 3, 4, 6 });
-    private static final Inet4Address TEST_INETADDR_1 = parseAddr4("192.168.42.248");
-    private static final Inet4Address TEST_INETADDR_2 = parseAddr4("192.168.42.249");
-    private static final String TEST_HOSTNAME_1 = "hostname1";
-    private static final String TEST_HOSTNAME_2 = "hostname2";
-    private static final IpPrefix TEST_IP_PREFIX = new IpPrefix(TEST_SERVER_ADDR, 22);
-    private static final long TEST_TIME = 100L;
-    private static final int TEST_LEASE_TIME_MS = 3_600_000;
-    private static final Set<Inet4Address> TEST_EXCL_SET =
-            Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
-                TEST_SERVER_ADDR, TEST_DEF_ROUTER, TEST_RESERVED_ADDR)));
-
-    @NonNull
-    private SharedLog mLog;
-    @NonNull @Mock
-    private Clock mClock;
-    @NonNull
-    private DhcpLeaseRepository mRepo;
-
-    private static Inet4Address parseAddr4(String inet4Addr) {
-        return (Inet4Address) parseNumericAddress(inet4Addr);
-    }
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mLog = new SharedLog("DhcpLeaseRepositoryTest");
-        when(mClock.elapsedRealtime()).thenReturn(TEST_TIME);
-        mRepo = new DhcpLeaseRepository(
-                TEST_IP_PREFIX, TEST_EXCL_SET, TEST_LEASE_TIME_MS, mLog, mClock);
-    }
-
-    /**
-     * Request a number of addresses through offer/request. Useful to test address exhaustion.
-     * @param nAddr Number of addresses to request.
-     */
-    private void requestAddresses(byte nAddr) throws Exception {
-        final HashSet<Inet4Address> addrs = new HashSet<>();
-        byte[] hwAddrBytes = new byte[] { 8, 4, 3, 2, 1, 0 };
-        for (byte i = 0; i < nAddr; i++) {
-            hwAddrBytes[5] = i;
-            MacAddress newMac = MacAddress.fromBytes(hwAddrBytes);
-            final String hostname = "host_" + i;
-            final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, newMac,
-                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname);
-
-            assertNotNull(lease);
-            assertEquals(newMac, lease.getHwAddr());
-            assertEquals(hostname, lease.getHostname());
-            assertTrue(format("Duplicate address allocated: %s in %s", lease.getNetAddr(), addrs),
-                    addrs.add(lease.getNetAddr()));
-
-            requestLeaseSelecting(newMac, lease.getNetAddr(), hostname);
-        }
-    }
-
-    @Test
-    public void testAddressExhaustion() throws Exception {
-        // Use a /28 to quickly run out of addresses
-        mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS);
-
-        // /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses
-        requestAddresses((byte) 11);
-
-        try {
-            mRepo.getOffer(null, TEST_MAC_2,
-                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-            fail("Should be out of addresses");
-        } catch (DhcpLeaseRepository.OutOfAddressesException e) {
-            // Expected
-        }
-    }
-
-    @Test
-    public void testUpdateParams_LeaseCleanup() throws Exception {
-        // Inside /28:
-        final Inet4Address reqAddrIn28 = parseAddr4("192.168.42.242");
-        final Inet4Address declinedAddrIn28 = parseAddr4("192.168.42.245");
-
-        // Inside /28, but not available there (first address of the range)
-        final Inet4Address declinedFirstAddrIn28 = parseAddr4("192.168.42.240");
-
-        final DhcpLease reqAddrIn28Lease = requestLeaseSelecting(TEST_MAC_1, reqAddrIn28);
-        mRepo.markLeaseDeclined(declinedAddrIn28);
-        mRepo.markLeaseDeclined(declinedFirstAddrIn28);
-
-        // Inside /22, but outside /28:
-        final Inet4Address reqAddrIn22 = parseAddr4("192.168.42.3");
-        final Inet4Address declinedAddrIn22 = parseAddr4("192.168.42.4");
-
-        final DhcpLease reqAddrIn22Lease = requestLeaseSelecting(TEST_MAC_3, reqAddrIn22);
-        mRepo.markLeaseDeclined(declinedAddrIn22);
-
-        // Address that will be reserved in the updateParams call below
-        final Inet4Address reservedAddr = parseAddr4("192.168.42.244");
-        final DhcpLease reservedAddrLease = requestLeaseSelecting(TEST_MAC_2, reservedAddr);
-
-        // Update from /22 to /28 and add another reserved address
-        Set<Inet4Address> newReserved = new HashSet<>(TEST_EXCL_SET);
-        newReserved.add(reservedAddr);
-        mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), newReserved, TEST_LEASE_TIME_MS);
-
-        assertHasLease(reqAddrIn28Lease);
-        assertDeclined(declinedAddrIn28);
-
-        assertNotDeclined(declinedFirstAddrIn28);
-
-        assertNoLease(reqAddrIn22Lease);
-        assertNotDeclined(declinedAddrIn22);
-
-        assertNoLease(reservedAddrLease);
-    }
-
-    @Test
-    public void testGetOffer_StableAddress() throws Exception {
-        for (final MacAddress macAddr : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) {
-            final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr,
-                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-
-            // Same lease is offered twice
-            final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr,
-                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-            assertEquals(lease, newLease);
-        }
-    }
-
-    @Test
-    public void testUpdateParams_UsesNewPrefix() throws Exception {
-        final IpPrefix newPrefix = new IpPrefix(parseAddr4("192.168.123.0"), 24);
-        mRepo.updateParams(newPrefix, TEST_EXCL_SET, TEST_LEASE_TIME_MS);
-
-        DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-        assertTrue(newPrefix.contains(lease.getNetAddr()));
-    }
-
-    @Test
-    public void testGetOffer_ExistingLease() throws Exception {
-        requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1, TEST_HOSTNAME_1);
-
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-        assertEquals(TEST_INETADDR_1, offer.getNetAddr());
-        assertEquals(TEST_HOSTNAME_1, offer.getHostname());
-    }
-
-    @Test
-    public void testGetOffer_ClientIdHasExistingLease() throws Exception {
-        final byte[] clientId = new byte[] { 1, 2 };
-        mRepo.requestLease(clientId, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */,
-                IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false,
-                TEST_HOSTNAME_1);
-
-        // Different MAC, but same clientId
-        DhcpLease offer = mRepo.getOffer(clientId, TEST_MAC_2,
-                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-        assertEquals(TEST_INETADDR_1, offer.getNetAddr());
-        assertEquals(TEST_HOSTNAME_1, offer.getHostname());
-    }
-
-    @Test
-    public void testGetOffer_DifferentClientId() throws Exception {
-        final byte[] clientId1 = new byte[] { 1, 2 };
-        final byte[] clientId2 = new byte[] { 3, 4 };
-        mRepo.requestLease(clientId1, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */,
-                IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false,
-                TEST_HOSTNAME_1);
-
-        // Same MAC, different client ID
-        DhcpLease offer = mRepo.getOffer(clientId2, TEST_MAC_1,
-                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-        // Obtains a different address
-        assertNotEquals(TEST_INETADDR_1, offer.getNetAddr());
-        assertEquals(HOSTNAME_NONE, offer.getHostname());
-        assertEquals(TEST_MAC_1, offer.getHwAddr());
-    }
-
-    @Test
-    public void testGetOffer_RequestedAddress() throws Exception {
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
-                TEST_INETADDR_1 /* reqAddr */, TEST_HOSTNAME_1);
-        assertEquals(TEST_INETADDR_1, offer.getNetAddr());
-        assertEquals(TEST_HOSTNAME_1, offer.getHostname());
-    }
-
-    @Test
-    public void testGetOffer_RequestedAddressInUse() throws Exception {
-        requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1);
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, IPV4_ADDR_ANY /* relayAddr */,
-                TEST_INETADDR_1 /* reqAddr */, HOSTNAME_NONE);
-        assertNotEquals(TEST_INETADDR_1, offer.getNetAddr());
-    }
-
-    @Test
-    public void testGetOffer_RequestedAddressReserved() throws Exception {
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
-                TEST_RESERVED_ADDR /* reqAddr */, HOSTNAME_NONE);
-        assertNotEquals(TEST_RESERVED_ADDR, offer.getNetAddr());
-    }
-
-    @Test
-    public void testGetOffer_RequestedAddressInvalid() throws Exception {
-        final Inet4Address invalidAddr = parseAddr4("192.168.42.0");
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
-                invalidAddr /* reqAddr */, HOSTNAME_NONE);
-        assertNotEquals(invalidAddr, offer.getNetAddr());
-    }
-
-    @Test
-    public void testGetOffer_RequestedAddressOutsideSubnet() throws Exception {
-        final Inet4Address invalidAddr = parseAddr4("192.168.254.2");
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
-                invalidAddr /* reqAddr */, HOSTNAME_NONE);
-        assertNotEquals(invalidAddr, offer.getNetAddr());
-    }
-
-    @Test(expected = DhcpLeaseRepository.InvalidSubnetException.class)
-    public void testGetOffer_RelayInInvalidSubnet() throws Exception {
-        mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, parseAddr4("192.168.254.2") /* relayAddr */,
-                INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-    }
-
-    @Test
-    public void testRequestLease_SelectingTwice() throws Exception {
-        final DhcpLease lease1 = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1,
-                TEST_HOSTNAME_1);
-
-        // Second request from same client for a different address
-        final DhcpLease lease2 = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_2,
-                TEST_HOSTNAME_2);
-
-        assertEquals(TEST_INETADDR_1, lease1.getNetAddr());
-        assertEquals(TEST_HOSTNAME_1, lease1.getHostname());
-
-        assertEquals(TEST_INETADDR_2, lease2.getNetAddr());
-        assertEquals(TEST_HOSTNAME_2, lease2.getHostname());
-
-        // First address freed when client requested a different one: another client can request it
-        final DhcpLease lease3 = requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1, HOSTNAME_NONE);
-        assertEquals(TEST_INETADDR_1, lease3.getNetAddr());
-    }
-
-    @Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
-    public void testRequestLease_SelectingInvalid() throws Exception {
-        requestLeaseSelecting(TEST_MAC_1, parseAddr4("192.168.254.5"));
-    }
-
-    @Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
-    public void testRequestLease_SelectingInUse() throws Exception {
-        requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1);
-        requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1);
-    }
-
-    @Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
-    public void testRequestLease_SelectingReserved() throws Exception {
-        requestLeaseSelecting(TEST_MAC_1, TEST_RESERVED_ADDR);
-    }
-
-    @Test(expected = DhcpLeaseRepository.InvalidSubnetException.class)
-    public void testRequestLease_SelectingRelayInInvalidSubnet() throws  Exception {
-        mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */,
-                parseAddr4("192.168.128.1") /* relayAddr */, TEST_INETADDR_1 /* reqAddr */,
-                true /* sidSet */, HOSTNAME_NONE);
-    }
-
-    @Test
-    public void testRequestLease_InitReboot() throws Exception {
-        // Request address once
-        requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1);
-
-        final long newTime = TEST_TIME + 100;
-        when(mClock.elapsedRealtime()).thenReturn(newTime);
-
-        // init-reboot (sidSet == false): verify configuration
-        final DhcpLease lease = requestLeaseInitReboot(TEST_MAC_1, TEST_INETADDR_1);
-        assertEquals(TEST_INETADDR_1, lease.getNetAddr());
-        assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime());
-    }
-
-    @Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
-    public void testRequestLease_InitRebootWrongAddr() throws Exception {
-        // Request address once
-        requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1);
-        // init-reboot with different requested address
-        requestLeaseInitReboot(TEST_MAC_1, TEST_INETADDR_2);
-    }
-
-    @Test
-    public void testRequestLease_InitRebootUnknownAddr() throws Exception {
-        // init-reboot with unknown requested address
-        final DhcpLease lease = requestLeaseInitReboot(TEST_MAC_1, TEST_INETADDR_2);
-        // RFC2131 says we should not reply to accommodate other servers, but since we are
-        // authoritative we allow creating the lease to avoid issues with lost lease DB (same as
-        // dnsmasq behavior)
-        assertEquals(TEST_INETADDR_2, lease.getNetAddr());
-    }
-
-    @Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
-    public void testRequestLease_InitRebootWrongSubnet() throws Exception {
-        requestLeaseInitReboot(TEST_MAC_1, parseAddr4("192.168.254.2"));
-    }
-
-    @Test
-    public void testRequestLease_Renewing() throws Exception {
-        requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1);
-
-        final long newTime = TEST_TIME + 100;
-        when(mClock.elapsedRealtime()).thenReturn(newTime);
-
-        final DhcpLease lease = requestLeaseRenewing(TEST_MAC_1, TEST_INETADDR_1);
-
-        assertEquals(TEST_INETADDR_1, lease.getNetAddr());
-        assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime());
-    }
-
-    @Test
-    public void testRequestLease_RenewingUnknownAddr() throws Exception {
-        final long newTime = TEST_TIME + 100;
-        when(mClock.elapsedRealtime()).thenReturn(newTime);
-        final DhcpLease lease = requestLeaseRenewing(TEST_MAC_1, TEST_INETADDR_1);
-        // Allows renewing an unknown address if available
-        assertEquals(TEST_INETADDR_1, lease.getNetAddr());
-        assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime());
-    }
-
-    @Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
-    public void testRequestLease_RenewingAddrInUse() throws Exception {
-        requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1);
-        requestLeaseRenewing(TEST_MAC_1, TEST_INETADDR_1);
-    }
-
-    @Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
-    public void testRequestLease_RenewingInvalidAddr() throws Exception {
-        requestLeaseRenewing(TEST_MAC_1, parseAddr4("192.168.254.2"));
-    }
-
-    @Test
-    public void testReleaseLease() throws Exception {
-        final DhcpLease lease1 = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1);
-
-        assertHasLease(lease1);
-        assertTrue(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1));
-        assertNoLease(lease1);
-
-        final DhcpLease lease2 = requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1);
-        assertEquals(TEST_INETADDR_1, lease2.getNetAddr());
-    }
-
-    @Test
-    public void testReleaseLease_UnknownLease() {
-        assertFalse(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1));
-    }
-
-    @Test
-    public void testReleaseLease_StableOffer() throws Exception {
-        for (MacAddress mac : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) {
-            final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, mac,
-                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-
-            requestLeaseSelecting(mac, lease.getNetAddr());
-            mRepo.releaseLease(CLIENTID_UNSPEC, mac, lease.getNetAddr());
-
-            // Same lease is offered after it was released
-            final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, mac,
-                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-            assertEquals(lease.getNetAddr(), newLease.getNetAddr());
-        }
-    }
-
-    @Test
-    public void testMarkLeaseDeclined() throws Exception {
-        final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-
-        mRepo.markLeaseDeclined(lease.getNetAddr());
-
-        // Same lease is not offered again
-        final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-        assertNotEquals(lease.getNetAddr(), newLease.getNetAddr());
-    }
-
-    @Test
-    public void testMarkLeaseDeclined_UsedIfOutOfAddresses() throws Exception {
-        // Use a /28 to quickly run out of addresses
-        mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS);
-
-        mRepo.markLeaseDeclined(TEST_INETADDR_1);
-        mRepo.markLeaseDeclined(TEST_INETADDR_2);
-
-        // /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses
-        requestAddresses((byte) 9);
-
-        // Last 2 addresses: addresses marked declined should be used
-        final DhcpLease firstLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1);
-        requestLeaseSelecting(TEST_MAC_1, firstLease.getNetAddr());
-
-        final DhcpLease secondLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2,
-                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2);
-        requestLeaseSelecting(TEST_MAC_2, secondLease.getNetAddr());
-
-        // Now out of addresses
-        try {
-            mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, IPV4_ADDR_ANY /* relayAddr */,
-                    INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
-            fail("Repository should be out of addresses and throw");
-        } catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ }
-
-        assertEquals(TEST_INETADDR_1, firstLease.getNetAddr());
-        assertEquals(TEST_HOSTNAME_1, firstLease.getHostname());
-        assertEquals(TEST_INETADDR_2, secondLease.getNetAddr());
-        assertEquals(TEST_HOSTNAME_2, secondLease.getHostname());
-    }
-
-    private DhcpLease requestLease(@NonNull MacAddress macAddr, @NonNull Inet4Address clientAddr,
-            @Nullable Inet4Address reqAddr, @Nullable String hostname, boolean sidSet)
-            throws DhcpLeaseRepository.DhcpLeaseException {
-        return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr,
-                IPV4_ADDR_ANY /* relayAddr */,
-                reqAddr, sidSet, hostname);
-    }
-
-    /**
-     * Request a lease simulating a client in the SELECTING state.
-     */
-    private DhcpLease requestLeaseSelecting(@NonNull MacAddress macAddr,
-            @NonNull Inet4Address reqAddr, @Nullable String hostname)
-            throws DhcpLeaseRepository.DhcpLeaseException {
-        return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, hostname,
-                true /* sidSet */);
-    }
-
-    /**
-     * Request a lease simulating a client in the SELECTING state.
-     */
-    private DhcpLease requestLeaseSelecting(@NonNull MacAddress macAddr,
-            @NonNull Inet4Address reqAddr) throws DhcpLeaseRepository.DhcpLeaseException {
-        return requestLeaseSelecting(macAddr, reqAddr, HOSTNAME_NONE);
-    }
-
-    /**
-     * Request a lease simulating a client in the INIT-REBOOT state.
-     */
-    private DhcpLease requestLeaseInitReboot(@NonNull MacAddress macAddr,
-            @NonNull Inet4Address reqAddr) throws DhcpLeaseRepository.DhcpLeaseException {
-        return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE,
-                false /* sidSet */);
-    }
-
-    /**
-     * Request a lease simulating a client in the RENEWING state.
-     */
-    private DhcpLease requestLeaseRenewing(@NonNull MacAddress macAddr,
-            @NonNull Inet4Address clientAddr) throws DhcpLeaseRepository.DhcpLeaseException {
-        // Renewing: clientAddr filled in, no reqAddr
-        return requestLease(macAddr, clientAddr, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE,
-                true /* sidSet */);
-    }
-
-    private void assertNoLease(DhcpLease lease) {
-        assertFalse("Leases contain " + lease, mRepo.getCommittedLeases().contains(lease));
-    }
-
-    private void assertHasLease(DhcpLease lease) {
-        assertTrue("Leases do not contain " + lease, mRepo.getCommittedLeases().contains(lease));
-    }
-
-    private void assertNotDeclined(Inet4Address addr) {
-        assertFalse("Address is declined: " + addr, mRepo.getDeclinedAddresses().contains(addr));
-    }
-
-    private void assertDeclined(Inet4Address addr) {
-        assertTrue("Address is not declined: " + addr, mRepo.getDeclinedAddresses().contains(addr));
-    }
-}
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
deleted file mode 100644
index a30d3e4..0000000
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
+++ /dev/null
@@ -1,1117 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS;
-import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER;
-import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME;
-import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_ACK;
-import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_OFFER;
-import static android.net.dhcp.DhcpPacket.DHCP_MTU;
-import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_ROUTER;
-import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK;
-import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO;
-import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
-import static android.net.dhcp.DhcpPacket.ENCAP_L2;
-import static android.net.dhcp.DhcpPacket.ENCAP_L3;
-import static android.net.dhcp.DhcpPacket.INADDR_ANY;
-import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
-import static android.net.dhcp.DhcpPacket.ParseException;
-import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.annotation.Nullable;
-import android.net.DhcpResults;
-import android.net.LinkAddress;
-import android.net.NetworkUtils;
-import android.net.metrics.DhcpErrorEvent;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.HexDump;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.net.Inet4Address;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Random;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class DhcpPacketTest {
-
-    private static final Inet4Address SERVER_ADDR = v4Address("192.0.2.1");
-    private static final Inet4Address CLIENT_ADDR = v4Address("192.0.2.234");
-    private static final int PREFIX_LENGTH = 22;
-    private static final Inet4Address NETMASK = getPrefixMaskAsInet4Address(PREFIX_LENGTH);
-    private static final Inet4Address BROADCAST_ADDR = getBroadcastAddress(
-            SERVER_ADDR, PREFIX_LENGTH);
-    private static final String HOSTNAME = "testhostname";
-    private static final short MTU = 1500;
-    // Use our own empty address instead of IPV4_ADDR_ANY or INADDR_ANY to ensure that the code
-    // doesn't use == instead of equals when comparing addresses.
-    private static final Inet4Address ANY = v4Address("0.0.0.0");
-
-    private static final byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
-
-    private static final Inet4Address v4Address(String addrString) throws IllegalArgumentException {
-        return (Inet4Address) NetworkUtils.numericToInetAddress(addrString);
-    }
-
-    @Before
-    public void setUp() {
-        DhcpPacket.testOverrideVendorId = "android-dhcp-???";
-        DhcpPacket.testOverrideHostname = "android-01234567890abcde";
-    }
-
-    class TestDhcpPacket extends DhcpPacket {
-        private byte mType;
-        // TODO: Make this a map of option numbers to bytes instead.
-        private byte[] mDomainBytes, mVendorInfoBytes, mLeaseTimeBytes, mNetmaskBytes;
-
-        public TestDhcpPacket(byte type, Inet4Address clientIp, Inet4Address yourIp) {
-            super(0xdeadbeef, (short) 0, clientIp, yourIp, INADDR_ANY, INADDR_ANY,
-                  CLIENT_MAC, true);
-            mType = type;
-        }
-
-        public TestDhcpPacket(byte type) {
-            this(type, INADDR_ANY, CLIENT_ADDR);
-        }
-
-        public TestDhcpPacket setDomainBytes(byte[] domainBytes) {
-            mDomainBytes = domainBytes;
-            return this;
-        }
-
-        public TestDhcpPacket setVendorInfoBytes(byte[] vendorInfoBytes) {
-            mVendorInfoBytes = vendorInfoBytes;
-            return this;
-        }
-
-        public TestDhcpPacket setLeaseTimeBytes(byte[] leaseTimeBytes) {
-            mLeaseTimeBytes = leaseTimeBytes;
-            return this;
-        }
-
-        public TestDhcpPacket setNetmaskBytes(byte[] netmaskBytes) {
-            mNetmaskBytes = netmaskBytes;
-            return this;
-        }
-
-        public ByteBuffer buildPacket(int encap, short unusedDestUdp, short unusedSrcUdp) {
-            ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
-            fillInPacket(encap, CLIENT_ADDR, SERVER_ADDR,
-                         DHCP_CLIENT, DHCP_SERVER, result, DHCP_BOOTREPLY, false);
-            return result;
-        }
-
-        public void finishPacket(ByteBuffer buffer) {
-            addTlv(buffer, DHCP_MESSAGE_TYPE, mType);
-            if (mDomainBytes != null) {
-                addTlv(buffer, DHCP_DOMAIN_NAME, mDomainBytes);
-            }
-            if (mVendorInfoBytes != null) {
-                addTlv(buffer, DHCP_VENDOR_INFO, mVendorInfoBytes);
-            }
-            if (mLeaseTimeBytes != null) {
-                addTlv(buffer, DHCP_LEASE_TIME, mLeaseTimeBytes);
-            }
-            if (mNetmaskBytes != null) {
-                addTlv(buffer, DHCP_SUBNET_MASK, mNetmaskBytes);
-            }
-            addTlvEnd(buffer);
-        }
-
-        // Convenience method.
-        public ByteBuffer build() {
-            // ENCAP_BOOTP packets don't contain ports, so just pass in 0.
-            ByteBuffer pkt = buildPacket(ENCAP_BOOTP, (short) 0, (short) 0);
-            pkt.flip();
-            return pkt;
-        }
-    }
-
-    private void assertDomainAndVendorInfoParses(
-            String expectedDomain, byte[] domainBytes,
-            String expectedVendorInfo, byte[] vendorInfoBytes) throws Exception {
-        ByteBuffer packet = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER)
-                .setDomainBytes(domainBytes)
-                .setVendorInfoBytes(vendorInfoBytes)
-                .build();
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
-        assertEquals(expectedDomain, offerPacket.mDomainName);
-        assertEquals(expectedVendorInfo, offerPacket.mVendorInfo);
-    }
-
-    @Test
-    public void testDomainName() throws Exception {
-        byte[] nullByte = new byte[] { 0x00 };
-        byte[] twoNullBytes = new byte[] { 0x00, 0x00 };
-        byte[] nonNullDomain = new byte[] {
-            (byte) 'g', (byte) 'o', (byte) 'o', (byte) '.', (byte) 'g', (byte) 'l'
-        };
-        byte[] trailingNullDomain = new byte[] {
-            (byte) 'g', (byte) 'o', (byte) 'o', (byte) '.', (byte) 'g', (byte) 'l', 0x00
-        };
-        byte[] embeddedNullsDomain = new byte[] {
-            (byte) 'g', (byte) 'o', (byte) 'o', 0x00, 0x00, (byte) 'g', (byte) 'l'
-        };
-        byte[] metered = "ANDROID_METERED".getBytes("US-ASCII");
-
-        byte[] meteredEmbeddedNull = metered.clone();
-        meteredEmbeddedNull[7] = (char) 0;
-
-        byte[] meteredTrailingNull = metered.clone();
-        meteredTrailingNull[meteredTrailingNull.length - 1] = (char) 0;
-
-        assertDomainAndVendorInfoParses("", nullByte, "\u0000", nullByte);
-        assertDomainAndVendorInfoParses("", twoNullBytes, "\u0000\u0000", twoNullBytes);
-        assertDomainAndVendorInfoParses("goo.gl", nonNullDomain, "ANDROID_METERED", metered);
-        assertDomainAndVendorInfoParses("goo", embeddedNullsDomain,
-                                        "ANDROID\u0000METERED", meteredEmbeddedNull);
-        assertDomainAndVendorInfoParses("goo.gl", trailingNullDomain,
-                                        "ANDROID_METERE\u0000", meteredTrailingNull);
-    }
-
-    private void assertLeaseTimeParses(boolean expectValid, Integer rawLeaseTime,
-            long leaseTimeMillis, byte[] leaseTimeBytes) throws Exception {
-        TestDhcpPacket testPacket = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER);
-        if (leaseTimeBytes != null) {
-            testPacket.setLeaseTimeBytes(leaseTimeBytes);
-        }
-        ByteBuffer packet = testPacket.build();
-        DhcpPacket offerPacket = null;
-
-        if (!expectValid) {
-            try {
-                offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
-                fail("Invalid packet parsed successfully: " + offerPacket);
-            } catch (ParseException expected) {
-            }
-            return;
-        }
-
-        offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
-        assertNotNull(offerPacket);
-        assertEquals(rawLeaseTime, offerPacket.mLeaseTime);
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();  // Just check this doesn't crash.
-        assertEquals(leaseTimeMillis, offerPacket.getLeaseTimeMillis());
-    }
-
-    @Test
-    public void testLeaseTime() throws Exception {
-        byte[] noLease = null;
-        byte[] tooShortLease = new byte[] { 0x00, 0x00 };
-        byte[] tooLongLease = new byte[] { 0x00, 0x00, 0x00, 60, 0x01 };
-        byte[] zeroLease = new byte[] { 0x00, 0x00, 0x00, 0x00 };
-        byte[] tenSecondLease = new byte[] { 0x00, 0x00, 0x00, 10 };
-        byte[] oneMinuteLease = new byte[] { 0x00, 0x00, 0x00, 60 };
-        byte[] fiveMinuteLease = new byte[] { 0x00, 0x00, 0x01, 0x2c };
-        byte[] oneDayLease = new byte[] { 0x00, 0x01, 0x51, (byte) 0x80 };
-        byte[] maxIntPlusOneLease = new byte[] { (byte) 0x80, 0x00, 0x00, 0x01 };
-        byte[] infiniteLease = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
-
-        assertLeaseTimeParses(true, null, 0, noLease);
-        assertLeaseTimeParses(false, null, 0, tooShortLease);
-        assertLeaseTimeParses(false, null, 0, tooLongLease);
-        assertLeaseTimeParses(true, 0, 60 * 1000, zeroLease);
-        assertLeaseTimeParses(true, 10, 60 * 1000, tenSecondLease);
-        assertLeaseTimeParses(true, 60, 60 * 1000, oneMinuteLease);
-        assertLeaseTimeParses(true, 300, 300 * 1000, fiveMinuteLease);
-        assertLeaseTimeParses(true, 86400, 86400 * 1000, oneDayLease);
-        assertLeaseTimeParses(true, -2147483647, 2147483649L * 1000, maxIntPlusOneLease);
-        assertLeaseTimeParses(true, DhcpPacket.INFINITE_LEASE, 0, infiniteLease);
-    }
-
-    private void checkIpAddress(String expected, Inet4Address clientIp, Inet4Address yourIp,
-                                byte[] netmaskBytes) throws Exception {
-        checkIpAddress(expected, DHCP_MESSAGE_TYPE_OFFER, clientIp, yourIp, netmaskBytes);
-        checkIpAddress(expected, DHCP_MESSAGE_TYPE_ACK, clientIp, yourIp, netmaskBytes);
-    }
-
-    private void checkIpAddress(String expected, byte type,
-                                Inet4Address clientIp, Inet4Address yourIp,
-                                byte[] netmaskBytes) throws Exception {
-        ByteBuffer packet = new TestDhcpPacket(type, clientIp, yourIp)
-                .setNetmaskBytes(netmaskBytes)
-                .build();
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
-        DhcpResults results = offerPacket.toDhcpResults();
-
-        if (expected != null) {
-            LinkAddress expectedAddress = new LinkAddress(expected);
-            assertEquals(expectedAddress, results.ipAddress);
-        } else {
-            assertNull(results);
-        }
-    }
-
-    @Test
-    public void testIpAddress() throws Exception {
-        byte[] slash11Netmask = new byte[] { (byte) 0xff, (byte) 0xe0, 0x00, 0x00 };
-        byte[] slash24Netmask = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00 };
-        byte[] invalidNetmask = new byte[] { (byte) 0xff, (byte) 0xfb, (byte) 0xff, 0x00 };
-        Inet4Address example1 = v4Address("192.0.2.1");
-        Inet4Address example2 = v4Address("192.0.2.43");
-
-        // A packet without any addresses is not valid.
-        checkIpAddress(null, ANY, ANY, slash24Netmask);
-
-        // ClientIP is used iff YourIP is not present.
-        checkIpAddress("192.0.2.1/24", example2, example1, slash24Netmask);
-        checkIpAddress("192.0.2.43/11", example2, ANY, slash11Netmask);
-        checkIpAddress("192.0.2.43/11", ANY, example2, slash11Netmask);
-
-        // Invalid netmasks are ignored.
-        checkIpAddress(null, example2, ANY, invalidNetmask);
-
-        // If there is no netmask, implicit netmasks are used.
-        checkIpAddress("192.0.2.43/24", ANY, example2, null);
-    }
-
-    private void assertDhcpResults(String ipAddress, String gateway, String dnsServersString,
-            String domains, String serverAddress, String serverHostName, String vendorInfo,
-            int leaseDuration, boolean hasMeteredHint, int mtu, DhcpResults dhcpResults)
-                    throws Exception {
-        assertEquals(new LinkAddress(ipAddress), dhcpResults.ipAddress);
-        assertEquals(v4Address(gateway), dhcpResults.gateway);
-
-        String[] dnsServerStrings = dnsServersString.split(",");
-        ArrayList dnsServers = new ArrayList();
-        for (String dnsServerString : dnsServerStrings) {
-            dnsServers.add(v4Address(dnsServerString));
-        }
-        assertEquals(dnsServers, dhcpResults.dnsServers);
-
-        assertEquals(domains, dhcpResults.domains);
-        assertEquals(v4Address(serverAddress), dhcpResults.serverAddress);
-        assertEquals(serverHostName, dhcpResults.serverHostName);
-        assertEquals(vendorInfo, dhcpResults.vendorInfo);
-        assertEquals(leaseDuration, dhcpResults.leaseDuration);
-        assertEquals(hasMeteredHint, dhcpResults.hasMeteredHint());
-        assertEquals(mtu, dhcpResults.mtu);
-    }
-
-    @Test
-    public void testOffer1() throws Exception {
-        // TODO: Turn all of these into golden files. This will probably require using
-        // androidx.test.InstrumentationRegistry for obtaining a Context object
-        // to read such golden files, along with an appropriate Android.mk.
-        // CHECKSTYLE:OFF Generated code
-        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
-            // IP header.
-            "451001480000000080118849c0a89003c0a89ff7" +
-            // UDP header.
-            "004300440134dcfa" +
-            // BOOTP header.
-            "02010600c997a63b0000000000000000c0a89ff70000000000000000" +
-            // MAC address.
-            "30766ff2a90c00000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options
-            "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" +
-            "3a0400000e103b040000189cff00000000000000000000"));
-        // CHECKSTYLE:ON Generated code
-
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
-        assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();
-        assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4",
-                null, "192.168.144.3", "", null, 7200, false, 0, dhcpResults);
-    }
-
-    @Test
-    public void testOffer2() throws Exception {
-        // CHECKSTYLE:OFF Generated code
-        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
-            // IP header.
-            "450001518d0600004011144dc0a82b01c0a82bf7" +
-            // UDP header.
-            "00430044013d9ac7" +
-            // BOOTP header.
-            "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
-            // MAC address.
-            "30766ff2a90c00000000000000000000" +
-            // Server name ("dhcp.android.com" plus invalid "AAAA" after null terminator).
-            "646863702e616e64726f69642e636f6d00000000000000000000000000000000" +
-            "0000000000004141414100000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options
-            "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
-            "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"));
-        // CHECKSTYLE:ON Generated code
-
-        assertEquals(337, packet.limit());
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
-        assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();
-        assertDhcpResults("192.168.43.247/24", "192.168.43.1", "192.168.43.1",
-                null, "192.168.43.1", "dhcp.android.com", "ANDROID_METERED", 3600, true, 0,
-                dhcpResults);
-        assertTrue(dhcpResults.hasMeteredHint());
-    }
-
-    @Test
-    public void testBadIpPacket() throws Exception {
-        final byte[] packet = HexDump.hexStringToByteArray(
-            // IP header.
-            "450001518d0600004011144dc0a82b01c0a82bf7");
-
-        try {
-            DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
-        } catch (DhcpPacket.ParseException expected) {
-            assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode);
-            return;
-        }
-        fail("Dhcp packet parsing should have failed");
-    }
-
-    @Test
-    public void testBadDhcpPacket() throws Exception {
-        final byte[] packet = HexDump.hexStringToByteArray(
-            // IP header.
-            "450001518d0600004011144dc0a82b01c0a82bf7" +
-            // UDP header.
-            "00430044013d9ac7" +
-            // BOOTP header.
-            "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000");
-
-        try {
-            DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
-        } catch (DhcpPacket.ParseException expected) {
-            assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode);
-            return;
-        }
-        fail("Dhcp packet parsing should have failed");
-    }
-
-    @Test
-    public void testBadTruncatedOffer() throws Exception {
-        final byte[] packet = HexDump.hexStringToByteArray(
-            // IP header.
-            "450001518d0600004011144dc0a82b01c0a82bf7" +
-            // UDP header.
-            "00430044013d9ac7" +
-            // BOOTP header.
-            "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
-            // MAC address.
-            "30766ff2a90c00000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File, missing one byte
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "00000000000000000000000000000000000000000000000000000000000000");
-
-        try {
-            DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
-        } catch (DhcpPacket.ParseException expected) {
-            assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode);
-            return;
-        }
-        fail("Dhcp packet parsing should have failed");
-    }
-
-    @Test
-    public void testBadOfferWithoutACookie() throws Exception {
-        final byte[] packet = HexDump.hexStringToByteArray(
-            // IP header.
-            "450001518d0600004011144dc0a82b01c0a82bf7" +
-            // UDP header.
-            "00430044013d9ac7" +
-            // BOOTP header.
-            "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
-            // MAC address.
-            "30766ff2a90c00000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000"
-            // No options
-            );
-
-        try {
-            DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
-        } catch (DhcpPacket.ParseException expected) {
-            assertDhcpErrorCodes(DhcpErrorEvent.DHCP_NO_COOKIE, expected.errorCode);
-            return;
-        }
-        fail("Dhcp packet parsing should have failed");
-    }
-
-    @Test
-    public void testOfferWithBadCookie() throws Exception {
-        final byte[] packet = HexDump.hexStringToByteArray(
-            // IP header.
-            "450001518d0600004011144dc0a82b01c0a82bf7" +
-            // UDP header.
-            "00430044013d9ac7" +
-            // BOOTP header.
-            "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
-            // MAC address.
-            "30766ff2a90c00000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Bad cookie
-            "DEADBEEF3501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
-            "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff");
-
-        try {
-            DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
-        } catch (DhcpPacket.ParseException expected) {
-            assertDhcpErrorCodes(DhcpErrorEvent.DHCP_BAD_MAGIC_COOKIE, expected.errorCode);
-            return;
-        }
-        fail("Dhcp packet parsing should have failed");
-    }
-
-    private void assertDhcpErrorCodes(int expected, int got) {
-        assertEquals(Integer.toHexString(expected), Integer.toHexString(got));
-    }
-
-    @Test
-    public void testTruncatedOfferPackets() throws Exception {
-        final byte[] packet = HexDump.hexStringToByteArray(
-            // IP header.
-            "450001518d0600004011144dc0a82b01c0a82bf7" +
-            // UDP header.
-            "00430044013d9ac7" +
-            // BOOTP header.
-            "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
-            // MAC address.
-            "30766ff2a90c00000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options
-            "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
-            "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff");
-
-        for (int len = 0; len < packet.length; len++) {
-            try {
-                DhcpPacket.decodeFullPacket(packet, len, ENCAP_L3);
-            } catch (ParseException e) {
-                if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) {
-                    fail(String.format("bad truncated packet of length %d", len));
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testRandomPackets() throws Exception {
-        final int maxRandomPacketSize = 512;
-        final Random r = new Random();
-        for (int i = 0; i < 10000; i++) {
-            byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
-            r.nextBytes(packet);
-            try {
-                DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
-            } catch (ParseException e) {
-                if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) {
-                    fail("bad packet: " + HexDump.toHexString(packet));
-                }
-            }
-        }
-    }
-
-    private byte[] mtuBytes(int mtu) {
-        // 0x1a02: option 26, length 2. 0xff: no more options.
-        if (mtu > Short.MAX_VALUE - Short.MIN_VALUE) {
-            throw new IllegalArgumentException(
-                String.format("Invalid MTU %d, must be 16-bit unsigned", mtu));
-        }
-        String hexString = String.format("1a02%04xff", mtu);
-        return HexDump.hexStringToByteArray(hexString);
-    }
-
-    private void checkMtu(ByteBuffer packet, int expectedMtu, byte[] mtuBytes) throws Exception {
-        if (mtuBytes != null) {
-            packet.position(packet.capacity() - mtuBytes.length);
-            packet.put(mtuBytes);
-            packet.clear();
-        }
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
-        assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();
-        assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4",
-                null, "192.168.144.3", "", null, 7200, false, expectedMtu, dhcpResults);
-    }
-
-    @Test
-    public void testMtu() throws Exception {
-        // CHECKSTYLE:OFF Generated code
-        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
-            // IP header.
-            "451001480000000080118849c0a89003c0a89ff7" +
-            // UDP header.
-            "004300440134dcfa" +
-            // BOOTP header.
-            "02010600c997a63b0000000000000000c0a89ff70000000000000000" +
-            // MAC address.
-            "30766ff2a90c00000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options
-            "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" +
-            "3a0400000e103b040000189cff00000000"));
-        // CHECKSTYLE:ON Generated code
-
-        checkMtu(packet, 0, null);
-        checkMtu(packet, 0, mtuBytes(1501));
-        checkMtu(packet, 1500, mtuBytes(1500));
-        checkMtu(packet, 1499, mtuBytes(1499));
-        checkMtu(packet, 1280, mtuBytes(1280));
-        checkMtu(packet, 0, mtuBytes(1279));
-        checkMtu(packet, 0, mtuBytes(576));
-        checkMtu(packet, 0, mtuBytes(68));
-        checkMtu(packet, 0, mtuBytes(Short.MIN_VALUE));
-        checkMtu(packet, 0, mtuBytes(Short.MAX_VALUE + 3));
-        checkMtu(packet, 0, mtuBytes(-1));
-    }
-
-    @Test
-    public void testBadHwaddrLength() throws Exception {
-        // CHECKSTYLE:OFF Generated code
-        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
-            // IP header.
-            "450001518d0600004011144dc0a82b01c0a82bf7" +
-            // UDP header.
-            "00430044013d9ac7" +
-            // BOOTP header.
-            "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
-            // MAC address.
-            "30766ff2a90c00000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options
-            "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
-            "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"));
-        // CHECKSTYLE:ON Generated code
-        String expectedClientMac = "30766FF2A90C";
-
-        final int hwAddrLenOffset = 20 + 8 + 2;
-        assertEquals(6, packet.get(hwAddrLenOffset));
-
-        // Expect the expected.
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
-        assertNotNull(offerPacket);
-        assertEquals(6, offerPacket.getClientMac().length);
-        assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
-
-        // Reduce the hardware address length and verify that it shortens the client MAC.
-        packet.flip();
-        packet.put(hwAddrLenOffset, (byte) 5);
-        offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
-        assertNotNull(offerPacket);
-        assertEquals(5, offerPacket.getClientMac().length);
-        assertEquals(expectedClientMac.substring(0, 10),
-                HexDump.toHexString(offerPacket.getClientMac()));
-
-        packet.flip();
-        packet.put(hwAddrLenOffset, (byte) 3);
-        offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
-        assertNotNull(offerPacket);
-        assertEquals(3, offerPacket.getClientMac().length);
-        assertEquals(expectedClientMac.substring(0, 6),
-                HexDump.toHexString(offerPacket.getClientMac()));
-
-        // Set the the hardware address length to 0xff and verify that we a) don't treat it as -1
-        // and crash, and b) hardcode it to 6.
-        packet.flip();
-        packet.put(hwAddrLenOffset, (byte) -1);
-        offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
-        assertNotNull(offerPacket);
-        assertEquals(6, offerPacket.getClientMac().length);
-        assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
-
-        // Set the the hardware address length to a positive invalid value (> 16) and verify that we
-        // hardcode it to 6.
-        packet.flip();
-        packet.put(hwAddrLenOffset, (byte) 17);
-        offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
-        assertNotNull(offerPacket);
-        assertEquals(6, offerPacket.getClientMac().length);
-        assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
-    }
-
-    @Test
-    public void testPadAndOverloadedOptionsOffer() throws Exception {
-        // A packet observed in the real world that is interesting for two reasons:
-        //
-        // 1. It uses pad bytes, which we previously didn't support correctly.
-        // 2. It uses DHCP option overloading, which we don't currently support (but it doesn't
-        //    store any information in the overloaded fields).
-        //
-        // For now, we just check that it parses correctly.
-        // CHECKSTYLE:OFF Generated code
-        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
-            // Ethernet header.
-            "b4cef6000000e80462236e300800" +
-            // IP header.
-            "4500014c00000000ff11741701010101ac119876" +
-            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
-            "004300440138ae5a" +
-            // BOOTP header.
-            "020106000fa0059f0000000000000000ac1198760000000000000000" +
-            // MAC address.
-            "b4cef600000000000000000000000000" +
-            // Server name.
-            "ff00000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "ff00000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options
-            "638253633501023604010101010104ffff000033040000a8c03401030304ac1101010604ac110101" +
-            "0000000000000000000000000000000000000000000000ff000000"));
-        // CHECKSTYLE:ON Generated code
-
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
-        assertTrue(offerPacket instanceof DhcpOfferPacket);
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();
-        assertDhcpResults("172.17.152.118/16", "172.17.1.1", "172.17.1.1",
-                null, "1.1.1.1", "", null, 43200, false, 0, dhcpResults);
-    }
-
-    @Test
-    public void testBug2111() throws Exception {
-        // CHECKSTYLE:OFF Generated code
-        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
-            // IP header.
-            "4500014c00000000ff119beac3eaf3880a3f5d04" +
-            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
-            "0043004401387464" +
-            // BOOTP header.
-            "0201060002554812000a0000000000000a3f5d040000000000000000" +
-            // MAC address.
-            "00904c00000000000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options.
-            "638253633501023604c00002fe33040000bfc60104fffff00003040a3f50010608c0000201c0000202" +
-            "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000"));
-        // CHECKSTYLE:ON Generated code
-
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
-        assertTrue(offerPacket instanceof DhcpOfferPacket);
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();
-        assertDhcpResults("10.63.93.4/20", "10.63.80.1", "192.0.2.1,192.0.2.2",
-                "domain123.co.uk", "192.0.2.254", "", null, 49094, false, 0, dhcpResults);
-    }
-
-    @Test
-    public void testBug2136() throws Exception {
-        // CHECKSTYLE:OFF Generated code
-        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
-            // Ethernet header.
-            "bcf5ac000000d0c7890000000800" +
-            // IP header.
-            "4500014c00000000ff119beac3eaf3880a3f5d04" +
-            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
-            "0043004401387574" +
-            // BOOTP header.
-            "0201060163339a3000050000000000000a209ecd0000000000000000" +
-            // MAC address.
-            "bcf5ac00000000000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options.
-            "6382536335010236040a20ff80330400001c200104fffff00003040a20900106089458413494584135" +
-            "0f0b6c616e63732e61632e756b000000000000000000ff00000000"));
-        // CHECKSTYLE:ON Generated code
-
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
-        assertTrue(offerPacket instanceof DhcpOfferPacket);
-        assertEquals("BCF5AC000000", HexDump.toHexString(offerPacket.getClientMac()));
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();
-        assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53",
-                "lancs.ac.uk", "10.32.255.128", "", null, 7200, false, 0, dhcpResults);
-    }
-
-    @Test
-    public void testUdpServerAnySourcePort() throws Exception {
-        // CHECKSTYLE:OFF Generated code
-        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
-            // Ethernet header.
-            "9cd917000000001c2e0000000800" +
-            // IP header.
-            "45a00148000040003d115087d18194fb0a0f7af2" +
-            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
-            // NOTE: The server source port is not the canonical port 67.
-            "C29F004401341268" +
-            // BOOTP header.
-            "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
-            // MAC address.
-            "9cd91700000000000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options.
-            "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
-            "d18180060f0777766d2e6564751c040a0fffffff000000"));
-        // CHECKSTYLE:ON Generated code
-
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
-        assertTrue(offerPacket instanceof DhcpOfferPacket);
-        assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac()));
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();
-        assertDhcpResults("10.15.122.242/16", "10.15.200.23",
-                "209.129.128.3,209.129.148.3,209.129.128.6",
-                "wvm.edu", "10.1.105.252", "", null, 86400, false, 0, dhcpResults);
-    }
-
-    @Test
-    public void testUdpInvalidDstPort() throws Exception {
-        // CHECKSTYLE:OFF Generated code
-        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
-            // Ethernet header.
-            "9cd917000000001c2e0000000800" +
-            // IP header.
-            "45a00148000040003d115087d18194fb0a0f7af2" +
-            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
-            // NOTE: The destination port is a non-DHCP port.
-            "0043aaaa01341268" +
-            // BOOTP header.
-            "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
-            // MAC address.
-            "9cd91700000000000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options.
-            "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
-            "d18180060f0777766d2e6564751c040a0fffffff000000"));
-        // CHECKSTYLE:ON Generated code
-
-        try {
-            DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
-            fail("Packet with invalid dst port did not throw ParseException");
-        } catch (ParseException expected) {}
-    }
-
-    @Test
-    public void testMultipleRouters() throws Exception {
-        // CHECKSTYLE:OFF Generated code
-        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
-            // Ethernet header.
-            "fc3d93000000" + "081735000000" + "0800" +
-            // IP header.
-            "45000148c2370000ff117ac2c0a8bd02ffffffff" +
-            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
-            "0043004401343beb" +
-            // BOOTP header.
-            "0201060027f518e20000800000000000c0a8bd310000000000000000" +
-            // MAC address.
-            "fc3d9300000000000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // File.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            // Options.
-            "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" +
-            "0308c0a8bd01ffffff0006080808080808080404ff000000000000"));
-        // CHECKSTYLE:ON Generated code
-
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
-        assertTrue(offerPacket instanceof DhcpOfferPacket);
-        assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac()));
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();
-        assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4",
-                null, "192.171.189.2", "", null, 28800, false, 0, dhcpResults);
-    }
-
-    @Test
-    public void testDiscoverPacket() throws Exception {
-        short secs = 7;
-        int transactionId = 0xdeadbeef;
-        byte[] hwaddr = {
-                (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a
-        };
-
-        ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
-                DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr,
-                false /* do unicast */, DhcpClient.REQUESTED_PARAMS);
-
-        byte[] headers = new byte[] {
-            // Ethernet header.
-            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
-            (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
-            (byte) 0x08, (byte) 0x00,
-            // IP header.
-            (byte) 0x45, (byte) 0x10, (byte) 0x01, (byte) 0x56,
-            (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x00,
-            (byte) 0x40, (byte) 0x11, (byte) 0x39, (byte) 0x88,
-            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
-            // UDP header.
-            (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43,
-            (byte) 0x01, (byte) 0x42, (byte) 0x6a, (byte) 0x4a,
-            // BOOTP.
-            (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00,
-            (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
-            (byte) 0x00, (byte) 0x07, (byte) 0x00, (byte) 0x00,
-            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-            (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b,
-            (byte) 0xb1, (byte) 0x7a
-        };
-        byte[] options = new byte[] {
-            // Magic cookie 0x63825363.
-            (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
-            // Message type DISCOVER.
-            (byte) 0x35, (byte) 0x01, (byte) 0x01,
-            // Client identifier Ethernet, da:01:19:5b:b1:7a.
-            (byte) 0x3d, (byte) 0x07,
-                    (byte) 0x01,
-                    (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
-            // Max message size 1500.
-            (byte) 0x39, (byte) 0x02, (byte) 0x05, (byte) 0xdc,
-            // Version "android-dhcp-???".
-            (byte) 0x3c, (byte) 0x10,
-                    'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 'h', 'c', 'p', '-', '?', '?', '?',
-            // Hostname "android-01234567890abcde"
-            (byte) 0x0c, (byte) 0x18,
-                    'a', 'n', 'd', 'r', 'o', 'i', 'd', '-',
-                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e',
-            // Requested parameter list.
-            (byte) 0x37, (byte) 0x0a,
-                DHCP_SUBNET_MASK,
-                DHCP_ROUTER,
-                DHCP_DNS_SERVER,
-                DHCP_DOMAIN_NAME,
-                DHCP_MTU,
-                DHCP_BROADCAST_ADDRESS,
-                DHCP_LEASE_TIME,
-                DHCP_RENEWAL_TIME,
-                DHCP_REBINDING_TIME,
-                DHCP_VENDOR_INFO,
-            // End options.
-            (byte) 0xff,
-            // Our packets are always of even length. TODO: find out why and possibly fix it.
-            (byte) 0x00
-        };
-        byte[] expected = new byte[DhcpPacket.MIN_PACKET_LENGTH_L2 + options.length];
-        assertTrue((expected.length & 1) == 0);
-        System.arraycopy(headers, 0, expected, 0, headers.length);
-        System.arraycopy(options, 0, expected, DhcpPacket.MIN_PACKET_LENGTH_L2, options.length);
-
-        byte[] actual = new byte[packet.limit()];
-        packet.get(actual);
-        String msg =
-                "Expected:\n  " + Arrays.toString(expected) +
-                "\nActual:\n  " + Arrays.toString(actual);
-        assertTrue(msg, Arrays.equals(expected, actual));
-    }
-
-    public void checkBuildOfferPacket(int leaseTimeSecs, @Nullable String hostname)
-            throws Exception {
-        final int renewalTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) / 2);
-        final int rebindingTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) * 875 / 1000);
-        final int transactionId = 0xdeadbeef;
-
-        final ByteBuffer packet = DhcpPacket.buildOfferPacket(
-                DhcpPacket.ENCAP_BOOTP, transactionId, false /* broadcast */,
-                SERVER_ADDR, INADDR_ANY /* relayIp */, CLIENT_ADDR /* yourIp */,
-                CLIENT_MAC, leaseTimeSecs, NETMASK /* netMask */,
-                BROADCAST_ADDR /* bcAddr */, Collections.singletonList(SERVER_ADDR) /* gateways */,
-                Collections.singletonList(SERVER_ADDR) /* dnsServers */,
-                SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, hostname,
-                false /* metered */, MTU);
-
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        // BOOTP headers
-        bos.write(new byte[] {
-                (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x00,
-                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
-                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-                // ciaddr
-                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-        });
-        // yiaddr
-        bos.write(CLIENT_ADDR.getAddress());
-        // siaddr
-        bos.write(SERVER_ADDR.getAddress());
-        // giaddr
-        bos.write(INADDR_ANY.getAddress());
-        // chaddr
-        bos.write(CLIENT_MAC);
-
-        // Padding
-        bos.write(new byte[202]);
-
-        // Options
-        bos.write(new byte[]{
-                // Magic cookie 0x63825363.
-                (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
-                // Message type OFFER.
-                (byte) 0x35, (byte) 0x01, (byte) 0x02,
-        });
-        // Server ID
-        bos.write(new byte[] { (byte) 0x36, (byte) 0x04 });
-        bos.write(SERVER_ADDR.getAddress());
-        // Lease time
-        bos.write(new byte[] { (byte) 0x33, (byte) 0x04 });
-        bos.write(intToByteArray(leaseTimeSecs));
-        if (leaseTimeSecs != INFINITE_LEASE) {
-            // Renewal time
-            bos.write(new byte[]{(byte) 0x3a, (byte) 0x04});
-            bos.write(intToByteArray(renewalTime));
-            // Rebinding time
-            bos.write(new byte[]{(byte) 0x3b, (byte) 0x04});
-            bos.write(intToByteArray(rebindingTime));
-        }
-        // Subnet mask
-        bos.write(new byte[] { (byte) 0x01, (byte) 0x04 });
-        bos.write(NETMASK.getAddress());
-        // Broadcast address
-        bos.write(new byte[] { (byte) 0x1c, (byte) 0x04 });
-        bos.write(BROADCAST_ADDR.getAddress());
-        // Router
-        bos.write(new byte[] { (byte) 0x03, (byte) 0x04 });
-        bos.write(SERVER_ADDR.getAddress());
-        // Nameserver
-        bos.write(new byte[] { (byte) 0x06, (byte) 0x04 });
-        bos.write(SERVER_ADDR.getAddress());
-        // Hostname
-        if (hostname != null) {
-            bos.write(new byte[]{(byte) 0x0c, (byte) hostname.length()});
-            bos.write(hostname.getBytes(Charset.forName("US-ASCII")));
-        }
-        // MTU
-        bos.write(new byte[] { (byte) 0x1a, (byte) 0x02 });
-        bos.write(shortToByteArray(MTU));
-        // End options.
-        bos.write(0xff);
-
-        if ((bos.size() & 1) != 0) {
-            bos.write(0x00);
-        }
-
-        final byte[] expected = bos.toByteArray();
-        final byte[] actual = new byte[packet.limit()];
-        packet.get(actual);
-        final String msg = "Expected:\n  " + HexDump.dumpHexString(expected) +
-                "\nActual:\n  " + HexDump.dumpHexString(actual);
-        assertTrue(msg, Arrays.equals(expected, actual));
-    }
-
-    @Test
-    public void testOfferPacket() throws Exception {
-        checkBuildOfferPacket(3600, HOSTNAME);
-        checkBuildOfferPacket(Integer.MAX_VALUE, HOSTNAME);
-        checkBuildOfferPacket(0x80000000, HOSTNAME);
-        checkBuildOfferPacket(INFINITE_LEASE, HOSTNAME);
-        checkBuildOfferPacket(3600, null);
-    }
-
-    private static byte[] intToByteArray(int val) {
-        return ByteBuffer.allocate(4).putInt(val).array();
-    }
-
-    private static byte[] shortToByteArray(short val) {
-        return ByteBuffer.allocate(2).putShort(val).array();
-    }
-}
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java
deleted file mode 100644
index f0e2f1b..0000000
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * 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.dhcp;
-
-import static android.net.InetAddresses.parseNumericAddress;
-import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
-import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
-import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
-import static android.net.dhcp.DhcpPacket.INADDR_ANY;
-import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
-import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.INetworkStackStatusCallback;
-import android.net.LinkAddress;
-import android.net.MacAddress;
-import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException;
-import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException;
-import android.net.dhcp.DhcpServer.Clock;
-import android.net.dhcp.DhcpServer.Dependencies;
-import android.net.util.SharedLog;
-import android.os.HandlerThread;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.net.Inet4Address;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-@RunWithLooper
-public class DhcpServerTest {
-    private static final String TEST_IFACE = "testiface";
-
-    private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2");
-    private static final LinkAddress TEST_SERVER_LINKADDR = new LinkAddress(TEST_SERVER_ADDR, 20);
-    private static final Set<Inet4Address> TEST_DEFAULT_ROUTERS = new HashSet<>(
-            Arrays.asList(parseAddr("192.168.0.123"), parseAddr("192.168.0.124")));
-    private static final Set<Inet4Address> TEST_DNS_SERVERS = new HashSet<>(
-            Arrays.asList(parseAddr("192.168.0.126"), parseAddr("192.168.0.127")));
-    private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>(
-            Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201")));
-    private static final long TEST_LEASE_TIME_SECS = 3600L;
-    private static final int TEST_MTU = 1500;
-    private static final String TEST_HOSTNAME = "testhostname";
-
-    private static final int TEST_TRANSACTION_ID = 123;
-    private static final byte[] TEST_CLIENT_MAC_BYTES = new byte [] { 1, 2, 3, 4, 5, 6 };
-    private static final MacAddress TEST_CLIENT_MAC = MacAddress.fromBytes(TEST_CLIENT_MAC_BYTES);
-    private static final Inet4Address TEST_CLIENT_ADDR = parseAddr("192.168.0.42");
-
-    private static final long TEST_CLOCK_TIME = 1234L;
-    private static final int TEST_LEASE_EXPTIME_SECS = 3600;
-    private static final DhcpLease TEST_LEASE = new DhcpLease(null, TEST_CLIENT_MAC,
-            TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
-            null /* hostname */);
-    private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC,
-            TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_HOSTNAME);
-
-    @NonNull @Mock
-    private Dependencies mDeps;
-    @NonNull @Mock
-    private DhcpLeaseRepository mRepository;
-    @NonNull @Mock
-    private Clock mClock;
-    @NonNull @Mock
-    private DhcpPacketListener mPacketListener;
-
-    @NonNull @Captor
-    private ArgumentCaptor<ByteBuffer> mSentPacketCaptor;
-    @NonNull @Captor
-    private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;
-
-    @NonNull
-    private HandlerThread mHandlerThread;
-    @NonNull
-    private TestableLooper mLooper;
-    @NonNull
-    private DhcpServer mServer;
-
-    @Nullable
-    private String mPrevShareClassloaderProp;
-
-    private final INetworkStackStatusCallback mAssertSuccessCallback =
-            new INetworkStackStatusCallback.Stub() {
-        @Override
-        public void onStatusAvailable(int statusCode) {
-            assertEquals(STATUS_SUCCESS, statusCode);
-        }
-
-        @Override
-        public int getInterfaceVersion() {
-            return this.VERSION;
-        }
-    };
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
-        when(mDeps.makeClock()).thenReturn(mClock);
-        when(mDeps.makePacketListener()).thenReturn(mPacketListener);
-        doNothing().when(mDeps)
-                .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture());
-        when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME);
-
-        final DhcpServingParams servingParams = new DhcpServingParams.Builder()
-                .setDefaultRouters(TEST_DEFAULT_ROUTERS)
-                .setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS)
-                .setDnsServers(TEST_DNS_SERVERS)
-                .setServerAddr(TEST_SERVER_LINKADDR)
-                .setLinkMtu(TEST_MTU)
-                .setExcludedAddrs(TEST_EXCLUDED_ADDRS)
-                .build();
-
-        mLooper = TestableLooper.get(this);
-        mHandlerThread = spy(new HandlerThread("TestDhcpServer"));
-        when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
-        mServer = new DhcpServer(mHandlerThread, TEST_IFACE, servingParams,
-                new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
-
-        mServer.start(mAssertSuccessCallback);
-        mLooper.processAllMessages();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mServer.stop(mAssertSuccessCallback);
-        mLooper.processMessages(1);
-        verify(mPacketListener, times(1)).stop();
-        verify(mHandlerThread, times(1)).quitSafely();
-    }
-
-    @Test
-    public void testStart() throws Exception {
-        verify(mPacketListener, times(1)).start();
-    }
-
-    @Test
-    public void testDiscover() throws Exception {
-        // TODO: refactor packet construction to eliminate unnecessary/confusing/duplicate fields
-        when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
-                eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
-                .thenReturn(TEST_LEASE);
-
-        final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
-                (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
-                false /* broadcast */, INADDR_ANY /* srcIp */);
-        mServer.processPacket(discover, DHCP_CLIENT);
-
-        assertResponseSentTo(TEST_CLIENT_ADDR);
-        final DhcpOfferPacket packet = assertOffer(getPacket());
-        assertMatchesTestLease(packet);
-    }
-
-    @Test
-    public void testDiscover_OutOfAddresses() throws Exception {
-        when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
-                eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
-                .thenThrow(new OutOfAddressesException("Test exception"));
-
-        final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
-                (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
-                false /* broadcast */, INADDR_ANY /* srcIp */);
-        mServer.processPacket(discover, DHCP_CLIENT);
-
-        assertResponseSentTo(INADDR_BROADCAST);
-        final DhcpNakPacket packet = assertNak(getPacket());
-        assertMatchesClient(packet);
-    }
-
-    private DhcpRequestPacket makeRequestSelectingPacket() {
-        final DhcpRequestPacket request = new DhcpRequestPacket(TEST_TRANSACTION_ID,
-                (short) 0 /* secs */, INADDR_ANY /* clientIp */, INADDR_ANY /* relayIp */,
-                TEST_CLIENT_MAC_BYTES, false /* broadcast */);
-        request.mServerIdentifier = TEST_SERVER_ADDR;
-        request.mRequestedIp = TEST_CLIENT_ADDR;
-        return request;
-    }
-
-    @Test
-    public void testRequest_Selecting_Ack() throws Exception {
-        when(mRepository.requestLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
-                eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
-                eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, eq(TEST_HOSTNAME)))
-                .thenReturn(TEST_LEASE_WITH_HOSTNAME);
-
-        final DhcpRequestPacket request = makeRequestSelectingPacket();
-        request.mHostName = TEST_HOSTNAME;
-        request.mRequestedParams = new byte[] { DHCP_HOST_NAME };
-        mServer.processPacket(request, DHCP_CLIENT);
-
-        assertResponseSentTo(TEST_CLIENT_ADDR);
-        final DhcpAckPacket packet = assertAck(getPacket());
-        assertMatchesTestLease(packet, TEST_HOSTNAME);
-    }
-
-    @Test
-    public void testRequest_Selecting_Nak() throws Exception {
-        when(mRepository.requestLease(isNull(), eq(TEST_CLIENT_MAC),
-                eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
-                eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, isNull() /* hostname */))
-                .thenThrow(new InvalidAddressException("Test error"));
-
-        final DhcpRequestPacket request = makeRequestSelectingPacket();
-        mServer.processPacket(request, DHCP_CLIENT);
-
-        assertResponseSentTo(INADDR_BROADCAST);
-        final DhcpNakPacket packet = assertNak(getPacket());
-        assertMatchesClient(packet);
-    }
-
-    @Test
-    public void testRequest_Selecting_WrongClientPort() throws Exception {
-        final DhcpRequestPacket request = makeRequestSelectingPacket();
-        mServer.processPacket(request, 50000);
-
-        verify(mRepository, never())
-                .requestLease(any(), any(), any(), any(), any(), anyBoolean(), any());
-        verify(mDeps, never()).sendPacket(any(), any(), any());
-    }
-
-    @Test
-    public void testRelease() throws Exception {
-        final DhcpReleasePacket release = new DhcpReleasePacket(TEST_TRANSACTION_ID,
-                TEST_SERVER_ADDR, TEST_CLIENT_ADDR,
-                INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES);
-        mServer.processPacket(release, DHCP_CLIENT);
-
-        verify(mRepository, times(1))
-                .releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR));
-    }
-
-    /* TODO: add more tests once packet construction is refactored, including:
-     *  - usage of giaddr
-     *  - usage of broadcast bit
-     *  - other request states (init-reboot/renewing/rebinding)
-     */
-
-    private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) {
-        assertMatchesClient(packet);
-        assertFalse(packet.hasExplicitClientId());
-        assertEquals(TEST_SERVER_ADDR, packet.mServerIdentifier);
-        assertEquals(TEST_CLIENT_ADDR, packet.mYourIp);
-        assertNotNull(packet.mLeaseTime);
-        assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime);
-        assertEquals(hostname, packet.mHostName);
-    }
-
-    private void assertMatchesTestLease(@NonNull DhcpPacket packet) {
-        assertMatchesTestLease(packet, null);
-    }
-
-    private void assertMatchesClient(@NonNull DhcpPacket packet) {
-        assertEquals(TEST_TRANSACTION_ID, packet.mTransId);
-        assertEquals(TEST_CLIENT_MAC, MacAddress.fromBytes(packet.mClientMac));
-    }
-
-    private void assertResponseSentTo(@NonNull Inet4Address addr) {
-        assertEquals(addr, mResponseDstAddrCaptor.getValue());
-    }
-
-    private static DhcpNakPacket assertNak(@Nullable DhcpPacket packet) {
-        assertTrue(packet instanceof DhcpNakPacket);
-        return (DhcpNakPacket) packet;
-    }
-
-    private static DhcpAckPacket assertAck(@Nullable DhcpPacket packet) {
-        assertTrue(packet instanceof DhcpAckPacket);
-        return (DhcpAckPacket) packet;
-    }
-
-    private static DhcpOfferPacket assertOffer(@Nullable DhcpPacket packet) {
-        assertTrue(packet instanceof DhcpOfferPacket);
-        return (DhcpOfferPacket) packet;
-    }
-
-    private DhcpPacket getPacket() throws Exception {
-        verify(mDeps, times(1)).sendPacket(any(), any(), any());
-        return DhcpPacket.decodeFullPacket(mSentPacketCaptor.getValue(), ENCAP_BOOTP);
-    }
-
-    private static Inet4Address parseAddr(@Nullable String inet4Addr) {
-        return (Inet4Address) parseNumericAddress(inet4Addr);
-    }
-}
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
deleted file mode 100644
index 57a87a4..0000000
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * 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.dhcp;
-
-import static android.net.InetAddresses.parseNumericAddress;
-import static android.net.dhcp.DhcpServingParams.MTU_UNSET;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.LinkAddress;
-import android.net.dhcp.DhcpServingParams.InvalidParameterException;
-import android.net.shared.Inet4AddressUtils;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Modifier;
-import java.net.Inet4Address;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class DhcpServingParamsTest {
-    @NonNull
-    private DhcpServingParams.Builder mBuilder;
-
-    private static final Set<Inet4Address> TEST_DEFAULT_ROUTERS = new HashSet<>(
-            Arrays.asList(parseAddr("192.168.0.123"), parseAddr("192.168.0.124")));
-    private static final long TEST_LEASE_TIME_SECS = 3600L;
-    private static final Set<Inet4Address> TEST_DNS_SERVERS = new HashSet<>(
-            Arrays.asList(parseAddr("192.168.0.126"), parseAddr("192.168.0.127")));
-    private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2");
-    private static final LinkAddress TEST_LINKADDR = new LinkAddress(TEST_SERVER_ADDR, 20);
-    private static final int TEST_MTU = 1500;
-    private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>(
-            Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201")));
-    private static final boolean TEST_METERED = true;
-
-    @Before
-    public void setUp() {
-        mBuilder = new DhcpServingParams.Builder()
-                .setDefaultRouters(TEST_DEFAULT_ROUTERS)
-                .setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS)
-                .setDnsServers(TEST_DNS_SERVERS)
-                .setServerAddr(TEST_LINKADDR)
-                .setLinkMtu(TEST_MTU)
-                .setExcludedAddrs(TEST_EXCLUDED_ADDRS)
-                .setMetered(TEST_METERED);
-    }
-
-    @Test
-    public void testBuild_Immutable() throws InvalidParameterException {
-        final Set<Inet4Address> routers = new HashSet<>(TEST_DEFAULT_ROUTERS);
-        final Set<Inet4Address> dnsServers = new HashSet<>(TEST_DNS_SERVERS);
-        final Set<Inet4Address> excludedAddrs = new HashSet<>(TEST_EXCLUDED_ADDRS);
-
-        final DhcpServingParams params = mBuilder
-                .setDefaultRouters(routers)
-                .setDnsServers(dnsServers)
-                .setExcludedAddrs(excludedAddrs)
-                .build();
-
-        // Modifications to source objects should not affect builder or final parameters
-        final Inet4Address addedAddr = parseAddr("192.168.0.223");
-        routers.add(addedAddr);
-        dnsServers.add(addedAddr);
-        excludedAddrs.add(addedAddr);
-
-        assertEquals(TEST_DEFAULT_ROUTERS, params.defaultRouters);
-        assertEquals(TEST_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs);
-        assertEquals(TEST_DNS_SERVERS, params.dnsServers);
-        assertEquals(TEST_LINKADDR, params.serverAddr);
-        assertEquals(TEST_MTU, params.linkMtu);
-        assertEquals(TEST_METERED, params.metered);
-
-        assertContains(params.excludedAddrs, TEST_EXCLUDED_ADDRS);
-        assertContains(params.excludedAddrs, TEST_DEFAULT_ROUTERS);
-        assertContains(params.excludedAddrs, TEST_DNS_SERVERS);
-        assertContains(params.excludedAddrs, TEST_SERVER_ADDR);
-
-        assertFalse("excludedAddrs should not contain " + addedAddr,
-                params.excludedAddrs.contains(addedAddr));
-    }
-
-    @Test(expected = InvalidParameterException.class)
-    public void testBuild_NegativeLeaseTime() throws InvalidParameterException {
-        mBuilder.setDhcpLeaseTimeSecs(-1).build();
-    }
-
-    @Test(expected = InvalidParameterException.class)
-    public void testBuild_LeaseTimeTooLarge() throws InvalidParameterException {
-        // Set lease time larger than max value for uint32
-        mBuilder.setDhcpLeaseTimeSecs(1L << 32).build();
-    }
-
-    @Test
-    public void testBuild_InfiniteLeaseTime() throws InvalidParameterException {
-        final long infiniteLeaseTime = 0xffffffffL;
-        final DhcpServingParams params = mBuilder
-                .setDhcpLeaseTimeSecs(infiniteLeaseTime).build();
-        assertEquals(infiniteLeaseTime, params.dhcpLeaseTimeSecs);
-        assertTrue(params.dhcpLeaseTimeSecs > 0L);
-    }
-
-    @Test
-    public void testBuild_UnsetMtu() throws InvalidParameterException {
-        final DhcpServingParams params = mBuilder.setLinkMtu(MTU_UNSET).build();
-        assertEquals(MTU_UNSET, params.linkMtu);
-    }
-
-    @Test(expected = InvalidParameterException.class)
-    public void testBuild_MtuTooSmall() throws InvalidParameterException {
-        mBuilder.setLinkMtu(20).build();
-    }
-
-    @Test(expected = InvalidParameterException.class)
-    public void testBuild_MtuTooLarge() throws InvalidParameterException {
-        mBuilder.setLinkMtu(65_536).build();
-    }
-
-    @Test(expected = InvalidParameterException.class)
-    public void testBuild_IPv6Addr() throws InvalidParameterException {
-        mBuilder.setServerAddr(new LinkAddress(parseNumericAddress("fe80::1111"), 120)).build();
-    }
-
-    @Test(expected = InvalidParameterException.class)
-    public void testBuild_PrefixTooLarge() throws InvalidParameterException {
-        mBuilder.setServerAddr(new LinkAddress(TEST_SERVER_ADDR, 15)).build();
-    }
-
-    @Test(expected = InvalidParameterException.class)
-    public void testBuild_PrefixTooSmall() throws InvalidParameterException {
-        mBuilder.setDefaultRouters(parseAddr("192.168.0.254"))
-                .setServerAddr(new LinkAddress(TEST_SERVER_ADDR, 31))
-                .build();
-    }
-
-    @Test(expected = InvalidParameterException.class)
-    public void testBuild_RouterNotInPrefix() throws InvalidParameterException {
-        mBuilder.setDefaultRouters(parseAddr("192.168.254.254")).build();
-    }
-
-    @Test
-    public void testFromParcelableObject() throws InvalidParameterException {
-        final DhcpServingParams params = mBuilder.build();
-        final DhcpServingParamsParcel parcel = new DhcpServingParamsParcel();
-        parcel.defaultRouters = toIntArray(TEST_DEFAULT_ROUTERS);
-        parcel.dhcpLeaseTimeSecs = TEST_LEASE_TIME_SECS;
-        parcel.dnsServers = toIntArray(TEST_DNS_SERVERS);
-        parcel.serverAddr = inet4AddressToIntHTH(TEST_SERVER_ADDR);
-        parcel.serverAddrPrefixLength = TEST_LINKADDR.getPrefixLength();
-        parcel.linkMtu = TEST_MTU;
-        parcel.excludedAddrs = toIntArray(TEST_EXCLUDED_ADDRS);
-        parcel.metered = TEST_METERED;
-        final DhcpServingParams parceled = DhcpServingParams.fromParcelableObject(parcel);
-
-        assertEquals(params.defaultRouters, parceled.defaultRouters);
-        assertEquals(params.dhcpLeaseTimeSecs, parceled.dhcpLeaseTimeSecs);
-        assertEquals(params.dnsServers, parceled.dnsServers);
-        assertEquals(params.serverAddr, parceled.serverAddr);
-        assertEquals(params.linkMtu, parceled.linkMtu);
-        assertEquals(params.excludedAddrs, parceled.excludedAddrs);
-        assertEquals(params.metered, parceled.metered);
-
-        // Ensure that we do not miss any field if added in the future
-        final long numFields = Arrays.stream(DhcpServingParams.class.getDeclaredFields())
-                .filter(f -> !Modifier.isStatic(f.getModifiers()))
-                .count();
-        assertEquals(7, numFields);
-    }
-
-    @Test(expected = InvalidParameterException.class)
-    public void testFromParcelableObject_NullArgument() throws InvalidParameterException {
-        DhcpServingParams.fromParcelableObject(null);
-    }
-
-    private static int[] toIntArray(Collection<Inet4Address> addrs) {
-        return addrs.stream().mapToInt(Inet4AddressUtils::inet4AddressToIntHTH).toArray();
-    }
-
-    private static <T> void assertContains(@NonNull Set<T> set, @NonNull Set<T> subset) {
-        for (final T elem : subset) {
-            assertContains(set, elem);
-        }
-    }
-
-    private static <T> void assertContains(@NonNull Set<T> set, @Nullable T elem) {
-        assertTrue("Set does not contain " + elem, set.contains(elem));
-    }
-
-    @NonNull
-    private static Inet4Address parseAddr(@NonNull String inet4Addr) {
-        return (Inet4Address) parseNumericAddress(inet4Addr);
-    }
-}
diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
deleted file mode 100644
index 5f80006..0000000
--- a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
+++ /dev/null
@@ -1,547 +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.net.ip;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.AlarmManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.MacAddress;
-import android.net.NetworkStackIpMemoryStore;
-import android.net.RouteInfo;
-import android.net.ipmemorystore.NetworkAttributes;
-import android.net.shared.InitialConfiguration;
-import android.net.shared.ProvisioningConfiguration;
-import android.net.util.InterfaceParams;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.server.NetworkObserver;
-import com.android.server.NetworkObserverRegistry;
-import com.android.server.NetworkStackService;
-
-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.net.InetAddress;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Tests for IpClient.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class IpClientTest {
-    private static final int DEFAULT_AVOIDBADWIFI_CONFIG_VALUE = 1;
-
-    private static final String VALID = "VALID";
-    private static final String INVALID = "INVALID";
-    private static final String TEST_IFNAME = "test_wlan0";
-    private static final int TEST_IFINDEX = 1001;
-    // See RFC 7042#section-2.1.2 for EUI-48 documentation values.
-    private static final MacAddress TEST_MAC = MacAddress.fromString("00:00:5E:00:53:01");
-    private static final int TEST_TIMEOUT_MS = 400;
-    private static final String TEST_L2KEY = "some l2key";
-    private static final String TEST_GROUPHINT = "some grouphint";
-
-    @Mock private Context mContext;
-    @Mock private ConnectivityManager mCm;
-    @Mock private NetworkObserverRegistry mObserverRegistry;
-    @Mock private INetd mNetd;
-    @Mock private Resources mResources;
-    @Mock private IIpClientCallbacks mCb;
-    @Mock private AlarmManager mAlarm;
-    @Mock private IpClient.Dependencies mDependencies;
-    @Mock private ContentResolver mContentResolver;
-    @Mock private NetworkStackService.NetworkStackServiceManager mNetworkStackServiceManager;
-    @Mock private NetworkStackIpMemoryStore mIpMemoryStore;
-
-    private NetworkObserver mObserver;
-    private InterfaceParams mIfParams;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
-        when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm);
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mDependencies.getNetd(any())).thenReturn(mNetd);
-        when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
-                .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
-        when(mContext.getContentResolver()).thenReturn(mContentResolver);
-
-        mIfParams = null;
-    }
-
-    private void setTestInterfaceParams(String ifname) {
-        mIfParams = (ifname != null)
-                ? new InterfaceParams(ifname, TEST_IFINDEX, TEST_MAC)
-                : null;
-        when(mDependencies.getInterfaceParams(anyString())).thenReturn(mIfParams);
-    }
-
-    private IpClient makeIpClient(String ifname) throws Exception {
-        setTestInterfaceParams(ifname);
-        final IpClient ipc = new IpClient(mContext, ifname, mCb, mObserverRegistry,
-                mNetworkStackServiceManager, mDependencies);
-        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(ifname, false);
-        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(ifname);
-        ArgumentCaptor<NetworkObserver> arg = ArgumentCaptor.forClass(NetworkObserver.class);
-        verify(mObserverRegistry, times(1)).registerObserverForNonblockingCallback(arg.capture());
-        mObserver = arg.getValue();
-        reset(mObserverRegistry);
-        reset(mNetd);
-        // Verify IpClient doesn't call onLinkPropertiesChange() when it starts.
-        verify(mCb, never()).onLinkPropertiesChange(any());
-        reset(mCb);
-        return ipc;
-    }
-
-    private static LinkProperties makeEmptyLinkProperties(String iface) {
-        final LinkProperties empty = new LinkProperties();
-        empty.setInterfaceName(iface);
-        return empty;
-    }
-
-    private void verifyNetworkAttributesStored(final String l2Key,
-            final NetworkAttributes attributes) {
-        // TODO : when storing is implemented, turn this on
-        // verify(mIpMemoryStore).storeNetworkAttributes(eq(l2Key), eq(attributes), any());
-    }
-
-    @Test
-    public void testNullInterfaceNameMostDefinitelyThrows() throws Exception {
-        setTestInterfaceParams(null);
-        try {
-            final IpClient ipc = new IpClient(mContext, null, mCb, mObserverRegistry,
-                    mNetworkStackServiceManager, mDependencies);
-            ipc.shutdown();
-            fail();
-        } catch (NullPointerException npe) {
-            // Phew; null interface names not allowed.
-        }
-    }
-
-    @Test
-    public void testNullCallbackMostDefinitelyThrows() throws Exception {
-        final String ifname = "lo";
-        setTestInterfaceParams(ifname);
-        try {
-            final IpClient ipc = new IpClient(mContext, ifname, null, mObserverRegistry,
-                    mNetworkStackServiceManager, mDependencies);
-            ipc.shutdown();
-            fail();
-        } catch (NullPointerException npe) {
-            // Phew; null callbacks not allowed.
-        }
-    }
-
-    @Test
-    public void testInvalidInterfaceDoesNotThrow() throws Exception {
-        setTestInterfaceParams(TEST_IFNAME);
-        final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mObserverRegistry,
-                mNetworkStackServiceManager, mDependencies);
-        verifyNoMoreInteractions(mIpMemoryStore);
-        ipc.shutdown();
-    }
-
-    @Test
-    public void testInterfaceNotFoundFailsImmediately() throws Exception {
-        setTestInterfaceParams(null);
-        final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mObserverRegistry,
-                mNetworkStackServiceManager, mDependencies);
-        ipc.startProvisioning(new ProvisioningConfiguration());
-        verify(mCb, times(1)).onProvisioningFailure(any());
-        verify(mIpMemoryStore, never()).storeNetworkAttributes(any(), any(), any());
-        ipc.shutdown();
-    }
-
-    @Test
-    public void testDefaultProvisioningConfiguration() throws Exception {
-        final String iface = TEST_IFNAME;
-        final IpClient ipc = makeIpClient(iface);
-
-        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
-                .withoutIPv4()
-                // TODO: mock IpReachabilityMonitor's dependencies (NetworkInterface, PowerManager)
-                // and enable it in this test
-                .withoutIpReachabilityMonitor()
-                .build();
-
-        ipc.startProvisioning(config);
-        verify(mCb, times(1)).setNeighborDiscoveryOffload(true);
-        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
-        verify(mCb, never()).onProvisioningFailure(any());
-        verify(mIpMemoryStore, never()).storeNetworkAttributes(any(), any(), any());
-
-        ipc.shutdown();
-        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
-        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
-        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
-                .onLinkPropertiesChange(makeEmptyLinkProperties(iface));
-    }
-
-    @Test
-    public void testProvisioningWithInitialConfiguration() throws Exception {
-        final String iface = TEST_IFNAME;
-        final IpClient ipc = makeIpClient(iface);
-        final String l2Key = TEST_L2KEY;
-        final String groupHint = TEST_GROUPHINT;
-
-        String[] addresses = {
-            "fe80::a4be:f92:e1f7:22d1/64",
-            "fe80::f04a:8f6:6a32:d756/64",
-            "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"
-        };
-        String[] prefixes = { "fe80::/64", "fd2c:4e57:8e3c::/64" };
-
-        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
-                .withoutIPv4()
-                .withoutIpReachabilityMonitor()
-                .withInitialConfiguration(conf(links(addresses), prefixes(prefixes), ips()))
-                .build();
-
-        ipc.startProvisioning(config);
-        verify(mCb, times(1)).setNeighborDiscoveryOffload(true);
-        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
-        verify(mCb, never()).onProvisioningFailure(any());
-        ipc.setL2KeyAndGroupHint(l2Key, groupHint);
-
-        for (String addr : addresses) {
-            String[] parts = addr.split("/");
-            verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1))
-                    .interfaceAddAddress(iface, parts[0], Integer.parseInt(parts[1]));
-        }
-
-        final int lastAddr = addresses.length - 1;
-
-        // Add N - 1 addresses
-        for (int i = 0; i < lastAddr; i++) {
-            mObserver.onInterfaceAddressUpdated(new LinkAddress(addresses[i]), iface);
-            verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(any());
-            reset(mCb);
-        }
-
-        // Add Nth address
-        mObserver.onInterfaceAddressUpdated(new LinkAddress(addresses[lastAddr]), iface);
-        LinkProperties want = linkproperties(links(addresses), routes(prefixes));
-        want.setInterfaceName(iface);
-        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(want);
-        verifyNetworkAttributesStored(l2Key, new NetworkAttributes.Builder()
-                .setGroupHint(groupHint)
-                .build());
-
-        ipc.shutdown();
-        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
-        verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
-        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
-                .onLinkPropertiesChange(makeEmptyLinkProperties(iface));
-        verifyNoMoreInteractions(mIpMemoryStore);
-    }
-
-    @Test
-    public void testIsProvisioned() throws Exception {
-        InitialConfiguration empty = conf(links(), prefixes());
-        IsProvisionedTestCase[] testcases = {
-            // nothing
-            notProvisionedCase(links(), routes(), dns(), null),
-            notProvisionedCase(links(), routes(), dns(), empty),
-
-            // IPv4
-            provisionedCase(links("192.0.2.12/24"), routes(), dns(), empty),
-
-            // IPv6
-            notProvisionedCase(
-                    links("fe80::a4be:f92:e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
-                    routes(), dns(), empty),
-            notProvisionedCase(
-                    links("fe80::a4be:f92:e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
-                    routes("fe80::/64", "fd2c:4e57:8e3c::/64"), dns("fd00:1234:5678::1000"), empty),
-            provisionedCase(
-                    links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
-                    routes("::/0"),
-                    dns("2001:db8:dead:beef:f00::02"), empty),
-
-            // Initial configuration
-            provisionedCase(
-                    links("fe80::e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
-                    routes("fe80::/64", "fd2c:4e57:8e3c::/64"),
-                    dns(),
-                    conf(links("fe80::e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
-                        prefixes( "fe80::/64", "fd2c:4e57:8e3c::/64"), ips()))
-        };
-
-        for (IsProvisionedTestCase testcase : testcases) {
-            if (IpClient.isProvisioned(testcase.lp, testcase.config) != testcase.isProvisioned) {
-                fail(testcase.errorMessage());
-            }
-        }
-    }
-
-    static class IsProvisionedTestCase {
-        boolean isProvisioned;
-        LinkProperties lp;
-        InitialConfiguration config;
-
-        String errorMessage() {
-            return String.format("expected %s with config %s to be %s, but was %s",
-                     lp, config, provisioned(isProvisioned), provisioned(!isProvisioned));
-        }
-
-        static String provisioned(boolean isProvisioned) {
-            return isProvisioned ? "provisioned" : "not provisioned";
-        }
-    }
-
-    static IsProvisionedTestCase provisionedCase(Set<LinkAddress> lpAddrs, Set<RouteInfo> lpRoutes,
-            Set<InetAddress> lpDns, InitialConfiguration config) {
-        return provisioningTest(true, lpAddrs, lpRoutes, lpDns, config);
-    }
-
-    static IsProvisionedTestCase notProvisionedCase(Set<LinkAddress> lpAddrs,
-            Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config) {
-        return provisioningTest(false, lpAddrs, lpRoutes, lpDns, config);
-    }
-
-    static IsProvisionedTestCase provisioningTest(boolean isProvisioned, Set<LinkAddress> lpAddrs,
-            Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config) {
-        IsProvisionedTestCase testcase = new IsProvisionedTestCase();
-        testcase.isProvisioned = isProvisioned;
-        testcase.lp = new LinkProperties();
-        testcase.lp.setLinkAddresses(lpAddrs);
-        for (RouteInfo route : lpRoutes) {
-            testcase.lp.addRoute(route);
-        }
-        for (InetAddress dns : lpDns) {
-            testcase.lp.addDnsServer(dns);
-        }
-        testcase.config = config;
-        return testcase;
-    }
-
-    @Test
-    public void testInitialConfigurations() throws Exception {
-        InitialConfigurationTestCase[] testcases = {
-            validConf("valid IPv4 configuration",
-                    links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("192.0.2.2")),
-            validConf("another valid IPv4 configuration",
-                    links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns()),
-            validConf("valid IPv6 configurations",
-                    links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
-                    prefixes("2001:db8:dead:beef::/64", "fe80::/64"),
-                    dns("2001:db8:dead:beef:f00::02")),
-            validConf("valid IPv6 configurations",
-                    links("fe80::1/64"), prefixes("fe80::/64"), dns()),
-            validConf("valid IPv6/v4 configuration",
-                    links("2001:db8:dead:beef:f00::a0/48", "192.0.2.12/24"),
-                    prefixes("2001:db8:dead:beef::/64", "192.0.2.0/24"),
-                    dns("192.0.2.2", "2001:db8:dead:beef:f00::02")),
-            validConf("valid IPv6 configuration without any GUA.",
-                    links("fd00:1234:5678::1/48"),
-                    prefixes("fd00:1234:5678::/48"),
-                    dns("fd00:1234:5678::1000")),
-
-            invalidConf("empty configuration", links(), prefixes(), dns()),
-            invalidConf("v4 addr and dns not in any prefix",
-                    links("192.0.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")),
-            invalidConf("v4 addr not in any prefix",
-                    links("198.51.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")),
-            invalidConf("v4 dns addr not in any prefix",
-                    links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("198.51.100.2")),
-            invalidConf("v6 addr not in any prefix",
-                    links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
-                    prefixes("2001:db8:dead:beef::/64"),
-                    dns("2001:db8:dead:beef:f00::02")),
-            invalidConf("v6 dns addr not in any prefix",
-                    links("fe80::1/64"), prefixes("fe80::/64"), dns("2001:db8:dead:beef:f00::02")),
-            invalidConf("default ipv6 route and no GUA",
-                    links("fd01:1111:2222:3333::a0/128"), prefixes("::/0"), dns()),
-            invalidConf("invalid v6 prefix length",
-                    links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/32"),
-                    dns()),
-            invalidConf("another invalid v6 prefix length",
-                    links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/72"),
-                    dns())
-        };
-
-        for (InitialConfigurationTestCase testcase : testcases) {
-            if (testcase.config.isValid() != testcase.isValid) {
-                fail(testcase.errorMessage());
-            }
-        }
-    }
-
-    static class InitialConfigurationTestCase {
-        String descr;
-        boolean isValid;
-        InitialConfiguration config;
-        public String errorMessage() {
-            return String.format("%s: expected configuration %s to be %s, but was %s",
-                    descr, config, validString(isValid), validString(!isValid));
-        }
-        static String validString(boolean isValid) {
-            return isValid ? VALID : INVALID;
-        }
-    }
-
-    static InitialConfigurationTestCase validConf(String descr, Set<LinkAddress> links,
-            Set<IpPrefix> prefixes, Set<InetAddress> dns) {
-        return confTestCase(descr, true, conf(links, prefixes, dns));
-    }
-
-    static InitialConfigurationTestCase invalidConf(String descr, Set<LinkAddress> links,
-            Set<IpPrefix> prefixes, Set<InetAddress> dns) {
-        return confTestCase(descr, false, conf(links, prefixes, dns));
-    }
-
-    static InitialConfigurationTestCase confTestCase(
-            String descr, boolean isValid, InitialConfiguration config) {
-        InitialConfigurationTestCase testcase = new InitialConfigurationTestCase();
-        testcase.descr = descr;
-        testcase.isValid = isValid;
-        testcase.config = config;
-        return testcase;
-    }
-
-    static LinkProperties linkproperties(Set<LinkAddress> addresses, Set<RouteInfo> routes) {
-        LinkProperties lp = new LinkProperties();
-        lp.setLinkAddresses(addresses);
-        for (RouteInfo route : routes) {
-            lp.addRoute(route);
-        }
-        return lp;
-    }
-
-    static InitialConfiguration conf(Set<LinkAddress> links, Set<IpPrefix> prefixes) {
-        return conf(links, prefixes, new HashSet<>());
-    }
-
-    static InitialConfiguration conf(
-            Set<LinkAddress> links, Set<IpPrefix> prefixes, Set<InetAddress> dns) {
-        InitialConfiguration conf = new InitialConfiguration();
-        conf.ipAddresses.addAll(links);
-        conf.directlyConnectedRoutes.addAll(prefixes);
-        conf.dnsServers.addAll(dns);
-        return conf;
-    }
-
-    static Set<RouteInfo> routes(String... routes) {
-        return mapIntoSet(routes, (r) -> new RouteInfo(new IpPrefix(r)));
-    }
-
-    static Set<IpPrefix> prefixes(String... prefixes) {
-        return mapIntoSet(prefixes, IpPrefix::new);
-    }
-
-    static Set<LinkAddress> links(String... addresses) {
-        return mapIntoSet(addresses, LinkAddress::new);
-    }
-
-    static Set<InetAddress> ips(String... addresses) {
-        return mapIntoSet(addresses, InetAddress::getByName);
-    }
-
-    static Set<InetAddress> dns(String... addresses) {
-        return ips(addresses);
-    }
-
-    static <A, B> Set<B> mapIntoSet(A[] in, Fn<A, B> fn) {
-        Set<B> out = new HashSet<>(in.length);
-        for (A item : in) {
-            try {
-                out.add(fn.call(item));
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        }
-        return out;
-    }
-
-    interface Fn<A,B> {
-        B call(A a) throws Exception;
-    }
-
-    @Test
-    public void testAll() {
-        List<String> list1 = Arrays.asList();
-        List<String> list2 = Arrays.asList("foo");
-        List<String> list3 = Arrays.asList("bar", "baz");
-        List<String> list4 = Arrays.asList("foo", "bar", "baz");
-
-        assertTrue(InitialConfiguration.all(list1, (x) -> false));
-        assertFalse(InitialConfiguration.all(list2, (x) -> false));
-        assertTrue(InitialConfiguration.all(list3, (x) -> true));
-        assertTrue(InitialConfiguration.all(list2, (x) -> x.charAt(0) == 'f'));
-        assertFalse(InitialConfiguration.all(list4, (x) -> x.charAt(0) == 'f'));
-    }
-
-    @Test
-    public void testAny() {
-        List<String> list1 = Arrays.asList();
-        List<String> list2 = Arrays.asList("foo");
-        List<String> list3 = Arrays.asList("bar", "baz");
-        List<String> list4 = Arrays.asList("foo", "bar", "baz");
-
-        assertFalse(InitialConfiguration.any(list1, (x) -> true));
-        assertTrue(InitialConfiguration.any(list2, (x) -> true));
-        assertTrue(InitialConfiguration.any(list2, (x) -> x.charAt(0) == 'f'));
-        assertFalse(InitialConfiguration.any(list3, (x) -> x.charAt(0) == 'f'));
-        assertTrue(InitialConfiguration.any(list4, (x) -> x.charAt(0) == 'f'));
-    }
-
-    @Test
-    public void testFindAll() {
-        List<String> list1 = Arrays.asList();
-        List<String> list2 = Arrays.asList("foo");
-        List<String> list3 = Arrays.asList("foo", "bar", "baz");
-
-        assertEquals(list1, IpClient.findAll(list1, (x) -> true));
-        assertEquals(list1, IpClient.findAll(list3, (x) -> false));
-        assertEquals(list3, IpClient.findAll(list3, (x) -> true));
-        assertEquals(list2, IpClient.findAll(list3, (x) -> x.charAt(0) == 'f'));
-    }
-}
diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
deleted file mode 100644
index 64b168a..0000000
--- a/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ip;
-
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.net.util.InterfaceParams;
-import android.net.util.SharedLog;
-import android.os.Handler;
-import android.os.Looper;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for IpReachabilityMonitor.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class IpReachabilityMonitorTest {
-
-    @Mock IpReachabilityMonitor.Callback mCallback;
-    @Mock IpReachabilityMonitor.Dependencies mDependencies;
-    @Mock SharedLog mLog;
-    @Mock Context mContext;
-    Handler mHandler;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mLog.forSubComponent(anyString())).thenReturn(mLog);
-        mHandler = new Handler(Looper.getMainLooper());
-    }
-
-    IpReachabilityMonitor makeMonitor() {
-        final InterfaceParams ifParams = new InterfaceParams("fake0", 1, null);
-        return new IpReachabilityMonitor(
-                mContext, ifParams, mHandler, mLog, mCallback, false, mDependencies);
-    }
-
-    @Test
-    public void testNothing() {
-        IpReachabilityMonitor monitor = makeMonitor();
-    }
-}
diff --git a/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
deleted file mode 100644
index 71be8b3..0000000
--- a/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.net.MacAddress;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import libcore.util.HexEncoding;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for ConnectivityPacketSummary.
- *
- * @hide
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ConnectivityPacketSummaryTest {
-    private static final MacAddress MYHWADDR = MacAddress.fromString("80:7a:bf:6f:48:f3");
-
-    private String getSummary(String hexBytes) {
-        hexBytes = hexBytes.replaceAll("\\s+", "");
-        final byte[] bytes = HexEncoding.decode(hexBytes.toCharArray(), false);
-        return ConnectivityPacketSummary.summarize(MYHWADDR, bytes);
-    }
-
-    @Test
-    public void testParseICMPv6DADProbe() {
-        final String packet =
-                // Ethernet
-                "3333FF6F48F3 807ABF6F48F3 86DD" +
-                // IPv6
-                "600000000018 3A FF" +
-                "00000000000000000000000000000000" +
-                "FF0200000000000000000001FF6F48F3" +
-                // ICMPv6
-                "87 00 A8E7" +
-                "00000000" +
-                "FE80000000000000827ABFFFFE6F48F3";
-
-        final String expected =
-                "TX 80:7a:bf:6f:48:f3 > 33:33:ff:6f:48:f3 ipv6" +
-                " :: > ff02::1:ff6f:48f3 icmp6" +
-                " ns fe80::827a:bfff:fe6f:48f3";
-
-        assertEquals(expected, getSummary(packet));
-    }
-
-    @Test
-    public void testParseICMPv6RS() {
-        final String packet =
-                // Ethernet
-                "333300000002 807ABF6F48F3 86DD" +
-                // IPv6
-                "600000000010 3A FF" +
-                "FE80000000000000827ABFFFFE6F48F3" +
-                "FF020000000000000000000000000002" +
-                // ICMPv6 RS
-                "85 00 6973" +
-                "00000000" +
-                "01 01 807ABF6F48F3";
-
-        final String expected =
-                "TX 80:7a:bf:6f:48:f3 > 33:33:00:00:00:02 ipv6" +
-                " fe80::827a:bfff:fe6f:48f3 > ff02::2 icmp6" +
-                " rs slla 80:7a:bf:6f:48:f3";
-
-        assertEquals(expected, getSummary(packet));
-    }
-
-    @Test
-    public void testParseICMPv6RA() {
-        final String packet =
-                // Ethernet
-                "807ABF6F48F3 100E7E263FC1 86DD" +
-                // IPv6
-                "600000000068 3A FF" +
-                "FE80000000000000FA000004FD000001" +
-                "FE80000000000000827ABFFFFE6F48F3" +
-                // ICMPv6 RA
-                "86 00 8141" +
-                "40 00 0E10" +
-                "00000000" +
-                "00000000" +
-                "01 01 00005E000265" +
-                "05 01 0000000005DC" +
-                "19 05 000000000E10" +
-                "      20014860486000000000000000008844" +
-                "      20014860486000000000000000008888" +
-                "03 04 40 C0" +
-                "      00278D00" +
-                "      00093A80" +
-                "      00000000" +
-                "      2401FA000004FD000000000000000000";
-
-        final String expected =
-                "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
-                " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" +
-                " ra slla 00:00:5e:00:02:65 mtu 1500";
-
-        assertEquals(expected, getSummary(packet));
-    }
-
-    @Test
-    public void testParseICMPv6NS() {
-        final String packet =
-                // Ethernet
-                  "807ABF6F48F3 100E7E263FC1 86DD" +
-                  // IPv6
-                  "6C0000000020 3A FF" +
-                  "FE80000000000000FA000004FD000001" +
-                  "FF0200000000000000000001FF01C146" +
-                  // ICMPv6 NS
-                  "87 00 8AD4" +
-                  "00000000" +
-                  "2401FA000004FD0015EA6A5C7B01C146" +
-                  "01 01 00005E000265";
-
-        final String expected =
-                "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
-                " fe80::fa00:4:fd00:1 > ff02::1:ff01:c146 icmp6" +
-                " ns 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 slla 00:00:5e:00:02:65";
-
-        assertEquals(expected, getSummary(packet));
-    }
-
-    @Test
-    public void testInvalidICMPv6NDLength() {
-        final String packet =
-                // Ethernet
-                "807ABF6F48F3 100E7E263FC1 86DD" +
-                // IPv6
-                "600000000068 3A FF" +
-                "FE80000000000000FA000004FD000001" +
-                "FE80000000000000827ABFFFFE6F48F3" +
-                // ICMPv6 RA
-                "86 00 8141" +
-                "40 00 0E10" +
-                "00000000" +
-                "00000000" +
-                "01 01 00005E000265" +
-                "00 00 0102030405D6";
-
-        final String expected =
-                "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
-                " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" +
-                " ra slla 00:00:5e:00:02:65 <malformed>";
-
-        assertEquals(expected, getSummary(packet));
-    }
-
-    @Test
-    public void testParseICMPv6NA() {
-        final String packet =
-                // Ethernet
-                "00005E000265 807ABF6F48F3 86DD" +
-                "600000000020 3A FF" +
-                "2401FA000004FD0015EA6A5C7B01C146" +
-                "FE80000000000000FA000004FD000001" +
-                "88 00 E8126" +
-                "0000000" +
-                "2401FA000004FD0015EA6A5C7B01C146" +
-                "02 01 807ABF6F48F3";
-
-        final String expected =
-                "TX 80:7a:bf:6f:48:f3 > 00:00:5e:00:02:65 ipv6" +
-                " 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 > fe80::fa00:4:fd00:1 icmp6" +
-                " na 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 tlla 80:7a:bf:6f:48:f3";
-
-        assertEquals(expected, getSummary(packet));
-    }
-
-    @Test
-    public void testParseARPRequest() {
-        final String packet =
-                // Ethernet
-                  "FFFFFFFFFFFF 807ABF6F48F3 0806" +
-                  // ARP
-                  "0001 0800 06 04" +
-                  // Request
-                  "0001" +
-                  "807ABF6F48F3 64706ADB" +
-                  "000000000000 64706FFD";
-
-        final String expected =
-                "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff arp" +
-                " who-has 100.112.111.253";
-
-        assertEquals(expected, getSummary(packet));
-    }
-
-    @Test
-    public void testParseARPReply() {
-        final String packet =
-                // Ethernet
-                  "807ABF6F48F3 288A1CA8DFC1 0806" +
-                  // ARP
-                  "0001 0800 06 04" +
-                  // Reply
-                  "0002" +
-                  "288A1CA8DFC1 64706FFD"+
-                  "807ABF6F48F3 64706ADB" +
-                  // Ethernet padding to packet min size.
-                  "0000000000000000000000000000";
-
-        final String expected =
-                "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 arp" +
-                " reply 100.112.111.253 28:8a:1c:a8:df:c1";
-
-        assertEquals(expected, getSummary(packet));
-    }
-
-    @Test
-    public void testParseDHCPv4Discover() {
-        final String packet =
-                // Ethernet
-                "FFFFFFFFFFFF 807ABF6F48F3 0800" +
-                // IPv4
-                "451001580000400040113986" +
-                "00000000" +
-                "FFFFFFFF" +
-                // UDP
-                "0044 0043" +
-                "0144 5559" +
-                // DHCPv4
-                "01 01 06 00" +
-                "79F7ACA4" +
-                "0000 0000" +
-                "00000000" +
-                "00000000" +
-                "00000000" +
-                "00000000" +
-                "807ABF6F48F300000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "63 82 53 63" +
-                "35 01 01" +
-                "3D 07 01807ABF6F48F3" +
-                "39 02 05DC" +
-                "3C 12 616E64726F69642D646863702D372E312E32" +
-                "0C 18 616E64726F69642D36623030366333313333393835343139" +
-                "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" +
-                "FF" +
-                "00";
-
-        final String expectedPrefix =
-                "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" +
-                " 0.0.0.0 > 255.255.255.255 udp" +
-                " 68 > 67 dhcp4" +
-                " 80:7a:bf:6f:48:f3 DISCOVER";
-
-        assertTrue(getSummary(packet).startsWith(expectedPrefix));
-    }
-
-    @Test
-    public void testParseDHCPv4Offer() {
-        final String packet =
-                // Ethernet
-                "807ABF6F48F3 288A1CA8DFC1 0800" +
-                // IPv4
-                "4500013D4D2C0000401188CB" +
-                "64706FFD" +
-                "64706ADB" +
-                // UDP
-                "0043 0044" +
-                "0129 371D" +
-                // DHCPv4
-                "02 01 06 01" +
-                "79F7ACA4" +
-                "0000 0000" +
-                "00000000" +
-                "64706ADB" +
-                "00000000" +
-                "00000000" +
-                "807ABF6F48F300000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "63 82 53 63" +
-                "35 01 02" +
-                "36 04 AC188A0B" +
-                "33 04 00000708" +
-                "01 04 FFFFF000" +
-                "03 04 64706FFE" +
-                "06 08 08080808" +
-                "      08080404" +
-                "FF0001076165313A363636FF";
-
-        final String expectedPrefix =
-                "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" +
-                " 100.112.111.253 > 100.112.106.219 udp" +
-                " 67 > 68 dhcp4" +
-                " 80:7a:bf:6f:48:f3 OFFER";
-
-        assertTrue(getSummary(packet).startsWith(expectedPrefix));
-    }
-
-    @Test
-    public void testParseDHCPv4Request() {
-        final String packet =
-                // Ethernet
-                "FFFFFFFFFFFF 807ABF6F48F3 0800" +
-                // IPv4
-                "45100164000040004011397A" +
-                "00000000" +
-                "FFFFFFFF" +
-                // UDP
-                "0044 0043" +
-                "0150 E5C7" +
-                // DHCPv4
-                "01 01 06 00" +
-                "79F7ACA4" +
-                "0001 0000" +
-                "00000000" +
-                "00000000" +
-                "00000000" +
-                "00000000" +
-                "807ABF6F48F300000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "63 82 53 63" +
-                "35 01 03" +
-                "3D 07 01807ABF6F48F3" +
-                "32 04 64706ADB" +
-                "36 04 AC188A0B" +
-                "39 02 05DC" +
-                "3C 12 616E64726F69642D646863702D372E312E32" +
-                "0C 18 616E64726F69642D36623030366333313333393835343139" +
-                "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" +
-                "FF" +
-                "00";
-
-        final String expectedPrefix =
-                "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" +
-                " 0.0.0.0 > 255.255.255.255 udp" +
-                " 68 > 67 dhcp4" +
-                " 80:7a:bf:6f:48:f3 REQUEST";
-
-        assertTrue(getSummary(packet).startsWith(expectedPrefix));
-    }
-
-    @Test
-    public void testParseDHCPv4Ack() {
-        final String packet =
-                // Ethernet
-                "807ABF6F48F3 288A1CA8DFC1 0800" +
-                // IPv4
-                "4500013D4D3B0000401188BC" +
-                "64706FFD" +
-                "64706ADB" +
-                // UDP
-                "0043 0044" +
-                "0129 341C" +
-                // DHCPv4
-                "02 01 06 01" +
-                "79F7ACA4" +
-                "0001 0000" +
-                "00000000" +
-                "64706ADB" +
-                "00000000" +
-                "00000000" +
-                "807ABF6F48F300000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "0000000000000000000000000000000000000000000000000000000000000000" +
-                "63 82 53 63" +
-                "35 01 05" +
-                "36 04 AC188A0B" +
-                "33 04 00000708" +
-                "01 04 FFFFF000" +
-                "03 04 64706FFE" +
-                "06 08 08080808" +
-                "      08080404" +
-                "FF0001076165313A363636FF";
-
-        final String expectedPrefix =
-                "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" +
-                " 100.112.111.253 > 100.112.106.219 udp" +
-                " 67 > 68 dhcp4" +
-                " 80:7a:bf:6f:48:f3 ACK";
-
-        assertTrue(getSummary(packet).startsWith(expectedPrefix));
-    }
-}
diff --git a/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java
deleted file mode 100644
index 289dcad..0000000
--- a/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.util;
-
-import static android.net.util.PacketReader.DEFAULT_RECV_BUF_SIZE;
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOCK_NONBLOCK;
-import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_SNDTIMEO;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructTimeval;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.FileDescriptor;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketException;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for PacketReader.
- *
- * @hide
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class PacketReaderTest {
-    static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress();
-    static final StructTimeval TIMEO = StructTimeval.fromMillis(500);
-
-    protected CountDownLatch mLatch;
-    protected FileDescriptor mLocalSocket;
-    protected InetSocketAddress mLocalSockName;
-    protected byte[] mLastRecvBuf;
-    protected boolean mStopped;
-    protected HandlerThread mHandlerThread;
-    protected PacketReader mReceiver;
-
-    class UdpLoopbackReader extends PacketReader {
-        public UdpLoopbackReader(Handler h) {
-            super(h);
-        }
-
-        @Override
-        protected FileDescriptor createFd() {
-            FileDescriptor s = null;
-            try {
-                s = Os.socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
-                Os.bind(s, LOOPBACK6, 0);
-                mLocalSockName = (InetSocketAddress) Os.getsockname(s);
-                Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO);
-            } catch (ErrnoException|SocketException e) {
-                closeFd(s);
-                fail();
-                return null;
-            }
-
-            mLocalSocket = s;
-            return s;
-        }
-
-        @Override
-        protected void handlePacket(byte[] recvbuf, int length) {
-            mLastRecvBuf = Arrays.copyOf(recvbuf, length);
-            mLatch.countDown();
-        }
-
-        @Override
-        protected void onStart() {
-            mStopped = false;
-            mLatch.countDown();
-        }
-
-        @Override
-        protected void onStop() {
-            mStopped = true;
-            mLatch.countDown();
-        }
-    };
-
-    @Before
-    public void setUp() {
-        resetLatch();
-        mLocalSocket = null;
-        mLocalSockName = null;
-        mLastRecvBuf = null;
-        mStopped = false;
-
-        mHandlerThread = new HandlerThread(PacketReaderTest.class.getSimpleName());
-        mHandlerThread.start();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mReceiver != null) {
-            mHandlerThread.getThreadHandler().post(() -> { mReceiver.stop(); });
-            waitForActivity();
-        }
-        mReceiver = null;
-        mHandlerThread.quit();
-        mHandlerThread = null;
-    }
-
-    void resetLatch() { mLatch = new CountDownLatch(1); }
-
-    void waitForActivity() throws Exception {
-        try {
-            mLatch.await(1000, TimeUnit.MILLISECONDS);
-        } finally {
-            resetLatch();
-        }
-    }
-
-    void sendPacket(byte[] contents) throws Exception {
-        final DatagramSocket sender = new DatagramSocket();
-        sender.connect(mLocalSockName);
-        sender.send(new DatagramPacket(contents, contents.length));
-        sender.close();
-    }
-
-    @Test
-    public void testBasicWorking() throws Exception {
-        final Handler h = mHandlerThread.getThreadHandler();
-        mReceiver = new UdpLoopbackReader(h);
-
-        h.post(() -> { mReceiver.start(); });
-        waitForActivity();
-        assertTrue(mLocalSockName != null);
-        assertEquals(LOOPBACK6, mLocalSockName.getAddress());
-        assertTrue(0 < mLocalSockName.getPort());
-        assertTrue(mLocalSocket != null);
-        assertFalse(mStopped);
-
-        final byte[] one = "one 1".getBytes("UTF-8");
-        sendPacket(one);
-        waitForActivity();
-        assertEquals(1, mReceiver.numPacketsReceived());
-        assertTrue(Arrays.equals(one, mLastRecvBuf));
-        assertFalse(mStopped);
-
-        final byte[] two = "two 2".getBytes("UTF-8");
-        sendPacket(two);
-        waitForActivity();
-        assertEquals(2, mReceiver.numPacketsReceived());
-        assertTrue(Arrays.equals(two, mLastRecvBuf));
-        assertFalse(mStopped);
-
-        mReceiver.stop();
-        waitForActivity();
-        assertEquals(2, mReceiver.numPacketsReceived());
-        assertTrue(Arrays.equals(two, mLastRecvBuf));
-        assertTrue(mStopped);
-        mReceiver = null;
-    }
-
-    class NullPacketReader extends PacketReader {
-        public NullPacketReader(Handler h, int recvbufsize) {
-            super(h, recvbufsize);
-        }
-
-        @Override
-        public FileDescriptor createFd() { return null; }
-    }
-
-    @Test
-    public void testMinimalRecvBufSize() throws Exception {
-        final Handler h = mHandlerThread.getThreadHandler();
-
-        for (int i : new int[]{-1, 0, 1, DEFAULT_RECV_BUF_SIZE-1}) {
-            final PacketReader b = new NullPacketReader(h, i);
-            assertEquals(DEFAULT_RECV_BUF_SIZE, b.recvBufSize());
-        }
-    }
-}
diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
deleted file mode 100644
index 832b712..0000000
--- a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ /dev/null
@@ -1,1022 +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.connectivity;
-
-import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
-import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE;
-import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL;
-import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD;
-import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
-import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.DnsResolver;
-import android.net.INetworkMonitorCallbacks;
-import android.net.InetAddresses;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.captiveportal.CaptivePortalProbeResult;
-import android.net.metrics.IpConnectivityLog;
-import android.net.shared.PrivateDnsConfig;
-import android.net.util.SharedLog;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.telephony.CellSignalStrength;
-import android.telephony.TelephonyManager;
-import android.util.ArrayMap;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.networkstack.R;
-import com.android.networkstack.metrics.DataStallDetectionStats;
-import com.android.networkstack.metrics.DataStallStatsUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Random;
-import java.util.concurrent.Executor;
-
-import javax.net.ssl.SSLHandshakeException;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class NetworkMonitorTest {
-    private static final String LOCATION_HEADER = "location";
-
-    private @Mock Context mContext;
-    private @Mock Resources mResources;
-    private @Mock IpConnectivityLog mLogger;
-    private @Mock SharedLog mValidationLogger;
-    private @Mock NetworkInfo mNetworkInfo;
-    private @Mock DnsResolver mDnsResolver;
-    private @Mock ConnectivityManager mCm;
-    private @Mock TelephonyManager mTelephony;
-    private @Mock WifiManager mWifi;
-    private @Mock HttpURLConnection mHttpConnection;
-    private @Mock HttpURLConnection mHttpsConnection;
-    private @Mock HttpURLConnection mFallbackConnection;
-    private @Mock HttpURLConnection mOtherFallbackConnection;
-    private @Mock Random mRandom;
-    private @Mock NetworkMonitor.Dependencies mDependencies;
-    private @Mock INetworkMonitorCallbacks mCallbacks;
-    private @Spy Network mCleartextDnsNetwork = new Network(TEST_NETID);
-    private @Mock Network mNetwork;
-    private @Mock DataStallStatsUtils mDataStallStatsUtils;
-    private @Mock WifiInfo mWifiInfo;
-    private @Captor ArgumentCaptor<String> mNetworkTestedRedirectUrlCaptor;
-
-    private HashSet<WrappedNetworkMonitor> mCreatedNetworkMonitors;
-    private HashSet<BroadcastReceiver> mRegisteredReceivers;
-
-    private static final int TEST_NETID = 4242;
-    private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
-    private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
-    private static final String TEST_FALLBACK_URL = "http://fallback.google.com/gen_204";
-    private static final String TEST_OTHER_FALLBACK_URL = "http://otherfallback.google.com/gen_204";
-    private static final String TEST_MCCMNC = "123456";
-
-    private static final int RETURN_CODE_DNS_SUCCESS = 0;
-    private static final int RETURN_CODE_DNS_TIMEOUT = 255;
-    private static final int DEFAULT_DNS_TIMEOUT_THRESHOLD = 5;
-
-    private static final int HANDLER_TIMEOUT_MS = 1000;
-
-    private static final LinkProperties TEST_LINK_PROPERTIES = new LinkProperties();
-
-    private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities()
-            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-            .addCapability(NET_CAPABILITY_INTERNET);
-
-    private static final NetworkCapabilities NOT_METERED_CAPABILITIES = new NetworkCapabilities()
-            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-            .addCapability(NET_CAPABILITY_INTERNET)
-            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-
-    private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
-            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
-    /**
-     * Fakes DNS responses.
-     *
-     * Allows test methods to configure the IP addresses that will be resolved by
-     * Network#getAllByName and by DnsResolver#query.
-     */
-    class FakeDns {
-        private final ArrayMap<String, List<InetAddress>> mAnswers = new ArrayMap<>();
-        private boolean mNonBypassPrivateDnsWorking = true;
-
-        /** Whether DNS queries on mNonBypassPrivateDnsWorking should succeed. */
-        private void setNonBypassPrivateDnsWorking(boolean working) {
-            mNonBypassPrivateDnsWorking = working;
-        }
-
-        /** Clears all DNS entries. */
-        private synchronized void clearAll() {
-            mAnswers.clear();
-        }
-
-        /** Returns the answer for a given name on the given mock network. */
-        private synchronized List<InetAddress> getAnswer(Object mock, String hostname) {
-            if (mock == mNetwork && !mNonBypassPrivateDnsWorking) {
-                return null;
-            }
-            if (mAnswers.containsKey(hostname)) {
-                return mAnswers.get(hostname);
-            }
-            return mAnswers.get("*");
-        }
-
-        /** Sets the answer for a given name. */
-        private synchronized void setAnswer(String hostname, String[] answer)
-                throws UnknownHostException {
-            if (answer == null) {
-                mAnswers.remove(hostname);
-            } else {
-                List<InetAddress> answerList = new ArrayList<>();
-                for (String addr : answer) {
-                    answerList.add(InetAddresses.parseNumericAddress(addr));
-                }
-                mAnswers.put(hostname, answerList);
-            }
-        }
-
-        /** Simulates a getAllByName call for the specified name on the specified mock network. */
-        private InetAddress[] getAllByName(Object mock, String hostname)
-                throws UnknownHostException {
-            List<InetAddress> answer = getAnswer(mock, hostname);
-            if (answer == null || answer.size() == 0) {
-                throw new UnknownHostException(hostname);
-            }
-            return answer.toArray(new InetAddress[0]);
-        }
-
-        /** Starts mocking DNS queries. */
-        private void startMocking() throws UnknownHostException {
-            // Queries on mNetwork using getAllByName.
-            doAnswer(invocation -> {
-                return getAllByName(invocation.getMock(), invocation.getArgument(0));
-            }).when(mNetwork).getAllByName(any());
-
-            // Queries on mCleartextDnsNetwork using DnsResolver#query.
-            doAnswer(invocation -> {
-                String hostname = (String) invocation.getArgument(1);
-                Executor executor = (Executor) invocation.getArgument(3);
-                DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5);
-
-                List<InetAddress> answer = getAnswer(invocation.getMock(), hostname);
-                if (answer != null && answer.size() > 0) {
-                    new Handler(Looper.getMainLooper()).post(() -> {
-                        executor.execute(() -> callback.onAnswer(answer, 0));
-                    });
-                }
-                // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
-                return null;
-            }).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
-
-            // Queries on mCleartextDnsNetwork using using DnsResolver#query with QueryType.
-            doAnswer(invocation -> {
-                String hostname = (String) invocation.getArgument(1);
-                Executor executor = (Executor) invocation.getArgument(4);
-                DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(6);
-
-                List<InetAddress> answer = getAnswer(invocation.getMock(), hostname);
-                if (answer != null && answer.size() > 0) {
-                    new Handler(Looper.getMainLooper()).post(() -> {
-                        executor.execute(() -> callback.onAnswer(answer, 0));
-                    });
-                }
-                // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
-                return null;
-            }).when(mDnsResolver).query(any(), any(), anyInt(), anyInt(), any(), any(), any());
-        }
-    }
-
-    private FakeDns mFakeDns;
-
-    @Before
-    public void setUp() throws IOException {
-        MockitoAnnotations.initMocks(this);
-        when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mCleartextDnsNetwork);
-        when(mDependencies.getDnsResolver()).thenReturn(mDnsResolver);
-        when(mDependencies.getRandom()).thenReturn(mRandom);
-        when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
-                .thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
-        when(mDependencies.getDeviceConfigPropertyInt(any(), eq(CAPTIVE_PORTAL_USE_HTTPS),
-                anyInt())).thenReturn(1);
-        when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
-                .thenReturn(TEST_HTTP_URL);
-        when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), any()))
-                .thenReturn(TEST_HTTPS_URL);
-
-        doReturn(mCleartextDnsNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
-
-        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
-        when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
-        when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
-        when(mContext.getResources()).thenReturn(mResources);
-
-        when(mResources.getString(anyInt())).thenReturn("");
-        when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
-
-        when(mNetworkInfo.getType()).thenReturn(ConnectivityManager.TYPE_WIFI);
-        setFallbackUrl(TEST_FALLBACK_URL);
-        setOtherFallbackUrls(TEST_OTHER_FALLBACK_URL);
-        setFallbackSpecs(null); // Test with no fallback spec by default
-        when(mRandom.nextInt()).thenReturn(0);
-
-        // DNS probe timeout should not be defined more than half of HANDLER_TIMEOUT_MS. Otherwise,
-        // it will fail the test because of timeout expired for querying AAAA and A sequentially.
-        when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
-                .thenReturn(200);
-
-        doAnswer((invocation) -> {
-            URL url = invocation.getArgument(0);
-            switch(url.toString()) {
-                case TEST_HTTP_URL:
-                    return mHttpConnection;
-                case TEST_HTTPS_URL:
-                    return mHttpsConnection;
-                case TEST_FALLBACK_URL:
-                    return mFallbackConnection;
-                case TEST_OTHER_FALLBACK_URL:
-                    return mOtherFallbackConnection;
-                default:
-                    fail("URL not mocked: " + url.toString());
-                    return null;
-            }
-        }).when(mCleartextDnsNetwork).openConnection(any());
-        when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
-        when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
-
-        mFakeDns = new FakeDns();
-        mFakeDns.startMocking();
-        mFakeDns.setAnswer("*", new String[]{"2001:db8::1", "192.0.2.2"});
-
-        when(mContext.registerReceiver(any(BroadcastReceiver.class), any())).then((invocation) -> {
-            mRegisteredReceivers.add(invocation.getArgument(0));
-            return new Intent();
-        });
-
-        doAnswer((invocation) -> {
-            mRegisteredReceivers.remove(invocation.getArgument(0));
-            return null;
-        }).when(mContext).unregisterReceiver(any());
-
-        setMinDataStallEvaluateInterval(500);
-        setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
-        setValidDataStallDnsTimeThreshold(500);
-        setConsecutiveDnsTimeoutThreshold(5);
-
-        mCreatedNetworkMonitors = new HashSet<>();
-        mRegisteredReceivers = new HashSet<>();
-    }
-
-    @After
-    public void tearDown() {
-        mFakeDns.clearAll();
-        assertTrue(mCreatedNetworkMonitors.size() > 0);
-        // Make a local copy of mCreatedNetworkMonitors because during the iteration below,
-        // WrappedNetworkMonitor#onQuitting will delete elements from it on the handler threads.
-        WrappedNetworkMonitor[] networkMonitors = mCreatedNetworkMonitors.toArray(
-                new WrappedNetworkMonitor[0]);
-        for (WrappedNetworkMonitor nm : networkMonitors) {
-            nm.notifyNetworkDisconnected();
-            nm.awaitQuit();
-        }
-        assertEquals("NetworkMonitor still running after disconnect",
-                0, mCreatedNetworkMonitors.size());
-        assertEquals("BroadcastReceiver still registered after disconnect",
-                0, mRegisteredReceivers.size());
-    }
-
-    private class WrappedNetworkMonitor extends NetworkMonitor {
-        private long mProbeTime = 0;
-        private final ConditionVariable mQuitCv = new ConditionVariable(false);
-
-        WrappedNetworkMonitor() {
-            super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger,
-                    mDependencies, mDataStallStatsUtils);
-        }
-
-        @Override
-        protected long getLastProbeTime() {
-            return mProbeTime;
-        }
-
-        protected void setLastProbeTime(long time) {
-            mProbeTime = time;
-        }
-
-        @Override
-        protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
-            generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
-        }
-
-        @Override
-        protected void onQuitting() {
-            assertTrue(mCreatedNetworkMonitors.remove(this));
-            mQuitCv.open();
-        }
-
-        protected void awaitQuit() {
-            assertTrue("NetworkMonitor did not quit after " + HANDLER_TIMEOUT_MS + "ms",
-                    mQuitCv.block(HANDLER_TIMEOUT_MS));
-        }
-    }
-
-    private WrappedNetworkMonitor makeMonitor(NetworkCapabilities nc) {
-        final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
-        nm.start();
-        setNetworkCapabilities(nm, nc);
-        waitForIdle(nm.getHandler());
-        mCreatedNetworkMonitors.add(nm);
-        return nm;
-    }
-
-    private WrappedNetworkMonitor makeMeteredNetworkMonitor() {
-        final WrappedNetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
-        return nm;
-    }
-
-    private WrappedNetworkMonitor makeNotMeteredNetworkMonitor() {
-        final WrappedNetworkMonitor nm = makeMonitor(NOT_METERED_CAPABILITIES);
-        return nm;
-    }
-
-    private void setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc) {
-        nm.notifyNetworkCapabilitiesChanged(nc);
-        waitForIdle(nm.getHandler());
-    }
-
-    private void waitForIdle(Handler handler) {
-        final ConditionVariable cv = new ConditionVariable(false);
-        handler.post(cv::open);
-        if (!cv.block(HANDLER_TIMEOUT_MS)) {
-            fail("Timed out waiting for handler");
-        }
-    }
-
-    @Test
-    public void testGetIntSetting() throws Exception {
-        WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
-
-        // No config resource, no device config. Expect to get default resource.
-        doThrow(new Resources.NotFoundException())
-                .when(mResources).getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout));
-        doAnswer(invocation -> {
-            int defaultValue = invocation.getArgument(2);
-            return defaultValue;
-        }).when(mDependencies).getDeviceConfigPropertyInt(any(),
-                eq(NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT),
-                anyInt());
-        when(mResources.getInteger(eq(R.integer.default_captive_portal_dns_probe_timeout)))
-                .thenReturn(42);
-        assertEquals(42, wnm.getIntSetting(mContext,
-                R.integer.config_captive_portal_dns_probe_timeout,
-                NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
-                R.integer.default_captive_portal_dns_probe_timeout));
-
-        // Set device config. Expect to get device config.
-        when(mDependencies.getDeviceConfigPropertyInt(any(),
-                eq(NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT), anyInt()))
-                        .thenReturn(1234);
-        assertEquals(1234, wnm.getIntSetting(mContext,
-                R.integer.config_captive_portal_dns_probe_timeout,
-                NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
-                R.integer.default_captive_portal_dns_probe_timeout));
-
-        // Set config resource. Expect to get config resource.
-        when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
-                .thenReturn(5678);
-        assertEquals(5678, wnm.getIntSetting(mContext,
-                R.integer.config_captive_portal_dns_probe_timeout,
-                NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
-                R.integer.default_captive_portal_dns_probe_timeout));
-    }
-
-    @Test
-    public void testIsCaptivePortal_HttpProbeIsPortal() throws IOException {
-        setSslException(mHttpsConnection);
-        setPortal302(mHttpConnection);
-
-        runPortalNetworkTest();
-    }
-
-    @Test
-    public void testIsCaptivePortal_HttpsProbeIsNotPortal() throws IOException {
-        setStatus(mHttpsConnection, 204);
-        setStatus(mHttpConnection, 500);
-
-        runNotPortalNetworkTest();
-    }
-
-    @Test
-    public void testIsCaptivePortal_FallbackProbeIsPortal() throws IOException {
-        setSslException(mHttpsConnection);
-        setStatus(mHttpConnection, 500);
-        setPortal302(mFallbackConnection);
-
-        runPortalNetworkTest();
-    }
-
-    @Test
-    public void testIsCaptivePortal_FallbackProbeIsNotPortal() throws IOException {
-        setSslException(mHttpsConnection);
-        setStatus(mHttpConnection, 500);
-        setStatus(mFallbackConnection, 500);
-
-        // Fallback probe did not see portal, HTTPS failed -> inconclusive
-        runFailedNetworkTest();
-    }
-
-    @Test
-    public void testIsCaptivePortal_OtherFallbackProbeIsPortal() throws IOException {
-        // Set all fallback probes but one to invalid URLs to verify they are being skipped
-        setFallbackUrl(TEST_FALLBACK_URL);
-        setOtherFallbackUrls(TEST_FALLBACK_URL + "," + TEST_OTHER_FALLBACK_URL);
-
-        setSslException(mHttpsConnection);
-        setStatus(mHttpConnection, 500);
-        setStatus(mFallbackConnection, 500);
-        setPortal302(mOtherFallbackConnection);
-
-        // TEST_OTHER_FALLBACK_URL is third
-        when(mRandom.nextInt()).thenReturn(2);
-
-        // First check always uses the first fallback URL: inconclusive
-        final NetworkMonitor monitor = runNetworkTest(NETWORK_TEST_RESULT_INVALID);
-        assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
-        verify(mFallbackConnection, times(1)).getResponseCode();
-        verify(mOtherFallbackConnection, never()).getResponseCode();
-
-        // Second check uses the URL chosen by Random
-        final CaptivePortalProbeResult result = monitor.isCaptivePortal();
-        assertTrue(result.isPortal());
-        verify(mOtherFallbackConnection, times(1)).getResponseCode();
-    }
-
-    @Test
-    public void testIsCaptivePortal_AllProbesFailed() throws IOException {
-        setSslException(mHttpsConnection);
-        setStatus(mHttpConnection, 500);
-        setStatus(mFallbackConnection, 404);
-
-        runFailedNetworkTest();
-        verify(mFallbackConnection, times(1)).getResponseCode();
-        verify(mOtherFallbackConnection, never()).getResponseCode();
-    }
-
-    @Test
-    public void testIsCaptivePortal_InvalidUrlSkipped() throws IOException {
-        setFallbackUrl("invalid");
-        setOtherFallbackUrls("otherinvalid," + TEST_OTHER_FALLBACK_URL + ",yetanotherinvalid");
-
-        setSslException(mHttpsConnection);
-        setStatus(mHttpConnection, 500);
-        setPortal302(mOtherFallbackConnection);
-
-        runPortalNetworkTest();
-        verify(mOtherFallbackConnection, times(1)).getResponseCode();
-        verify(mFallbackConnection, never()).getResponseCode();
-    }
-
-    private void setupFallbackSpec() throws IOException {
-        setFallbackSpecs("http://example.com@@/@@204@@/@@"
-                + "@@,@@"
-                + TEST_OTHER_FALLBACK_URL + "@@/@@30[12]@@/@@https://(www\\.)?google.com/?.*");
-
-        setSslException(mHttpsConnection);
-        setStatus(mHttpConnection, 500);
-
-        // Use the 2nd fallback spec
-        when(mRandom.nextInt()).thenReturn(1);
-    }
-
-    @Test
-    public void testIsCaptivePortal_FallbackSpecIsPartial() throws IOException {
-        setupFallbackSpec();
-        set302(mOtherFallbackConnection, "https://www.google.com/test?q=3");
-
-        // HTTPS failed, fallback spec went through -> partial connectivity
-        runPartialConnectivityNetworkTest();
-        verify(mOtherFallbackConnection, times(1)).getResponseCode();
-        verify(mFallbackConnection, never()).getResponseCode();
-    }
-
-    @Test
-    public void testIsCaptivePortal_FallbackSpecIsPortal() throws IOException {
-        setupFallbackSpec();
-        set302(mOtherFallbackConnection, "http://login.portal.example.com");
-
-        runPortalNetworkTest();
-    }
-
-    @Test
-    public void testIsCaptivePortal_IgnorePortals() throws IOException {
-        setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
-        setSslException(mHttpsConnection);
-        setPortal302(mHttpConnection);
-
-        runNotPortalNetworkTest();
-    }
-
-    @Test
-    public void testIsDataStall_EvaluationDisabled() {
-        setDataStallEvaluationType(0);
-        WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
-        assertFalse(wrappedMonitor.isDataStall());
-    }
-
-    @Test
-    public void testIsDataStall_EvaluationDnsOnNotMeteredNetwork() {
-        WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
-        makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
-        assertTrue(wrappedMonitor.isDataStall());
-    }
-
-    @Test
-    public void testIsDataStall_EvaluationDnsOnMeteredNetwork() {
-        WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
-        assertFalse(wrappedMonitor.isDataStall());
-
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
-        makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
-        assertTrue(wrappedMonitor.isDataStall());
-    }
-
-    @Test
-    public void testIsDataStall_EvaluationDnsWithDnsTimeoutCount() {
-        WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
-        makeDnsTimeoutEvent(wrappedMonitor, 3);
-        assertFalse(wrappedMonitor.isDataStall());
-        // Reset consecutive timeout counts.
-        makeDnsSuccessEvent(wrappedMonitor, 1);
-        makeDnsTimeoutEvent(wrappedMonitor, 2);
-        assertFalse(wrappedMonitor.isDataStall());
-
-        makeDnsTimeoutEvent(wrappedMonitor, 3);
-        assertTrue(wrappedMonitor.isDataStall());
-
-        // Set the value to larger than the default dns log size.
-        setConsecutiveDnsTimeoutThreshold(51);
-        wrappedMonitor = makeMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
-        makeDnsTimeoutEvent(wrappedMonitor, 50);
-        assertFalse(wrappedMonitor.isDataStall());
-
-        makeDnsTimeoutEvent(wrappedMonitor, 1);
-        assertTrue(wrappedMonitor.isDataStall());
-    }
-
-    @Test
-    public void testIsDataStall_EvaluationDnsWithDnsTimeThreshold() {
-        // Test dns events happened in valid dns time threshold.
-        WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
-        makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
-        assertFalse(wrappedMonitor.isDataStall());
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
-        assertTrue(wrappedMonitor.isDataStall());
-
-        // Test dns events happened before valid dns time threshold.
-        setValidDataStallDnsTimeThreshold(0);
-        wrappedMonitor = makeMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
-        makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
-        assertFalse(wrappedMonitor.isDataStall());
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
-        assertFalse(wrappedMonitor.isDataStall());
-    }
-
-    @Test
-    public void testBrokenNetworkNotValidated() throws Exception {
-        setSslException(mHttpsConnection);
-        setStatus(mHttpConnection, 500);
-        setStatus(mFallbackConnection, 404);
-
-        runFailedNetworkTest();
-    }
-
-    @Test
-    public void testNoInternetCapabilityValidated() throws Exception {
-        runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_TEST_RESULT_VALID);
-        verify(mCleartextDnsNetwork, never()).openConnection(any());
-    }
-
-    @Test
-    public void testLaunchCaptivePortalApp() throws Exception {
-        setSslException(mHttpsConnection);
-        setPortal302(mHttpConnection);
-
-        final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
-        nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, METERED_CAPABILITIES);
-
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .showProvisioningNotification(any(), any());
-
-        assertEquals(1, mRegisteredReceivers.size());
-
-        // Check that startCaptivePortalApp sends the expected intent.
-        nm.launchCaptivePortalApp();
-
-        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-        final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
-        verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .startCaptivePortalApp(networkCaptor.capture(), bundleCaptor.capture());
-        final Bundle bundle = bundleCaptor.getValue();
-        final Network bundleNetwork = bundle.getParcelable(ConnectivityManager.EXTRA_NETWORK);
-        assertEquals(TEST_NETID, bundleNetwork.netId);
-        // network is passed both in bundle and as parameter, as the bundle is opaque to the
-        // framework and only intended for the captive portal app, but the framework needs
-        // the network to identify the right NetworkMonitor.
-        assertEquals(TEST_NETID, networkCaptor.getValue().netId);
-
-        // Have the app report that the captive portal is dismissed, and check that we revalidate.
-        setStatus(mHttpsConnection, 204);
-        setStatus(mHttpConnection, 204);
-
-        nm.notifyCaptivePortalAppFinished(APP_RETURN_DISMISSED);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
-
-        assertEquals(0, mRegisteredReceivers.size());
-    }
-
-    @Test
-    public void testPrivateDnsSuccess() throws Exception {
-        setStatus(mHttpsConnection, 204);
-        setStatus(mHttpConnection, 204);
-        mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::53"});
-
-        WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
-        wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
-        wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
-    }
-
-    @Test
-    public void testPrivateDnsResolutionRetryUpdate() throws Exception {
-        // Set a private DNS hostname that doesn't resolve and expect validation to fail.
-        mFakeDns.setAnswer("dns.google", new String[0]);
-        setStatus(mHttpsConnection, 204);
-        setStatus(mHttpConnection, 204);
-
-        WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
-        wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
-        wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
-
-        // Fix DNS and retry, expect validation to succeed.
-        reset(mCallbacks);
-        mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"});
-
-        wnm.forceReevaluation(Process.myUid());
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
-
-        // Change configuration to an invalid DNS name, expect validation to fail.
-        reset(mCallbacks);
-        mFakeDns.setAnswer("dns.bad", new String[0]);
-        wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
-
-        // Change configuration back to working again, but make private DNS not work.
-        // Expect validation to fail.
-        reset(mCallbacks);
-        mFakeDns.setNonBypassPrivateDnsWorking(false);
-        wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
-
-        // Make private DNS work again. Expect validation to succeed.
-        reset(mCallbacks);
-        mFakeDns.setNonBypassPrivateDnsWorking(true);
-        wnm.forceReevaluation(Process.myUid());
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
-    }
-
-    @Test
-    public void testDataStall_StallSuspectedAndSendMetrics() throws IOException {
-        WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
-        makeDnsTimeoutEvent(wrappedMonitor, 5);
-        assertTrue(wrappedMonitor.isDataStall());
-        verify(mDataStallStatsUtils, times(1)).write(makeEmptyDataStallDetectionStats(), any());
-    }
-
-    @Test
-    public void testDataStall_NoStallSuspectedAndSendMetrics() throws IOException {
-        WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
-        wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
-        makeDnsTimeoutEvent(wrappedMonitor, 3);
-        assertFalse(wrappedMonitor.isDataStall());
-        verify(mDataStallStatsUtils, never()).write(makeEmptyDataStallDetectionStats(), any());
-    }
-
-    @Test
-    public void testCollectDataStallMetrics() {
-        WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
-
-        when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
-        when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC);
-        when(mTelephony.getSimOperator()).thenReturn(TEST_MCCMNC);
-
-        DataStallDetectionStats.Builder stats =
-                new DataStallDetectionStats.Builder()
-                .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
-                .setNetworkType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */,
-                        true /* roaming */,
-                        TEST_MCCMNC /* networkMccmnc */,
-                        TEST_MCCMNC /* simMccmnc */,
-                        CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN /* signalStrength */);
-        generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
-
-        assertEquals(wrappedMonitor.buildDataStallDetectionStats(
-                 NetworkCapabilities.TRANSPORT_CELLULAR), stats.build());
-
-        when(mWifi.getConnectionInfo()).thenReturn(mWifiInfo);
-
-        stats = new DataStallDetectionStats.Builder()
-                .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
-                .setNetworkType(NetworkCapabilities.TRANSPORT_WIFI)
-                .setWiFiData(mWifiInfo);
-        generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
-
-        assertEquals(
-                wrappedMonitor.buildDataStallDetectionStats(NetworkCapabilities.TRANSPORT_WIFI),
-                stats.build());
-    }
-
-    @Test
-    public void testIgnoreHttpsProbe() throws Exception {
-        setSslException(mHttpsConnection);
-        setStatus(mHttpConnection, 204);
-
-        final NetworkMonitor nm = runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY);
-
-        nm.setAcceptPartialConnectivity();
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), any());
-    }
-
-    @Test
-    public void testIsPartialConnectivity() throws IOException {
-        setStatus(mHttpsConnection, 500);
-        setStatus(mHttpConnection, 204);
-        setStatus(mFallbackConnection, 500);
-        runPartialConnectivityNetworkTest();
-
-        setStatus(mHttpsConnection, 500);
-        setStatus(mHttpConnection, 500);
-        setStatus(mFallbackConnection, 204);
-        runPartialConnectivityNetworkTest();
-    }
-
-    private void assertIpAddressArrayEquals(String[] expected, InetAddress[] actual) {
-        String[] actualStrings = new String[actual.length];
-        for (int i = 0; i < actual.length; i++) {
-            actualStrings[i] = actual[i].getHostAddress();
-        }
-        assertArrayEquals("Array of IP addresses differs", expected, actualStrings);
-    }
-
-    @Test
-    public void testSendDnsProbeWithTimeout() throws Exception {
-        WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
-        final int shortTimeoutMs = 200;
-
-        // Clear the wildcard DNS response created in setUp.
-        mFakeDns.setAnswer("*", null);
-
-        String[] expected = new String[]{"2001:db8::"};
-        mFakeDns.setAnswer("www.google.com", expected);
-        InetAddress[] actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
-        assertIpAddressArrayEquals(expected, actual);
-
-        expected = new String[]{"2001:db8::", "192.0.2.1"};
-        mFakeDns.setAnswer("www.googleapis.com", expected);
-        actual = wnm.sendDnsProbeWithTimeout("www.googleapis.com", shortTimeoutMs);
-        assertIpAddressArrayEquals(expected, actual);
-
-        mFakeDns.setAnswer("www.google.com", new String[0]);
-        try {
-            wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
-            fail("No DNS results, expected UnknownHostException");
-        } catch (UnknownHostException e) {
-        }
-
-        mFakeDns.setAnswer("www.google.com", null);
-        try {
-            wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
-            fail("DNS query timed out, expected UnknownHostException");
-        } catch (UnknownHostException e) {
-        }
-    }
-
-    private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
-        for (int i = 0; i < count; i++) {
-            wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
-                    RETURN_CODE_DNS_TIMEOUT);
-        }
-    }
-
-    private void makeDnsSuccessEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
-        for (int i = 0; i < count; i++) {
-            wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
-                    RETURN_CODE_DNS_SUCCESS);
-        }
-    }
-
-    private DataStallDetectionStats makeEmptyDataStallDetectionStats() {
-        return new DataStallDetectionStats.Builder().build();
-    }
-
-    private void setDataStallEvaluationType(int type) {
-        when(mDependencies.getDeviceConfigPropertyInt(any(),
-            eq(CONFIG_DATA_STALL_EVALUATION_TYPE), anyInt())).thenReturn(type);
-    }
-
-    private void setMinDataStallEvaluateInterval(int time) {
-        when(mDependencies.getDeviceConfigPropertyInt(any(),
-            eq(CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL), anyInt())).thenReturn(time);
-    }
-
-    private void setValidDataStallDnsTimeThreshold(int time) {
-        when(mDependencies.getDeviceConfigPropertyInt(any(),
-            eq(CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD), anyInt())).thenReturn(time);
-    }
-
-    private void setConsecutiveDnsTimeoutThreshold(int num) {
-        when(mDependencies.getDeviceConfigPropertyInt(any(),
-            eq(CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD), anyInt())).thenReturn(num);
-    }
-
-    private void setFallbackUrl(String url) {
-        when(mDependencies.getSetting(any(),
-                eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL), any())).thenReturn(url);
-    }
-
-    private void setOtherFallbackUrls(String urls) {
-        when(mDependencies.getDeviceConfigProperty(any(),
-                eq(CAPTIVE_PORTAL_OTHER_FALLBACK_URLS), any())).thenReturn(urls);
-    }
-
-    private void setFallbackSpecs(String specs) {
-        when(mDependencies.getDeviceConfigProperty(any(),
-                eq(CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
-    }
-
-    private void setCaptivePortalMode(int mode) {
-        when(mDependencies.getSetting(any(),
-                eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
-    }
-
-    private void runPortalNetworkTest() {
-        runNetworkTest(NETWORK_TEST_RESULT_INVALID);
-        assertEquals(1, mRegisteredReceivers.size());
-        assertNotNull(mNetworkTestedRedirectUrlCaptor.getValue());
-    }
-
-    private void runNotPortalNetworkTest() {
-        runNetworkTest(NETWORK_TEST_RESULT_VALID);
-        assertEquals(0, mRegisteredReceivers.size());
-        assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
-    }
-
-    private void runFailedNetworkTest() {
-        runNetworkTest(NETWORK_TEST_RESULT_INVALID);
-        assertEquals(0, mRegisteredReceivers.size());
-        assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
-    }
-
-    private void runPartialConnectivityNetworkTest() {
-        runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY);
-        assertEquals(0, mRegisteredReceivers.size());
-        assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
-    }
-
-    private NetworkMonitor runNetworkTest(int testResult) {
-        return runNetworkTest(METERED_CAPABILITIES, testResult);
-    }
-
-    private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) {
-        final NetworkMonitor monitor = makeMonitor(nc);
-        monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
-        try {
-            verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                    .notifyNetworkTested(eq(testResult), mNetworkTestedRedirectUrlCaptor.capture());
-        } catch (RemoteException e) {
-            fail("Unexpected exception: " + e);
-        }
-        waitForIdle(monitor.getHandler());
-
-        return monitor;
-    }
-
-    private void setSslException(HttpURLConnection connection) throws IOException {
-        doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode();
-    }
-
-    private void set302(HttpURLConnection connection, String location) throws IOException {
-        setStatus(connection, 302);
-        doReturn(location).when(connection).getHeaderField(LOCATION_HEADER);
-    }
-
-    private void setPortal302(HttpURLConnection connection) throws IOException {
-        set302(connection, "http://login.example.com");
-    }
-
-    private void setStatus(HttpURLConnection connection, int status) throws IOException {
-        doReturn(status).when(connection).getResponseCode();
-    }
-
-    private void generateTimeoutDnsEvent(DataStallDetectionStats.Builder stats, int num) {
-        for (int i = 0; i < num; i++) {
-            stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, 123456789 /* timeMs */);
-        }
-    }
-}
-
diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
deleted file mode 100644
index 87346e5..0000000
--- a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ /dev/null
@@ -1,663 +0,0 @@
-/*
- * 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.connectivity.ipmemorystore;
-
-import static com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService.InterruptMaintenance;
-
-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.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doReturn;
-
-import android.app.job.JobScheduler;
-import android.content.Context;
-import android.net.ipmemorystore.Blob;
-import android.net.ipmemorystore.IOnBlobRetrievedListener;
-import android.net.ipmemorystore.IOnL2KeyResponseListener;
-import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
-import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
-import android.net.ipmemorystore.IOnStatusListener;
-import android.net.ipmemorystore.NetworkAttributes;
-import android.net.ipmemorystore.NetworkAttributesParcelable;
-import android.net.ipmemorystore.SameL3NetworkResponse;
-import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
-import android.net.ipmemorystore.Status;
-import android.net.ipmemorystore.StatusParcelable;
-import android.os.ConditionVariable;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-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.lang.reflect.Modifier;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/** Unit tests for {@link IpMemoryStoreService}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class IpMemoryStoreServiceTest {
-    private static final String TEST_CLIENT_ID = "testClientId";
-    private static final String TEST_DATA_NAME = "testData";
-
-    private static final int TEST_DATABASE_SIZE_THRESHOLD = 100 * 1024; //100KB
-    private static final int DEFAULT_TIMEOUT_MS = 5000;
-    private static final int LONG_TIMEOUT_MS = 30000;
-    private static final int FAKE_KEY_COUNT = 20;
-    private static final String[] FAKE_KEYS;
-    static {
-        FAKE_KEYS = new String[FAKE_KEY_COUNT];
-        for (int i = 0; i < FAKE_KEYS.length; ++i) {
-            FAKE_KEYS[i] = "fakeKey" + i;
-        }
-    }
-
-    @Mock
-    private Context mMockContext;
-    @Mock
-    private JobScheduler mMockJobScheduler;
-    private File mDbFile;
-
-    private IpMemoryStoreService mService;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        final Context context = InstrumentationRegistry.getContext();
-        final File dir = context.getFilesDir();
-        mDbFile = new File(dir, "test.db");
-        doReturn(mDbFile).when(mMockContext).getDatabasePath(anyString());
-        doReturn(mMockJobScheduler).when(mMockContext)
-                .getSystemService(Context.JOB_SCHEDULER_SERVICE);
-        mService = new IpMemoryStoreService(mMockContext) {
-            @Override
-            protected int getDbSizeThreshold() {
-                return TEST_DATABASE_SIZE_THRESHOLD;
-            }
-
-            @Override
-            boolean isDbSizeOverThreshold() {
-                // Add a 100ms delay here for pausing maintenance job a while. Interrupted flag can
-                // be set at this time.
-                waitForMs(100);
-                return super.isDbSizeOverThreshold();
-            }
-        };
-    }
-
-    @After
-    public void tearDown() {
-        mService.shutdown();
-        mDbFile.delete();
-    }
-
-    /** Helper method to make a vanilla IOnStatusListener */
-    private IOnStatusListener onStatus(Consumer<Status> functor) {
-        return new IOnStatusListener() {
-            @Override
-            public void onComplete(final StatusParcelable statusParcelable) throws RemoteException {
-                functor.accept(new Status(statusParcelable));
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return null;
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-        };
-    }
-
-    /** Helper method to make an IOnBlobRetrievedListener */
-    private interface OnBlobRetrievedListener {
-        void onBlobRetrieved(Status status, String l2Key, String name, byte[] data);
-    }
-    private IOnBlobRetrievedListener onBlobRetrieved(final OnBlobRetrievedListener functor) {
-        return new IOnBlobRetrievedListener() {
-            @Override
-            public void onBlobRetrieved(final StatusParcelable statusParcelable,
-                    final String l2Key, final String name, final Blob blob) throws RemoteException {
-                functor.onBlobRetrieved(new Status(statusParcelable), l2Key, name,
-                        null == blob ? null : blob.data);
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return null;
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-        };
-    }
-
-    /** Helper method to make an IOnNetworkAttributesRetrievedListener */
-    private interface OnNetworkAttributesRetrievedListener  {
-        void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attr);
-    }
-    private IOnNetworkAttributesRetrievedListener onNetworkAttributesRetrieved(
-            final OnNetworkAttributesRetrievedListener functor) {
-        return new IOnNetworkAttributesRetrievedListener() {
-            @Override
-            public void onNetworkAttributesRetrieved(final StatusParcelable status,
-                    final String l2Key, final NetworkAttributesParcelable attributes)
-                    throws RemoteException {
-                functor.onNetworkAttributesRetrieved(new Status(status), l2Key,
-                        null == attributes ? null : new NetworkAttributes(attributes));
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return null;
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-        };
-    }
-
-    /** Helper method to make an IOnSameNetworkResponseListener */
-    private interface OnSameL3NetworkResponseListener {
-        void onSameL3NetworkResponse(Status status, SameL3NetworkResponse answer);
-    }
-    private IOnSameL3NetworkResponseListener onSameResponse(
-            final OnSameL3NetworkResponseListener functor) {
-        return new IOnSameL3NetworkResponseListener() {
-            @Override
-            public void onSameL3NetworkResponse(final StatusParcelable status,
-                    final SameL3NetworkResponseParcelable sameL3Network)
-                    throws RemoteException {
-                functor.onSameL3NetworkResponse(new Status(status),
-                        null == sameL3Network ? null : new SameL3NetworkResponse(sameL3Network));
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return null;
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-        };
-    }
-
-    /** Helper method to make an IOnL2KeyResponseListener */
-    private interface OnL2KeyResponseListener {
-        void onL2KeyResponse(Status status, String key);
-    }
-    private IOnL2KeyResponseListener onL2KeyResponse(final OnL2KeyResponseListener functor) {
-        return new IOnL2KeyResponseListener() {
-            @Override
-            public void onL2KeyResponse(final StatusParcelable status, final String key)
-                    throws RemoteException {
-                functor.onL2KeyResponse(new Status(status), key);
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return null;
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-        };
-    }
-
-    // Helper method to factorize some boilerplate
-    private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) {
-        doLatched(timeoutMessage, functor, DEFAULT_TIMEOUT_MS);
-    }
-
-    private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor,
-            final int timeout) {
-        final CountDownLatch latch = new CountDownLatch(1);
-        functor.accept(latch);
-        try {
-            if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
-                fail(timeoutMessage);
-            }
-        } catch (InterruptedException e) {
-            fail("Thread was interrupted");
-        }
-    }
-
-    // Helper methods to factorize more boilerplate
-    private void storeAttributes(final String l2Key, final NetworkAttributes na) {
-        storeAttributes("Did not complete storing attributes", l2Key, na);
-    }
-    private void storeAttributes(final String timeoutMessage, final String l2Key,
-            final NetworkAttributes na) {
-        doLatched(timeoutMessage, latch -> mService.storeNetworkAttributes(l2Key, na.toParcelable(),
-                onStatus(status -> {
-                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
-                    latch.countDown();
-                })));
-    }
-
-    /** Insert large data that db size will be over threshold for maintenance test usage. */
-    private void insertFakeDataAndOverThreshold() {
-        try {
-            final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-            na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-            na.setGroupHint("hint1");
-            na.setMtu(219);
-            na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
-            final byte[] data = new byte[]{-3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34};
-            final long time = System.currentTimeMillis() - 1;
-            for (int i = 0; i < 1000; i++) {
-                int errorCode = IpMemoryStoreDatabase.storeNetworkAttributes(
-                        mService.mDb,
-                        "fakeKey" + i,
-                        // Let first 100 records get expiry.
-                        i < 100 ? time : time + TimeUnit.HOURS.toMillis(i),
-                        na.build());
-                assertEquals(errorCode, Status.SUCCESS);
-
-                errorCode = IpMemoryStoreDatabase.storeBlob(
-                        mService.mDb, "fakeKey" + i, TEST_CLIENT_ID, TEST_DATA_NAME, data);
-                assertEquals(errorCode, Status.SUCCESS);
-            }
-
-            // After added 5000 records, db size is larger than fake threshold(100KB).
-            assertTrue(mService.isDbSizeOverThreshold());
-        } catch (final UnknownHostException e) {
-            fail("Insert fake data fail");
-        }
-    }
-
-    /** Wait for assigned time. */
-    private void waitForMs(long ms) {
-        try {
-            Thread.sleep(ms);
-        } catch (final InterruptedException e) {
-            fail("Thread was interrupted");
-        }
-    }
-
-    @Test
-    public void testNetworkAttributes() throws UnknownHostException {
-        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-        na.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000);
-        na.setGroupHint("hint1");
-        na.setMtu(219);
-        final String l2Key = FAKE_KEYS[0];
-        NetworkAttributes attributes = na.build();
-        storeAttributes(l2Key, attributes);
-
-        doLatched("Did not complete retrieving attributes", latch ->
-                mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
-                        (status, key, attr) -> {
-                            assertTrue("Retrieve network attributes not successful : "
-                                    + status.resultCode, status.isSuccess());
-                            assertEquals(l2Key, key);
-                            assertEquals(attributes, attr);
-                            latch.countDown();
-                        })));
-
-        final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder();
-        na.setDnsAddresses(Arrays.asList(
-                new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
-        final NetworkAttributes attributes2 = na2.build();
-        storeAttributes("Did not complete storing attributes 2", l2Key, attributes2);
-
-        doLatched("Did not complete retrieving attributes 2", latch ->
-                mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
-                        (status, key, attr) -> {
-                            assertTrue("Retrieve network attributes not successful : "
-                                    + status.resultCode, status.isSuccess());
-                            assertEquals(l2Key, key);
-                            assertEquals(attributes.assignedV4Address, attr.assignedV4Address);
-                            assertEquals(attributes.assignedV4AddressExpiry,
-                                    attr.assignedV4AddressExpiry);
-                            assertEquals(attributes.groupHint, attr.groupHint);
-                            assertEquals(attributes.mtu, attr.mtu);
-                            assertEquals(attributes2.dnsAddresses, attr.dnsAddresses);
-                            latch.countDown();
-                        })));
-
-        doLatched("Did not complete retrieving attributes 3", latch ->
-                mService.retrieveNetworkAttributes(l2Key + "nonexistent",
-                        onNetworkAttributesRetrieved(
-                                (status, key, attr) -> {
-                                    assertTrue("Retrieve network attributes not successful : "
-                                            + status.resultCode, status.isSuccess());
-                                    assertEquals(l2Key + "nonexistent", key);
-                                    assertNull("Retrieved data not stored", attr);
-                                    latch.countDown();
-                                }
-                        )));
-
-        // Verify that this test does not miss any new field added later.
-        // If any field is added to NetworkAttributes it must be tested here for storing
-        // and retrieving.
-        assertEquals(5, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
-                .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
-    }
-
-    @Test
-    public void testInvalidAttributes() {
-        doLatched("Did not complete storing bad attributes", latch ->
-                mService.storeNetworkAttributes("key", null, onStatus(status -> {
-                    assertFalse("Success storing on a null key",
-                            status.isSuccess());
-                    assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
-                    latch.countDown();
-                })));
-
-        final NetworkAttributes na = new NetworkAttributes.Builder().setMtu(2).build();
-        doLatched("Did not complete storing bad attributes", latch ->
-                mService.storeNetworkAttributes(null, na.toParcelable(), onStatus(status -> {
-                    assertFalse("Success storing null attributes on a null key",
-                            status.isSuccess());
-                    assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
-                    latch.countDown();
-                })));
-
-        doLatched("Did not complete storing bad attributes", latch ->
-                mService.storeNetworkAttributes(null, null, onStatus(status -> {
-                    assertFalse("Success storing null attributes on a null key",
-                            status.isSuccess());
-                    assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
-                    latch.countDown();
-                })));
-
-        doLatched("Did not complete retrieving bad attributes", latch ->
-                mService.retrieveNetworkAttributes(null, onNetworkAttributesRetrieved(
-                        (status, key, attr) -> {
-                            assertFalse("Success retrieving attributes for a null key",
-                                    status.isSuccess());
-                            assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
-                            assertNull(key);
-                            assertNull(attr);
-                            latch.countDown();
-                        })));
-    }
-
-    @Test
-    public void testPrivateData() {
-        final Blob b = new Blob();
-        b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 };
-        final String l2Key = FAKE_KEYS[0];
-        doLatched("Did not complete storing private data", latch ->
-                mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
-                        onStatus(status -> {
-                            assertTrue("Store status not successful : " + status.resultCode,
-                                    status.isSuccess());
-                            latch.countDown();
-                        })));
-
-        doLatched("Did not complete retrieving private data", latch ->
-                mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved(
-                        (status, key, name, data) -> {
-                            assertTrue("Retrieve blob status not successful : " + status.resultCode,
-                                    status.isSuccess());
-                            assertEquals(l2Key, key);
-                            assertEquals(name, TEST_DATA_NAME);
-                            assertTrue(Arrays.equals(b.data, data));
-                            latch.countDown();
-                        })));
-
-        // Most puzzling error message ever
-        doLatched("Did not complete retrieving nothing", latch ->
-                mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME + "2", onBlobRetrieved(
-                        (status, key, name, data) -> {
-                            assertTrue("Retrieve blob status not successful : " + status.resultCode,
-                                    status.isSuccess());
-                            assertEquals(l2Key, key);
-                            assertEquals(name, TEST_DATA_NAME + "2");
-                            assertNull(data);
-                            latch.countDown();
-                        })));
-    }
-
-    @Test
-    public void testFindL2Key() throws UnknownHostException {
-        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        na.setGroupHint("hint0");
-        storeAttributes(FAKE_KEYS[0], na.build());
-
-        na.setDnsAddresses(Arrays.asList(
-                new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")}));
-        na.setMtu(219);
-        storeAttributes(FAKE_KEYS[1], na.build());
-        na.setMtu(null);
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-        na.setDnsAddresses(Arrays.asList(
-                new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
-        na.setGroupHint("hint1");
-        storeAttributes(FAKE_KEYS[2], na.build());
-        na.setMtu(219);
-        storeAttributes(FAKE_KEYS[3], na.build());
-        na.setMtu(240);
-        storeAttributes(FAKE_KEYS[4], na.build());
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8"));
-        storeAttributes(FAKE_KEYS[5], na.build());
-
-        // Matches key 5 exactly
-        doLatched("Did not finish finding L2Key", latch ->
-                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
-                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
-                            status.isSuccess());
-                    assertEquals(FAKE_KEYS[5], key);
-                    latch.countDown();
-                })));
-
-        // MTU matches key 4 but v4 address matches key 5. The latter is stronger.
-        na.setMtu(240);
-        doLatched("Did not finish finding L2Key", latch ->
-                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
-                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
-                            status.isSuccess());
-                    assertEquals(FAKE_KEYS[5], key);
-                    latch.countDown();
-                })));
-
-        // Closest to key 3 (indeed, identical)
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-        na.setMtu(219);
-        doLatched("Did not finish finding L2Key", latch ->
-                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
-                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
-                            status.isSuccess());
-                    assertEquals(FAKE_KEYS[3], key);
-                    latch.countDown();
-                })));
-
-        // Group hint alone must not be strong enough to override the rest
-        na.setGroupHint("hint0");
-        doLatched("Did not finish finding L2Key", latch ->
-                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
-                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
-                            status.isSuccess());
-                    assertEquals(FAKE_KEYS[3], key);
-                    latch.countDown();
-                })));
-
-        // Still closest to key 3, though confidence is lower
-        na.setGroupHint("hint1");
-        na.setDnsAddresses(null);
-        doLatched("Did not finish finding L2Key", latch ->
-                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
-                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
-                            status.isSuccess());
-                    assertEquals(FAKE_KEYS[3], key);
-                    latch.countDown();
-                })));
-
-        // But changing the MTU makes this closer to key 4
-        na.setMtu(240);
-        doLatched("Did not finish finding L2Key", latch ->
-                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
-                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
-                            status.isSuccess());
-                    assertEquals(FAKE_KEYS[4], key);
-                    latch.countDown();
-                })));
-
-        // MTU alone not strong enough to make this group-close
-        na.setGroupHint(null);
-        na.setDnsAddresses(null);
-        na.setAssignedV4Address(null);
-        doLatched("Did not finish finding L2Key", latch ->
-                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
-                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
-                            status.isSuccess());
-                    assertNull(key);
-                    latch.countDown();
-                })));
-    }
-
-    private void assertNetworksSameness(final String key1, final String key2, final int sameness) {
-        doLatched("Did not finish evaluating sameness", latch ->
-                mService.isSameNetwork(key1, key2, onSameResponse((status, answer) -> {
-                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
-                            status.isSuccess());
-                    assertEquals(sameness, answer.getNetworkSameness());
-                    latch.countDown();
-                })));
-    }
-
-    @Test
-    public void testIsSameNetwork() throws UnknownHostException {
-        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-        na.setGroupHint("hint1");
-        na.setMtu(219);
-        na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
-
-        storeAttributes(FAKE_KEYS[0], na.build());
-        // 0 and 1 have identical attributes
-        storeAttributes(FAKE_KEYS[1], na.build());
-
-        // Hopefully only the MTU being different still means it's the same network
-        na.setMtu(200);
-        storeAttributes(FAKE_KEYS[2], na.build());
-
-        // Hopefully different MTU, assigned V4 address and grouphint make a different network,
-        // even with identical DNS addresses
-        na.setAssignedV4Address(null);
-        na.setGroupHint("hint2");
-        storeAttributes(FAKE_KEYS[3], na.build());
-
-        assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[1], SameL3NetworkResponse.NETWORK_SAME);
-        assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME);
-        assertNetworksSameness(FAKE_KEYS[1], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME);
-        assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[3], SameL3NetworkResponse.NETWORK_DIFFERENT);
-        assertNetworksSameness(FAKE_KEYS[0], "neverInsertedKey",
-                SameL3NetworkResponse.NETWORK_NEVER_CONNECTED);
-
-        doLatched("Did not finish evaluating sameness", latch ->
-                mService.isSameNetwork(null, null, onSameResponse((status, answer) -> {
-                    assertFalse("Retrieve network sameness suspiciously successful : "
-                            + status.resultCode, status.isSuccess());
-                    assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
-                    assertNull(answer);
-                    latch.countDown();
-                })));
-    }
-
-
-    @Test
-    public void testFullMaintenance() {
-        insertFakeDataAndOverThreshold();
-
-        final InterruptMaintenance im = new InterruptMaintenance(0/* Fake JobId */);
-        // Do full maintenance and then db size should go down and meet the threshold.
-        doLatched("Maintenance unexpectedly completed successfully", latch ->
-                mService.fullMaintenance(onStatus((status) -> {
-                    assertTrue("Execute full maintenance failed: "
-                            + status.resultCode, status.isSuccess());
-                    latch.countDown();
-                }), im), LONG_TIMEOUT_MS);
-
-        // Assume that maintenance is successful, db size shall meet the threshold.
-        assertFalse(mService.isDbSizeOverThreshold());
-    }
-
-    @Test
-    public void testInterruptMaintenance() {
-        insertFakeDataAndOverThreshold();
-
-        final InterruptMaintenance im = new InterruptMaintenance(0/* Fake JobId */);
-
-        // Test interruption immediately.
-        im.setInterrupted(true);
-        // Do full maintenance and the expectation is not completed by interruption.
-        doLatched("Maintenance unexpectedly completed successfully", latch ->
-                mService.fullMaintenance(onStatus((status) -> {
-                    assertFalse(status.isSuccess());
-                    latch.countDown();
-                }), im), LONG_TIMEOUT_MS);
-
-        // Assume that no data are removed, db size shall be over the threshold.
-        assertTrue(mService.isDbSizeOverThreshold());
-
-        // Reset the flag and test interruption during maintenance.
-        im.setInterrupted(false);
-
-        final ConditionVariable latch = new ConditionVariable();
-        // Do full maintenance and the expectation is not completed by interruption.
-        mService.fullMaintenance(onStatus((status) -> {
-            assertFalse(status.isSuccess());
-            latch.open();
-        }), im);
-
-        // Give a little bit of time for maintenance to start up for realism
-        waitForMs(50);
-        // Interrupt maintenance job.
-        im.setInterrupted(true);
-
-        if (!latch.block(LONG_TIMEOUT_MS)) {
-            fail("Maintenance unexpectedly completed successfully");
-        }
-
-        // Assume that only do dropAllExpiredRecords method in previous maintenance, db size shall
-        // still be over the threshold.
-        assertTrue(mService.isDbSizeOverThreshold());
-    }
-}
diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/RelevanceUtilsTests.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/RelevanceUtilsTests.java
deleted file mode 100644
index 3d3aabc..0000000
--- a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/RelevanceUtilsTests.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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.connectivity.ipmemorystore;
-
-import static com.android.server.connectivity.ipmemorystore.RelevanceUtils.CAPPED_RELEVANCE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit tests for {@link RelevanceUtils}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RelevanceUtilsTests {
-    @Test
-    public void testComputeRelevanceForTargetDate() {
-        final long dayInMillis = 24L * 60 * 60 * 1000;
-        final long base = 1_000_000L; // any given point in time
-        // Relevance when the network expires in 1000 years must be capped
-        assertEquals(CAPPED_RELEVANCE, RelevanceUtils.computeRelevanceForTargetDate(
-                base + 1000L * dayInMillis, base));
-        // Relevance when expiry is before the date must be 0
-        assertEquals(0, RelevanceUtils.computeRelevanceForTargetDate(base - 1, base));
-        // Make sure the relevance for a given target date is higher if the expiry is further
-        // in the future
-        assertTrue(RelevanceUtils.computeRelevanceForTargetDate(base + 100 * dayInMillis, base)
-                < RelevanceUtils.computeRelevanceForTargetDate(base + 150 * dayInMillis, base));
-
-        // Make sure the relevance falls slower as the expiry is closing in. This is to ensure
-        // the decay is indeed logarithmic.
-        final int relevanceAtExpiry = RelevanceUtils.computeRelevanceForTargetDate(base, base);
-        final int relevance50DaysBeforeExpiry =
-                RelevanceUtils.computeRelevanceForTargetDate(base + 50 * dayInMillis, base);
-        final int relevance100DaysBeforeExpiry =
-                RelevanceUtils.computeRelevanceForTargetDate(base + 100 * dayInMillis, base);
-        final int relevance150DaysBeforeExpiry =
-                RelevanceUtils.computeRelevanceForTargetDate(base + 150 * dayInMillis, base);
-        assertEquals(0, relevanceAtExpiry);
-        assertTrue(relevance50DaysBeforeExpiry - relevanceAtExpiry
-                < relevance100DaysBeforeExpiry - relevance50DaysBeforeExpiry);
-        assertTrue(relevance100DaysBeforeExpiry - relevance50DaysBeforeExpiry
-                < relevance150DaysBeforeExpiry - relevance100DaysBeforeExpiry);
-    }
-
-    @Test
-    public void testIncreaseRelevance() {
-        long expiry = System.currentTimeMillis();
-
-        final long firstBump = RelevanceUtils.bumpExpiryDate(expiry);
-        // Though a few milliseconds might have elapsed, the first bump should push the duration
-        // to days in the future, so unless this test takes literal days between these two lines,
-        // this should always pass.
-        assertTrue(firstBump > expiry);
-
-        expiry = 0;
-        long lastDifference = Long.MAX_VALUE;
-        // The relevance should be capped in at most this many steps. Otherwise, fail.
-        final int steps = 1000;
-        for (int i = 0; i < steps; ++i) {
-            final long newExpiry = RelevanceUtils.bumpExpiryDuration(expiry);
-            if (newExpiry == expiry) {
-                // The relevance should be capped. Make sure it is, then exit without failure.
-                assertEquals(newExpiry, RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS);
-                return;
-            }
-            // Make sure the new expiry is further in the future than last time.
-            assertTrue(newExpiry > expiry);
-            // Also check that it was not bumped as much as the last bump, because the
-            // decay must be exponential.
-            assertTrue(newExpiry - expiry < lastDifference);
-            lastDifference = newExpiry - expiry;
-            expiry = newExpiry;
-        }
-        fail("Relevance failed to go to the maximum value after " + steps + " bumps");
-    }
-
-    @Test
-    public void testContinuity() {
-        final long expiry = System.currentTimeMillis();
-
-        // Relevance at expiry and after expiry should be the cap.
-        final int relevanceBeforeMaxLifetime = RelevanceUtils.computeRelevanceForTargetDate(expiry,
-                expiry - (RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS + 1_000_000));
-        assertEquals(relevanceBeforeMaxLifetime, CAPPED_RELEVANCE);
-        final int relevanceForMaxLifetime = RelevanceUtils.computeRelevanceForTargetDate(expiry,
-                expiry - RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS);
-        assertEquals(relevanceForMaxLifetime, CAPPED_RELEVANCE);
-
-        // If the max relevance is reached at the cap lifetime, one millisecond less than this
-        // should be very close. Strictly speaking this is a bit brittle, but it should be
-        // good enough for the purposes of the memory store.
-        final int relevanceForOneMillisecLessThanCap = RelevanceUtils.computeRelevanceForTargetDate(
-                expiry, expiry - RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS + 1);
-        assertTrue(relevanceForOneMillisecLessThanCap <= CAPPED_RELEVANCE);
-        assertTrue(relevanceForOneMillisecLessThanCap >= CAPPED_RELEVANCE - 10);
-
-        // Likewise the relevance one millisecond before expiry should be very close to 0. It's
-        // fine if it rounds down to 0.
-        final int relevanceOneMillisecBeforeExpiry = RelevanceUtils.computeRelevanceForTargetDate(
-                expiry, expiry - 1);
-        assertTrue(relevanceOneMillisecBeforeExpiry <= 10);
-        assertTrue(relevanceOneMillisecBeforeExpiry >= 0);
-
-        final int relevanceAtExpiry = RelevanceUtils.computeRelevanceForTargetDate(expiry, expiry);
-        assertEquals(relevanceAtExpiry, 0);
-        final int relevanceAfterExpiry = RelevanceUtils.computeRelevanceForTargetDate(expiry,
-                expiry + 1_000_000);
-        assertEquals(relevanceAfterExpiry, 0);
-    }
-
-    // testIncreaseRelevance makes sure bumping the expiry continuously always yields a
-    // monotonically increasing date as a side effect, but this tests that the relevance (as
-    // opposed to the expiry date) increases monotonically with increasing periods.
-    @Test
-    public void testMonotonicity() {
-        // Hopefully the relevance is granular enough to give a different value for every one
-        // of this number of steps.
-        final int steps = 40;
-        final long expiry = System.currentTimeMillis();
-
-        int lastRelevance = -1;
-        for (int i = 0; i < steps; ++i) {
-            final long date = expiry - i * (RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS / steps);
-            final int relevance = RelevanceUtils.computeRelevanceForTargetDate(expiry, date);
-            assertTrue(relevance > lastRelevance);
-            lastRelevance = relevance;
-        }
-    }
-}
diff --git a/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java b/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java
deleted file mode 100644
index b1db051..0000000
--- a/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java
+++ /dev/null
@@ -1,97 +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.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.net.util.SharedLog;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintWriter;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class SharedLogTest {
-    private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}";
-    private static final String TIMESTAMP = "HH:MM:SS";
-
-    @Test
-    public void testBasicOperation() {
-        final SharedLog logTop = new SharedLog("top");
-        logTop.mark("first post!");
-
-        final SharedLog logLevel2a = logTop.forSubComponent("twoA");
-        final SharedLog logLevel2b = logTop.forSubComponent("twoB");
-        logLevel2b.e("2b or not 2b");
-        logLevel2b.e("No exception", null);
-        logLevel2b.e("Wait, here's one", new Exception("Test"));
-        logLevel2a.w("second post?");
-
-        final SharedLog logLevel3 = logLevel2a.forSubComponent("three");
-        logTop.log("still logging");
-        logLevel3.log("3 >> 2");
-        logLevel2a.mark("ok: last post");
-
-        final String[] expected = {
-            " - MARK first post!",
-            " - [twoB] ERROR 2b or not 2b",
-            " - [twoB] ERROR No exception",
-            // No stacktrace in shared log, only in logcat
-            " - [twoB] ERROR Wait, here's one: Test",
-            " - [twoA] WARN second post?",
-            " - still logging",
-            " - [twoA.three] 3 >> 2",
-            " - [twoA] MARK ok: last post",
-        };
-        // Verify the logs are all there and in the correct order.
-        verifyLogLines(expected, logTop);
-
-        // In fact, because they all share the same underlying LocalLog,
-        // every subcomponent SharedLog's dump() is identical.
-        verifyLogLines(expected, logLevel2a);
-        verifyLogLines(expected, logLevel2b);
-        verifyLogLines(expected, logLevel3);
-    }
-
-    private static void verifyLogLines(String[] expected, SharedLog log) {
-        final ByteArrayOutputStream ostream = new ByteArrayOutputStream();
-        final PrintWriter pw = new PrintWriter(ostream, true);
-        log.dump(null, pw, null);
-
-        final String dumpOutput = ostream.toString();
-        assertTrue(dumpOutput != null);
-        assertTrue(!"".equals(dumpOutput));
-
-        final String[] lines = dumpOutput.split("\n");
-        assertEquals(expected.length, lines.length);
-
-        for (int i = 0; i < expected.length; i++) {
-            String got = lines[i];
-            String want = expected[i];
-            assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want));
-            assertTrue(String.format("'%s' did not contain a %s timestamp", got, TIMESTAMP),
-                    got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP));
-        }
-    }
-}
diff --git a/packages/ExtServices/Android.bp b/packages/PrintRecommendationService/Android.bp
similarity index 72%
copy from packages/ExtServices/Android.bp
copy to packages/PrintRecommendationService/Android.bp
index db94eec..6d28bdb 100644
--- a/packages/ExtServices/Android.bp
+++ b/packages/PrintRecommendationService/Android.bp
@@ -13,14 +13,11 @@
 // limitations under the License.
 
 android_app {
-    name: "ExtServices",
+    name: "PrintRecommendationService",
     srcs: ["src/**/*.java"],
-    platform_apis: true,
-    certificate: "platform",
-    aaptflags: ["--shared-lib"],
-    export_package_resources: true,
-    optimize: {
-        proguard_flags_files: ["proguard.proguard"],
-    },
-    privileged: true,
+    sdk_version: "system_current",
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.core_core",
+    ],
 }
diff --git a/packages/PrintRecommendationService/Android.mk b/packages/PrintRecommendationService/Android.mk
deleted file mode 100644
index d27a6ef..0000000
--- a/packages/PrintRecommendationService/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_USE_AAPT2 := true
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := PrintRecommendationService
-
-LOCAL_SDK_VERSION := system_current
-
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.annotation_annotation
-LOCAL_STATIC_ANDROID_LIBRARIES := androidx.core_core
-
-include $(BUILD_PACKAGE)
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index d879087..a28ba85 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -1,21 +1,13 @@
 # People who can approve changes for submission
-asapperstein@google.com
-asargent@google.com
-dehboxturtle@google.com
-dhnishi@google.com
-dling@google.com
 dsandler@android.com
+edgarwang@google.com
+emilychuang@google.com
 evanlaird@google.com
-jackqdyulei@google.com
-jmonk@google.com
 leifhendrik@google.com
-mfritze@google.com
-rogerxue@google.com
+rafftsai@google.com
+tmfang@google.com
 virgild@google.com
 zhfan@google.com
 
-# Emergency approvers in case the above are not available
-miket@google.com
-
 # Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
 per-file *.xml=*
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 291d4ab..1c35a9d 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -184,7 +184,7 @@
     <string name="enable_adb_summary" msgid="4881186971746056635">"Fejlretningstilstand, når USB er tilsluttet"</string>
     <string name="clear_adb_keys" msgid="4038889221503122743">"Tilbagekald tilladelser for USB-fejlfinding"</string>
     <string name="bugreport_in_power" msgid="7923901846375587241">"Genvej til fejlrapporting"</string>
-    <string name="bugreport_in_power_summary" msgid="1778455732762984579">"Vis en knap til oprettelse af fejlrapporter i menu for slukknap"</string>
+    <string name="bugreport_in_power_summary" msgid="1778455732762984579">"Vis en knap til oprettelse af fejlrapporter i afbrydermenuen"</string>
     <string name="keep_screen_on" msgid="1146389631208760344">"Lås ikke"</string>
     <string name="keep_screen_on_summary" msgid="2173114350754293009">"Skærmen går ikke i dvale under opladning"</string>
     <string name="bt_hci_snoop_log" msgid="3340699311158865670">"Aktivér Bluetooth HCI snoop log"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 84e33bc..df8245c 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -192,7 +192,7 @@
     <string name="oem_unlock_enable" msgid="6040763321967327691">"OEM-Entsperrung"</string>
     <string name="oem_unlock_enable_summary" msgid="4720281828891618376">"Bootloader-Entsperrung zulassen"</string>
     <string name="confirm_enable_oem_unlock_title" msgid="4802157344812385674">"OEM-Entsperrung zulassen?"</string>
-    <string name="confirm_enable_oem_unlock_text" msgid="5517144575601647022">"Achtung: Die Geräteschutzfunktionen funktionieren auf diesem Gerät nicht, solange diese Einstellung aktiviert ist."</string>
+    <string name="confirm_enable_oem_unlock_text" msgid="5517144575601647022">"Achtung: Der Geräteschutz funktioniert auf diesem Gerät nicht, solange diese Einstellung aktiviert ist."</string>
     <string name="mock_location_app" msgid="7966220972812881854">"App für simulierte Standorte auswählen"</string>
     <string name="mock_location_app_not_set" msgid="809543285495344223">"Keine App für simulierte Standorte eingerichtet"</string>
     <string name="mock_location_app_set" msgid="8966420655295102685">"App für simulierte Standorte: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index bcaf79c..5b2b8b0 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -33,7 +33,7 @@
     <string name="wifi_check_password_try_again" msgid="516958988102584767">"Egiaztatu pasahitza zuzena dela eta saiatu berriro"</string>
     <string name="wifi_not_in_range" msgid="1136191511238508967">"Urrunegi"</string>
     <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Ez da konektatuko automatikoki"</string>
-    <string name="wifi_no_internet" msgid="4663834955626848401">"Ezin da atzitu Internet"</string>
+    <string name="wifi_no_internet" msgid="4663834955626848401">"Ezin da konektatu Internetera"</string>
     <string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> aplikazioak gorde du"</string>
     <string name="connected_via_network_scorer" msgid="5713793306870815341">"%1$s bidez automatikoki konektatuta"</string>
     <string name="connected_via_network_scorer_default" msgid="7867260222020343104">"Automatikoki konektatuta sareen balorazioen hornitzailearen bidez"</string>
@@ -67,7 +67,7 @@
     <string name="bluetooth_profile_headset" msgid="7815495680863246034">"Telefono-deiak"</string>
     <string name="bluetooth_profile_opp" msgid="9168139293654233697">"Fitxategi-transferentzia"</string>
     <string name="bluetooth_profile_hid" msgid="3680729023366986480">"Sarrerako gailua"</string>
-    <string name="bluetooth_profile_pan" msgid="3391606497945147673">"Interneterako sarbidea"</string>
+    <string name="bluetooth_profile_pan" msgid="3391606497945147673">"Interneteko konexioa"</string>
     <string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontaktuak partekatzea"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Erabili kontaktuak partekatzeko"</string>
     <string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Interneteko konexioa partekatzea"</string>
@@ -84,9 +84,9 @@
     <string name="bluetooth_sap_profile_summary_connected" msgid="8561765057453083838">"SAP sarbide-puntura konektatuta"</string>
     <string name="bluetooth_opp_profile_summary_not_connected" msgid="1267091356089086285">"Fitxategi-transferentziako zerbitzarira konektatu gabe"</string>
     <string name="bluetooth_hid_profile_summary_connected" msgid="3381760054215168689">"Sarrerako gailura konektatuta"</string>
-    <string name="bluetooth_pan_user_profile_summary_connected" msgid="6436258151814414028">"Gailura konektatuta Interneteko sarbiderako"</string>
+    <string name="bluetooth_pan_user_profile_summary_connected" msgid="6436258151814414028">"Gailura konektatuta Internet atzitzeko"</string>
     <string name="bluetooth_pan_nap_profile_summary_connected" msgid="1322694224800769308">"Tokiko Interneteko konexioa gailu batekin partekatzea"</string>
-    <string name="bluetooth_pan_profile_summary_use_for" msgid="5736111170225304239">"Erabili Internet atzitzeko"</string>
+    <string name="bluetooth_pan_profile_summary_use_for" msgid="5736111170225304239">"Erabili Internetera konektatzeko"</string>
     <string name="bluetooth_map_profile_summary_use_for" msgid="5154200119919927434">"Erabili maparako"</string>
     <string name="bluetooth_sap_profile_summary_use_for" msgid="7085362712786907993">"Erabili SIM txartelerako sarbiderako"</string>
     <string name="bluetooth_a2dp_profile_summary_use_for" msgid="4630849022250168427">"Erabili euskarriaren audiorako"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 6157fec..f1e997b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -33,11 +33,14 @@
 
 import com.android.settingslib.R;
 
+import libcore.timezone.CountryTimeZones;
+import libcore.timezone.CountryTimeZones.TimeZoneMapping;
 import libcore.timezone.TimeZoneFinder;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -386,7 +389,21 @@
 
         @VisibleForTesting
         public List<String> lookupTimeZoneIdsByCountry(String country) {
-            return TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(country);
+            final CountryTimeZones countryTimeZones =
+                    TimeZoneFinder.getInstance().lookupCountryTimeZones(country);
+            if (countryTimeZones == null) {
+                return null;
+            }
+            final List<TimeZoneMapping> mappings = countryTimeZones.getTimeZoneMappings();
+            return extractTimeZoneIds(mappings);
+        }
+
+        private static List<String> extractTimeZoneIds(List<TimeZoneMapping> timeZoneMappings) {
+            final List<String> zoneIds = new ArrayList<>(timeZoneMappings.size());
+            for (TimeZoneMapping timeZoneMapping : timeZoneMappings) {
+                zoneIds.add(timeZoneMapping.timeZoneId);
+            }
+            return Collections.unmodifiableList(zoneIds);
         }
     }
 }
diff --git a/packages/SettingsLib/tests/Android.mk b/packages/SettingsLib/tests/Android.mk
deleted file mode 100644
index 333c41d..0000000
--- a/packages/SettingsLib/tests/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-# Include all makefiles in subdirectories
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
new file mode 100644
index 0000000..1ccac1f
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -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.
+
+android_test {
+    name: "SettingsLibTests",
+    defaults: ["SettingsLibDefaults"],
+
+    certificate: "platform",
+
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "android.test.runner",
+        "telephony-common",
+        "android.test.base",
+    ],
+
+    platform_apis: true,
+    test_suites: ["device-tests"],
+
+    static_libs: [
+        "android-support-test",
+        "espresso-core",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
+    ],
+
+    dxflags: ["--multi-dex"],
+}
diff --git a/packages/SettingsLib/tests/integ/Android.mk b/packages/SettingsLib/tests/integ/Android.mk
deleted file mode 100644
index c893b6d..0000000
--- a/packages/SettingsLib/tests/integ/Android.mk
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_CERTIFICATE := platform
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.test.base
-
-LOCAL_JACK_FLAGS := --multi-dex native
-
-LOCAL_PACKAGE_NAME := SettingsLibTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
-    espresso-core \
-    mockito-target-minus-junit4 \
-    truth-prebuilt
-
-# Code coverage puts us over the dex limit, so enable multi-dex for coverage-enabled builds
-ifeq (true,$(EMMA_INSTRUMENT))
-LOCAL_JACK_FLAGS := --multi-dex native
-LOCAL_DX_FLAGS := --multi-dex
-endif # EMMA_INSTRUMENT
-
-include frameworks/base/packages/SettingsLib/common.mk
-
-include $(BUILD_PACKAGE)
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
new file mode 100644
index 0000000..20c2cf9
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -0,0 +1,40 @@
+// 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.
+
+//###########################################################
+// SettingsLib Shell app just for Robolectric test target.  #
+//###########################################################
+
+android_app {
+    name: "SettingsLibShell",
+    defaults: ["SettingsLibDefaults"],
+    platform_apis: true,
+
+    privileged: true,
+
+    resource_dirs: ["res"],
+}
+
+//############################################
+// SettingsLib Robolectric test target.      #
+//############################################
+android_robolectric_test {
+    name: "SettingsLibRoboTests",
+    srcs: ["src/**/*.java"],
+    java_resource_dirs: ["config"],
+    instrumentation_for: "SettingsLibShell",
+    test_options: {
+        timeout: 36000,
+    },
+}
diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk
deleted file mode 100644
index d15a3ef..0000000
--- a/packages/SettingsLib/tests/robotests/Android.mk
+++ /dev/null
@@ -1,76 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-############################################################
-# SettingsLib Shell app just for Robolectric test target.  #
-############################################################
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := SettingsLibShell
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_PRIVILEGED_MODULE := true
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_USE_AAPT2 := true
-
-include frameworks/base/packages/SettingsLib/common.mk
-
-include $(BUILD_PACKAGE)
-
-#############################################
-# SettingsLib Robolectric test target.      #
-#############################################
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := SettingsLibRoboTests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_RESOURCE_DIRS := config
-
-LOCAL_JAVA_LIBRARIES := \
-    robolectric_android-all-stub \
-    Robolectric_all-target \
-    mockito-robolectric-prebuilt \
-    truth-prebuilt
-
-LOCAL_INSTRUMENTATION_FOR := SettingsLibShell
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-#############################################################
-# SettingsLib runner target to run the previous target.     #
-#############################################################
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := RunSettingsLibRoboTests
-
-LOCAL_JAVA_LIBRARIES := \
-    SettingsLibRoboTests \
-    robolectric_android-all-stub \
-    Robolectric_all-target \
-    mockito-robolectric-prebuilt \
-    truth-prebuilt
-
-LOCAL_TEST_PACKAGE := SettingsLibShell
-
-LOCAL_ROBOTEST_TIMEOUT := 36000
-
-include external/robolectric-shadows/run_robotests.mk
\ No newline at end of file
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 8428797..53ccd1b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -112,7 +112,6 @@
     <uses-permission android:name="android.permission.BIND_APPWIDGET" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
     <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
-    <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" />
     <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 1060c7b..3750a8a 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -175,9 +175,9 @@
     // Passed to Message.obtain() when msg.arg2 is not used.
     private static final int UNUSED_ARG2 = -2;
 
-    // Maximum progress displayed (like 99.00%).
-    private static final int CAPPED_PROGRESS = 9900;
-    private static final int CAPPED_MAX = 10000;
+    // Maximum progress displayed in %.
+    private static final int CAPPED_PROGRESS = 99;
+    private static final int CAPPED_MAX = 100;
 
     /** Show the progress log every this percent. */
     private static final int LOG_PROGRESS_STEP = 10;
@@ -1954,7 +1954,10 @@
 
         @Override
         public void onProgress(int progress) throws RemoteException {
-            updateProgressInfo(progress, 100 /* progress is already a percentage; so max = 100 */);
+            if (progress > CAPPED_PROGRESS) {
+                progress = CAPPED_PROGRESS;
+            }
+            updateProgressInfo(progress, CAPPED_MAX);
         }
 
         @Override
@@ -1967,46 +1970,6 @@
             // TODO(b/111441001): implement
         }
 
-        @Override
-        public void onProgressUpdated(int progress) throws RemoteException {
-            /*
-             * Checks whether the progress changed in a way that should be displayed to the user:
-             * - info.progress / info.max represents the displayed progress
-             * - info.realProgress / info.realMax represents the real progress
-             * - since the real progress can decrease, the displayed progress is only updated if it
-             *   increases
-             * - the displayed progress is capped at a maximum (like 99%)
-             */
-            info.realProgress = progress;
-            final int oldPercentage = (CAPPED_MAX * info.progress) / info.max;
-            int newPercentage = (CAPPED_MAX * info.realProgress) / info.realMax;
-            int max = info.realMax;
-
-            if (newPercentage > CAPPED_PROGRESS) {
-                progress = newPercentage = CAPPED_PROGRESS;
-                max = CAPPED_MAX;
-            }
-
-            if (newPercentage > oldPercentage) {
-                updateProgressInfo(progress, max);
-            }
-        }
-
-        @Override
-        public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
-            Log.d(TAG, "onMaxProgressUpdated: " + maxProgress);
-            info.realMax = maxProgress;
-        }
-
-        @Override
-        public void onSectionComplete(String title, int status, int size, int durationMs)
-                throws RemoteException {
-            if (DEBUG) {
-                Log.v(TAG, "Title: " + title + " Status: " + status + " Size: " + size
-                        + " Duration: " + durationMs + "ms");
-            }
-        }
-
         public void dump(String prefix, PrintWriter pw) {
             pw.print(prefix); pw.print("token: "); pw.println(token);
         }
diff --git a/packages/SimAppDialog/Android.bp b/packages/SimAppDialog/Android.bp
new file mode 100644
index 0000000..d91a116
--- /dev/null
+++ b/packages/SimAppDialog/Android.bp
@@ -0,0 +1,15 @@
+android_app {
+    name: "SimAppDialog",
+
+    srcs: ["src/**/*.java"],
+
+    platform_apis: true,
+    certificate: "platform",
+
+    static_libs: [
+        "android-support-v4",
+        "setup-wizard-lib",
+    ],
+
+    resource_dirs: ["res"],
+}
diff --git a/packages/SimAppDialog/Android.mk b/packages/SimAppDialog/Android.mk
deleted file mode 100644
index 6a4099b..0000000
--- a/packages/SimAppDialog/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := SimAppDialog
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_CERTIFICATE := platform
-
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    android-support-v4
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-include frameworks/opt/setupwizard/library/common-platform-deprecated.mk
-
-include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7d0291f..1a5d6e3 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -68,8 +68,8 @@
     ],
 
     aaptflags: [
-        "--extra-packages",
-        "com.android.keyguard",
+        "--extra-packages com.android.keyguard",
+        "--legacy",
     ],
 }
 
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 99c48cb..151ea6a 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -12,6 +12,7 @@
 dupin@google.com
 ethibodeau@google.com
 evanlaird@google.com
+hyunyoungs@google.com
 jmonk@google.com
 jaggies@google.com
 jjaggi@google.com
@@ -23,6 +24,8 @@
 lynhan@google.com
 madym@google.com
 mankoff@google.com
+mrcasey@google.com
+mrenouf@google.com
 nbenbernou@google.com
 nesciosquid@google.com
 ngmatthew@google.com
diff --git a/packages/SystemUI/plugin/ExamplePlugin/Android.bp b/packages/SystemUI/plugin/ExamplePlugin/Android.bp
index a0eaf14..c6c80f3 100644
--- a/packages/SystemUI/plugin/ExamplePlugin/Android.bp
+++ b/packages/SystemUI/plugin/ExamplePlugin/Android.bp
@@ -11,4 +11,5 @@
 
     srcs: ["src/**/*.java"],
 
+    platform_apis: true,
 }
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index 012fe2f..16073a7 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -34,6 +34,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Forkert pinkode."</string>
     <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Ugyldigt kort."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Opladet"</string>
+    <string name="keyguard_plugged_in_wireless" msgid="3004717438401575235">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Trådløs opladning"</string>
     <string name="keyguard_plugged_in" msgid="3161102098900158923">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Oplader"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="3684592786276709342">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Oplader hurtigt"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="509533586841478405">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Oplader langsomt"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 83451ba..73b1675 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -399,6 +399,7 @@
     <string name="interruption_level_none_twoline" msgid="3957581548190765889">"Silenci\ntotal"</string>
     <string name="interruption_level_priority_twoline" msgid="1564715335217164124">"Només\ninterr. prior."</string>
     <string name="interruption_level_alarms_twoline" msgid="3266909566410106146">"Només\nalarmes"</string>
+    <string name="keyguard_indication_charging_time_wireless" msgid="5376059837186496558">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • S\'està carregant sense fils (temps restant: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
     <string name="keyguard_indication_charging_time" msgid="2056340799276374421">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • S\'està carregant (temps restant: <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
     <string name="keyguard_indication_charging_time_fast" msgid="7767562163577492332">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant ràpidament (temps restant: <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="3769655133567307069">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant lentament (temps restant: <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 42c5cae..f290579 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -405,6 +405,7 @@
     <string name="interruption_level_none_twoline" msgid="3957581548190765889">"שקט\nמוחלט"</string>
     <string name="interruption_level_priority_twoline" msgid="1564715335217164124">"הודעות בעדיפות\nבלבד"</string>
     <string name="interruption_level_alarms_twoline" msgid="3266909566410106146">"התראות\nבלבד"</string>
+    <string name="keyguard_indication_charging_time_wireless" msgid="5376059837186496558">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה אלחוטית (<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום)"</string>
     <string name="keyguard_indication_charging_time" msgid="2056340799276374421">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> עד לסיום)"</string>
     <string name="keyguard_indication_charging_time_fast" msgid="7767562163577492332">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה מהירה (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> עד לסיום)"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="3769655133567307069">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה איטית (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> עד לסיום)"</string>
@@ -800,8 +801,8 @@
     <string name="pip_notification_message" msgid="5619512781514343311">"אם אינך רוצה שהתכונה הזו תשמש את <xliff:g id="NAME">%s</xliff:g>, יש להקיש כדי לפתוח את ההגדרות ולכבות את התכונה."</string>
     <string name="pip_play" msgid="1417176722760265888">"הפעל"</string>
     <string name="pip_pause" msgid="8881063404466476571">"השהה"</string>
-    <string name="pip_skip_to_next" msgid="1948440006726306284">"ברצוני לדלג אל הבא"</string>
-    <string name="pip_skip_to_prev" msgid="1955311326688637914">"ברצוני לדלג אל הקודם"</string>
+    <string name="pip_skip_to_next" msgid="1948440006726306284">"אפשר לדלג אל הבא"</string>
+    <string name="pip_skip_to_prev" msgid="1955311326688637914">"אפשר לדלג אל הקודם"</string>
     <string name="thermal_shutdown_title" msgid="4458304833443861111">"הטלפון כבה עקב התחממות"</string>
     <string name="thermal_shutdown_message" msgid="9006456746902370523">"הטלפון פועל כרגיל עכשיו"</string>
     <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"הטלפון שלך התחמם יותר מדי וכבה כדי להתקרר. הטלפון פועל כרגיל עכשיו.\n\nייתכן שהטלפון יתחמם יותר מדי אם:\n	• תשתמש באפליקציות עתירות משאבים (כגון משחקים, אפליקציות וידאו או אפליקציות ניווט)\n	• תוריד או תעלה קבצים גדולים\n	• תשתמש בטלפון בטמפרטורות גבוהות"</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 03fb9bb..0fcb994 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -340,7 +340,7 @@
                 case SimPuk:
                     // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
                     SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
-                    if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled(
+                    if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled(
                             KeyguardUpdateMonitor.getCurrentUser())) {
                         finish = true;
                     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 38a90cf..c906240 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -219,7 +219,7 @@
         intent.setComponent(assistComponent);
         intent.putExtras(args);
 
-        if (structureEnabled) {
+        if (structureEnabled && AssistUtils.isDisclosureEnabled(mContext)) {
             showDisclosure();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index b7c20aa..b67a9f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -548,7 +548,7 @@
     }
 
     private boolean isDataDisabled() {
-        return !mPhone.getDataEnabled(mSubscriptionInfo.getSubscriptionId());
+        return !mPhone.isDataCapable();
     }
 
     @VisibleForTesting
@@ -568,6 +568,7 @@
         pw.println("  mSignalStrength=" + mSignalStrength + ",");
         pw.println("  mDataState=" + mDataState + ",");
         pw.println("  mDataNetType=" + mDataNetType + ",");
+        pw.println("  isDataDisabled=" + isDataDisabled() + ",");
     }
 
     class MobilePhoneStateListener extends PhoneStateListener {
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
index b0e1c56..c1931f01 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
@@ -86,6 +86,7 @@
         mAlwaysAllow = (CheckBox)checkbox.findViewById(com.android.internal.R.id.alwaysUse);
         mAlwaysAllow.setText(getString(R.string.usb_debugging_always));
         ap.mView = checkbox;
+        window.setCloseOnTouchOutside(false);
 
         setupAlert();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index c1f8885..02f99a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -155,7 +155,7 @@
     protected void setupNetworkController() {
         // For now just pretend to be the data sim, so we can test that too.
         mSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-        when(mMockTm.getDataEnabled(mSubId)).thenReturn(true);
+        when(mMockTm.isDataCapable()).thenReturn(true);
         setDefaultSubId(mSubId);
         setSubscriptions(mSubId);
         mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 96fad21..2aa933e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -119,7 +119,7 @@
     @Test
     public void testNoInternetIcon() {
         setupNetworkController();
-        when(mMockTm.getDataEnabled(mSubId)).thenReturn(false);
+        when(mMockTm.isDataCapable()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -133,7 +133,7 @@
     @Test
     public void testDataDisabledIcon() {
         setupNetworkController();
-        when(mMockTm.getDataEnabled(mSubId)).thenReturn(false);
+        when(mMockTm.isDataCapable()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -188,7 +188,7 @@
     @Test
     public void testDataDisabledIcon_UserNotSetup() {
         setupNetworkController();
-        when(mMockTm.getDataEnabled(mSubId)).thenReturn(false);
+        when(mMockTm.isDataCapable()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -203,7 +203,7 @@
     @Test
     public void testAlwaysShowDataRatIcon() {
         setupDefaultSignal();
-        when(mMockTm.getDataEnabled(mSubId)).thenReturn(false);
+        when(mMockTm.isDataCapable()).thenReturn(false);
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED,
                 TelephonyManager.NETWORK_TYPE_GSM);
 
diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml
index 1d0b9b6..1f49aa1 100644
--- a/packages/VpnDialogs/AndroidManifest.xml
+++ b/packages/VpnDialogs/AndroidManifest.xml
@@ -22,6 +22,7 @@
     <uses-permission android:name="android.permission.CONTROL_VPN" />
     <uses-permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
 
     <application android:label="VpnDialogs"
                  android:allowBackup="false">
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java
index 846fcf8..ba4baf3 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java
@@ -16,6 +16,10 @@
 
 package com.android.vpndialogs;
 
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -31,7 +35,6 @@
 import android.text.style.ClickableSpan;
 import android.util.Log;
 import android.view.View;
-import android.view.WindowManager;
 import android.widget.TextView;
 
 import com.android.internal.app.AlertActivity;
@@ -74,8 +77,9 @@
         setupAlert();
 
         getWindow().setCloseOnTouchOutside(false);
-        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        getWindow().setType(TYPE_SYSTEM_ALERT);
+        getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
+        getWindow().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
     }
 
     @Override
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
index 72ce9c4..0933974 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
@@ -16,6 +16,8 @@
 
 package com.android.vpndialogs;
 
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.pm.PackageManager;
@@ -78,6 +80,7 @@
         setupAlert();
 
         getWindow().setCloseOnTouchOutside(false);
+        getWindow().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
         Button button = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
         button.setFilterTouchesWhenObscured(true);
     }
diff --git a/packages/WAPPushManager/Android.bp b/packages/WAPPushManager/Android.bp
index 1bec492..c391369 100644
--- a/packages/WAPPushManager/Android.bp
+++ b/packages/WAPPushManager/Android.bp
@@ -9,4 +9,6 @@
     optimize: {
         proguard_flags_files: ["proguard.flags"],
     },
+
+    product_specific: true,
 }
diff --git a/packages/WAPPushManager/CleanSpec.mk b/packages/WAPPushManager/CleanSpec.mk
index b84e1b6..2dcbb10 100644
--- a/packages/WAPPushManager/CleanSpec.mk
+++ b/packages/WAPPushManager/CleanSpec.mk
@@ -47,3 +47,5 @@
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/WAPPushManager)
diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk
index 74c43b4..f5ad089 100644
--- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk
@@ -4,8 +4,6 @@
 LOCAL_RRO_THEME := DisplayCutoutEmulationCorner
 LOCAL_CERTIFICATE := platform
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DisplayCutoutEmulationCornerOverlay
diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk
index d83b30a..6264556 100644
--- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk
@@ -4,8 +4,6 @@
 LOCAL_RRO_THEME := DisplayCutoutEmulationDouble
 LOCAL_CERTIFICATE := platform
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DisplayCutoutEmulationDoubleOverlay
diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk
index f5afad2..2ebb87b 100644
--- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk
@@ -4,8 +4,6 @@
 LOCAL_RRO_THEME := DisplayCutoutEmulationNarrow
 LOCAL_CERTIFICATE := platform
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DisplayCutoutEmulationNarrowOverlay
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk
index f1f8c27..cab86f7 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk
@@ -4,8 +4,6 @@
 LOCAL_RRO_THEME := DisplayCutoutEmulationTall
 LOCAL_CERTIFICATE := platform
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DisplayCutoutEmulationTallOverlay
diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk
index d149d8e..51c6f5f 100644
--- a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk
@@ -4,8 +4,6 @@
 LOCAL_RRO_THEME := DisplayCutoutEmulationWide
 LOCAL_CERTIFICATE := platform
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DisplayCutoutEmulationWideOverlay
diff --git a/packages/overlays/SysuiDarkThemeOverlay/Android.mk b/packages/overlays/SysuiDarkThemeOverlay/Android.mk
index 7b277bc..fd3d5d2 100644
--- a/packages/overlays/SysuiDarkThemeOverlay/Android.mk
+++ b/packages/overlays/SysuiDarkThemeOverlay/Android.mk
@@ -4,8 +4,6 @@
 LOCAL_RRO_THEME := SysuiDarkTheme
 LOCAL_CERTIFICATE := platform
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := SysuiDarkThemeOverlay
diff --git a/packages/services/PacProcessor/Android.bp b/packages/services/PacProcessor/Android.bp
index 93b2d95..494a818 100644
--- a/packages/services/PacProcessor/Android.bp
+++ b/packages/services/PacProcessor/Android.bp
@@ -21,3 +21,9 @@
     certificate: "platform",
     jni_libs: ["libjni_pacprocessor"],
 }
+
+filegroup {
+    name: "PacProcessor-aidl-sources",
+    srcs: ["src/**/*.aidl"],
+    path: "src",
+}
diff --git a/packages/services/PacProcessor/com/android/net/IProxyService.aidl b/packages/services/PacProcessor/src/com/android/net/IProxyService.aidl
similarity index 100%
rename from packages/services/PacProcessor/com/android/net/IProxyService.aidl
rename to packages/services/PacProcessor/src/com/android/net/IProxyService.aidl
diff --git a/packages/services/Proxy/Android.bp b/packages/services/Proxy/Android.bp
index 87aa763..d93c9f8 100644
--- a/packages/services/Proxy/Android.bp
+++ b/packages/services/Proxy/Android.bp
@@ -5,3 +5,9 @@
     certificate: "platform",
     privileged: true,
 }
+
+filegroup {
+    name: "ProxyHandler-aidl-sources",
+    srcs: ["src/**/*.aidl"],
+    path: "src",
+}
diff --git a/packages/services/Proxy/com/android/net/IProxyCallback.aidl b/packages/services/Proxy/src/com/android/net/IProxyCallback.aidl
similarity index 100%
rename from packages/services/Proxy/com/android/net/IProxyCallback.aidl
rename to packages/services/Proxy/src/com/android/net/IProxyCallback.aidl
diff --git a/packages/services/Proxy/com/android/net/IProxyPortListener.aidl b/packages/services/Proxy/src/com/android/net/IProxyPortListener.aidl
similarity index 100%
rename from packages/services/Proxy/com/android/net/IProxyPortListener.aidl
rename to packages/services/Proxy/src/com/android/net/IProxyPortListener.aidl
diff --git a/proto/Android.bp b/proto/Android.bp
index e924a4d..ad4e04c 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -5,7 +5,7 @@
         type: "nano",
     },
     srcs: ["src/**/*.proto"],
-    no_framework_libs: true,
+    sdk_version: "core_platform",
     // Pin java_version until jarjar is certified to support later versions. http://b/72703434
     java_version: "1.8",
     target: {
@@ -25,6 +25,5 @@
         type: "nano",
     },
     srcs: ["src/metrics_constants.proto"],
-    no_framework_libs: true,
     sdk_version: "system_current",
 }
diff --git a/services/Android.bp b/services/Android.bp
index bea51be..771f2a7 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -37,6 +37,10 @@
         "android.hidl.manager-V1.0-java",
     ],
 
+    plugins: [
+        "compat-changeid-annotation-processor",
+    ],
+
     // Uncomment to enable output of certain warnings (deprecated, unchecked)
     //javacflags: ["-Xlint"],
 
@@ -50,3 +54,8 @@
     defaults: ["libservices.core-libs"],
     whole_static_libs: ["libservices.core"],
 }
+
+platform_compat_config {
+    name: "services-platform-compat-config",
+    src: ":services",
+}
diff --git a/services/backup/OWNERS b/services/backup/OWNERS
index 1c9a43a..9c21e8f 100644
--- a/services/backup/OWNERS
+++ b/services/backup/OWNERS
@@ -1,7 +1,9 @@
-artikz@google.com
+alsutton@google.com
+anniemeng@google.com
 brufino@google.com
 bryanmawhinney@google.com
 ctate@google.com
 jorlow@google.com
-mkarpinski@google.com
+nathch@google.com
+rthakohov@google.com
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 75c6849..5c9921a 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -18,6 +18,7 @@
         ":vold_aidl",
         ":gsiservice_aidl",
         ":mediaupdateservice_aidl",
+        ":platform-compat-config",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/policy/EventLogTags.logtags",
@@ -49,7 +50,7 @@
         "android.hardware.configstore-V1.0-java",
         "android.hardware.contexthub-V1.0-java",
         "android.hidl.manager-V1.2-java",
-        "dnsresolver_aidl_interface-java",
+        "dnsresolver_aidl_interface-V2-java",
         "netd_aidl_interface-java",
         "netd_event_listener_interface-java",
     ],
@@ -77,4 +78,4 @@
 prebuilt_etc {
     name: "gps_debug.conf",
     src: "java/com/android/server/location/gps_debug.conf",
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 3b78fda..9465617 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1227,14 +1227,21 @@
         }
         @Override
         public void scheduleUpdate() throws RemoteException {
-            traceBegin("HealthScheduleUpdate");
-            try {
-                IHealth service = mHealthServiceWrapper.getLastService();
-                if (service == null) throw new RemoteException("no health service");
-                service.update();
-            } finally {
-                traceEnd();
-            }
+            mHealthServiceWrapper.getHandlerThread().getThreadHandler().post(() -> {
+                traceBegin("HealthScheduleUpdate");
+                try {
+                    IHealth service = mHealthServiceWrapper.getLastService();
+                    if (service == null) {
+                        Slog.e(TAG, "no health service");
+                        return;
+                    }
+                    service.update();
+                } catch (RemoteException ex) {
+                    Slog.e(TAG, "Cannot call update on health HAL", ex);
+                } finally {
+                    traceEnd();
+                }
+            });
         }
     }
 
@@ -1311,7 +1318,7 @@
                 Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
 
         private final IServiceNotification mNotification = new Notification();
-        private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceRefresh");
+        private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder");
         // These variables are fixed after init.
         private Callback mCallback;
         private IHealthSupplier mHealthSupplier;
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 223eb55..89b59cf 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1142,7 +1142,8 @@
         if (isBluetoothDisallowed) {
             return;
         }
-        if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) {
+        final boolean isSafeMode = mContext.getPackageManager().isSafeMode();
+        if (mEnableExternal && isBluetoothPersistedStateOnBluetooth() && !isSafeMode) {
             if (DBG) {
                 Slog.d(TAG, "Auto-enabling Bluetooth.");
             }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b3b5e45..d822879 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -25,8 +25,8 @@
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -77,6 +77,7 @@
 import android.net.ISocketKeepaliveCallback;
 import android.net.ITetheringEventCallback;
 import android.net.InetAddresses;
+import android.net.IpMemoryStore;
 import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.LinkProperties.CompareResult;
@@ -90,6 +91,7 @@
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkMisc;
+import android.net.NetworkMonitorManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
@@ -147,7 +149,6 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.Xml;
 
@@ -165,9 +166,9 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.MessageUtils;
-import com.android.internal.util.WakeupMessage;
 import com.android.internal.util.XmlUtils;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.connectivity.AutodestructReference;
 import com.android.server.connectivity.DataConnectionStats;
 import com.android.server.connectivity.DnsManager;
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
@@ -301,7 +302,8 @@
     /** Flag indicating if background data is restricted. */
     private boolean mRestrictBackground;
 
-    final private Context mContext;
+    private final Context mContext;
+    private final Dependencies mDeps;
     // 0 is full bad, 100 is full good
     private int mDefaultInetConditionPublished = 0;
 
@@ -582,11 +584,6 @@
     private NetworkNotificationManager mNotifier;
     private LingerMonitor mLingerMonitor;
 
-    // sequence number for Networks; keep in sync with system/netd/NetworkController.cpp
-    private static final int MIN_NET_ID = 100; // some reserved marks
-    private static final int MAX_NET_ID = 65535 - 0x0400; // Top 1024 bits reserved by IpSecService
-    private int mNextNetId = MIN_NET_ID;
-
     // sequence number of NetworkRequests
     private int mNextNetworkRequestId = 1;
 
@@ -830,19 +827,113 @@
         }
     };
 
+    /**
+     * Dependencies of ConnectivityService, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /**
+         * Get system properties to use in ConnectivityService.
+         */
+        public MockableSystemProperties getSystemProperties() {
+            return new MockableSystemProperties();
+        }
+
+        /**
+         * Create a HandlerThread to use in ConnectivityService.
+         */
+        public HandlerThread makeHandlerThread() {
+            return new HandlerThread("ConnectivityServiceThread");
+        }
+
+        /**
+         * Get a reference to the NetworkStackClient.
+         */
+        public NetworkStackClient getNetworkStack() {
+            return NetworkStackClient.getInstance();
+        }
+
+        /**
+         * @see Tethering
+         */
+        public Tethering makeTethering(@NonNull Context context,
+                @NonNull INetworkManagementService nms,
+                @NonNull INetworkStatsService statsService,
+                @NonNull INetworkPolicyManager policyManager,
+                @NonNull TetheringDependencies tetheringDeps) {
+            return new Tethering(context, nms, statsService, policyManager,
+                    IoThread.get().getLooper(), getSystemProperties(), tetheringDeps);
+        }
+
+        /**
+         * @see ProxyTracker
+         */
+        public ProxyTracker makeProxyTracker(@NonNull Context context,
+                @NonNull Handler connServiceHandler) {
+            return new ProxyTracker(context, connServiceHandler, EVENT_PROXY_HAS_CHANGED);
+        }
+
+        /**
+         * @see NetIdManager
+         */
+        public NetIdManager makeNetIdManager() {
+            return new NetIdManager();
+        }
+
+        /**
+         * @see NetworkUtils#queryUserAccess(int, int)
+         */
+        public boolean queryUserAccess(int uid, int netId) {
+            return NetworkUtils.queryUserAccess(uid, netId);
+        }
+
+        /**
+         * @see MultinetworkPolicyTracker
+         */
+        public MultinetworkPolicyTracker makeMultinetworkPolicyTracker(
+                @NonNull Context c, @NonNull Handler h, @NonNull Runnable r) {
+            return new MultinetworkPolicyTracker(c, h, r);
+        }
+
+        /**
+         * @see ServiceManager#checkService(String)
+         */
+        public boolean hasService(@NonNull String name) {
+            return ServiceManager.checkService(name) != null;
+        }
+
+        /**
+         * @see IpConnectivityMetrics.Logger
+         */
+        public IpConnectivityMetrics.Logger getMetricsLogger() {
+            return checkNotNull(LocalServices.getService(IpConnectivityMetrics.Logger.class),
+                    "no IpConnectivityMetrics service");
+        }
+
+        /**
+         * @see IpConnectivityMetrics
+         */
+        public IIpConnectivityMetrics getIpConnectivityMetrics() {
+            return IIpConnectivityMetrics.Stub.asInterface(
+                    ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
+        }
+    }
+
     public ConnectivityService(Context context, INetworkManagementService netManager,
             INetworkStatsService statsService, INetworkPolicyManager policyManager) {
-        this(context, netManager, statsService, policyManager,
-            getDnsResolver(), new IpConnectivityLog(), NetdService.getInstance());
+        this(context, netManager, statsService, policyManager, getDnsResolver(),
+                new IpConnectivityLog(), NetdService.getInstance(), new Dependencies());
     }
 
     @VisibleForTesting
     protected ConnectivityService(Context context, INetworkManagementService netManager,
             INetworkStatsService statsService, INetworkPolicyManager policyManager,
-            IDnsResolver dnsresolver, IpConnectivityLog logger, INetd netd) {
+            IDnsResolver dnsresolver, IpConnectivityLog logger, INetd netd, Dependencies deps) {
         if (DBG) log("ConnectivityService starting up");
 
-        mSystemProperties = getSystemProperties();
+        mDeps = checkNotNull(deps, "missing Dependencies");
+        mSystemProperties = mDeps.getSystemProperties();
+        mNetIdManager = mDeps.makeNetIdManager();
 
         mMetricsLog = logger;
         mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
@@ -859,7 +950,7 @@
         mDefaultWifiRequest = createDefaultInternetRequestForTransport(
                 NetworkCapabilities.TRANSPORT_WIFI, NetworkRequest.Type.BACKGROUND_REQUEST);
 
-        mHandlerThread = new HandlerThread("ConnectivityServiceThread");
+        mHandlerThread = mDeps.makeHandlerThread();
         mHandlerThread.start();
         mHandler = new InternalHandler(mHandlerThread.getLooper());
         mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
@@ -877,7 +968,7 @@
                 LocalServices.getService(NetworkPolicyManagerInternal.class),
                 "missing NetworkPolicyManagerInternal");
         mDnsResolver = checkNotNull(dnsresolver, "missing IDnsResolver");
-        mProxyTracker = makeProxyTracker();
+        mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler);
 
         mNetd = netd;
         mKeyStore = KeyStore.getInstance();
@@ -945,7 +1036,7 @@
 
         // Do the same for Ethernet, since it's often not specified in the configs, although many
         // devices can use it via USB host adapters.
-        if (mNetConfigs[TYPE_ETHERNET] == null && hasService(Context.ETHERNET_SERVICE)) {
+        if (mNetConfigs[TYPE_ETHERNET] == null && mDeps.hasService(Context.ETHERNET_SERVICE)) {
             mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET);
             mNetworksDefined++;
         }
@@ -963,7 +1054,10 @@
             }
         }
 
-        mTethering = makeTethering();
+        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+
+        mTethering = deps.makeTethering(mContext, mNMS, mStatsService, mPolicyManager,
+                makeTetheringDependencies());
 
         mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
 
@@ -1007,11 +1101,9 @@
         mSettingsObserver = new SettingsObserver(mContext, mHandler);
         registerSettingsCallbacks();
 
-        final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext);
+        final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext, mHandler);
         dataConnectionStats.startMonitoring();
 
-        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-
         mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
         mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager,
                 mContext.getSystemService(NotificationManager.class));
@@ -1024,7 +1116,7 @@
                 LingerMonitor.DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS);
         mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
 
-        mMultinetworkPolicyTracker = createMultinetworkPolicyTracker(
+        mMultinetworkPolicyTracker = mDeps.makeMultinetworkPolicyTracker(
                 mContext, mHandler, () -> rematchForAvoidBadWifiUpdate());
         mMultinetworkPolicyTracker.start();
 
@@ -1034,10 +1126,8 @@
         registerPrivateDnsSettingsCallbacks();
     }
 
-    @VisibleForTesting
-    protected Tethering makeTethering() {
-        // TODO: Move other elements into @Overridden getters.
-        final TetheringDependencies deps = new TetheringDependencies() {
+    private TetheringDependencies makeTetheringDependencies() {
+        return new TetheringDependencies() {
             @Override
             public boolean isTetheringSupported() {
                 return ConnectivityService.this.isTetheringSupported();
@@ -1047,14 +1137,6 @@
                 return mDefaultRequest;
             }
         };
-        return new Tethering(mContext, mNMS, mStatsService, mPolicyManager,
-                IoThread.get().getLooper(), new MockableSystemProperties(),
-                deps);
-    }
-
-    @VisibleForTesting
-    protected ProxyTracker makeProxyTracker() {
-        return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
     }
 
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
@@ -1146,22 +1228,6 @@
         return mNextNetworkRequestId++;
     }
 
-    @VisibleForTesting
-    protected int reserveNetId() {
-        synchronized (mNetworkForNetId) {
-            for (int i = MIN_NET_ID; i <= MAX_NET_ID; i++) {
-                int netId = mNextNetId;
-                if (++mNextNetId > MAX_NET_ID) mNextNetId = MIN_NET_ID;
-                // Make sure NetID unused.  http://b/16815182
-                if (!mNetIdInUse.get(netId)) {
-                    mNetIdInUse.put(netId, true);
-                    return netId;
-                }
-            }
-        }
-        throw new IllegalStateException("No free netIds");
-    }
-
     private NetworkState getFilteredNetworkState(int networkType, int uid) {
         if (mLegacyTypeTracker.isTypeSupported(networkType)) {
             final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
@@ -1782,8 +1848,7 @@
             // callback from each caller type. Need to re-factor NetdEventListenerService to allow
             // multiple NetworkMonitor registrants.
             if (nai != null && nai.satisfies(mDefaultRequest)) {
-                Binder.withCleanCallingIdentity(() ->
-                        nai.networkMonitor().notifyDnsResponse(returnCode));
+                nai.networkMonitor().notifyDnsResponse(returnCode);
             }
         }
 
@@ -1794,11 +1859,8 @@
         }
     };
 
-    @VisibleForTesting
-    protected void registerNetdEventCallback() {
-        final IIpConnectivityMetrics ipConnectivityMetrics =
-                IIpConnectivityMetrics.Stub.asInterface(
-                        ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
+    private void registerNetdEventCallback() {
+        final IIpConnectivityMetrics ipConnectivityMetrics = mDeps.getIpConnectivityMetrics();
         if (ipConnectivityMetrics == null) {
             Slog.wtf(TAG, "Missing IIpConnectivityMetrics");
             return;
@@ -2234,12 +2296,6 @@
     protected static final String DEFAULT_TCP_BUFFER_SIZES = "4096,87380,110208,4096,16384,110208";
     private static final String DEFAULT_TCP_RWND_KEY = "net.tcp.default_init_rwnd";
 
-    // Overridden for testing purposes to avoid writing to SystemProperties.
-    @VisibleForTesting
-    protected MockableSystemProperties getSystemProperties() {
-        return new MockableSystemProperties();
-    }
-
     private void updateTcpBufferSizes(String tcpBufferSizes) {
         String[] values = null;
         if (tcpBufferSizes != null) {
@@ -2573,11 +2629,11 @@
                     break;
                 }
                 case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: {
-                    if (nai.everConnected && !nai.networkMisc.explicitlySelected) {
-                        loge("ERROR: already-connected network explicitly selected.");
+                    if (nai.everConnected) {
+                        loge("ERROR: cannot call explicitlySelected on already-connected network");
                     }
-                    nai.networkMisc.explicitlySelected = true;
-                    nai.networkMisc.acceptUnvalidated = msg.arg1 == 1;
+                    nai.networkMisc.explicitlySelected = toBool(msg.arg1);
+                    nai.networkMisc.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
                     // Mark the network as temporarily accepting partial connectivity so that it
                     // will be validated (and possibly become default) even if it only provides
                     // partial internet access. Note that if user connects to partial connectivity
@@ -2585,7 +2641,7 @@
                     // out of wifi coverage) and if the same wifi is available again, the device
                     // will auto connect to this wifi even though the wifi has "no internet".
                     // TODO: Evaluate using a separate setting in IpMemoryStore.
-                    nai.networkMisc.acceptPartialConnectivity = msg.arg1 == 1;
+                    nai.networkMisc.acceptPartialConnectivity = toBool(msg.arg2);
                     break;
                 }
                 case NetworkAgent.EVENT_SOCKET_KEEPALIVE: {
@@ -2603,26 +2659,19 @@
                     final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
                     if (nai == null) break;
 
-                    final boolean partialConnectivity =
-                            (msg.arg1 == NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY)
-                                    || (nai.networkMisc.acceptPartialConnectivity
-                                            && nai.partialConnectivity);
-                    // Once a network is determined to have partial connectivity, it cannot
-                    // go back to full connectivity without a disconnect. This is because
-                    // NetworkMonitor can only communicate either PARTIAL_CONNECTIVITY or VALID,
-                    // but not both.
-                    // TODO: Provide multi-testResult to improve the communication between
-                    // ConnectivityService and NetworkMonitor, so that ConnectivityService could
-                    // know the real status of network.
+                    final boolean wasPartial = nai.partialConnectivity;
+                    nai.partialConnectivity = ((msg.arg1 & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
                     final boolean partialConnectivityChanged =
-                            (partialConnectivity && !nai.partialConnectivity);
+                            (wasPartial != nai.partialConnectivity);
 
-                    final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
+                    final boolean valid = ((msg.arg1 & NETWORK_VALIDATION_RESULT_VALID) != 0);
                     final boolean wasValidated = nai.lastValidated;
                     final boolean wasDefault = isDefaultNetwork(nai);
-                    if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified
-                            && valid) {
-                        nai.captivePortalLoginNotified = true;
+                    // Only show a connected notification if the network is pending validation
+                    // after the captive portal app was open, and it has now validated.
+                    if (nai.captivePortalValidationPending && valid) {
+                        // User is now logged in, network validated.
+                        nai.captivePortalValidationPending = false;
                         showNetworkNotification(nai, NotificationType.LOGGED_IN);
                     }
 
@@ -2636,8 +2685,9 @@
                     }
                     if (valid != nai.lastValidated) {
                         if (wasDefault) {
-                            metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
-                                    SystemClock.elapsedRealtime(), valid);
+                            mDeps.getMetricsLogger()
+                                    .defaultNetworkMetrics().logDefaultNetworkValidity(
+                                            SystemClock.elapsedRealtime(), valid);
                         }
                         final int oldScore = nai.getCurrentScore();
                         nai.lastValidated = valid;
@@ -2647,25 +2697,38 @@
                         if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                         if (valid) {
                             handleFreshlyValidatedNetwork(nai);
-                            // Clear NO_INTERNET and LOST_INTERNET notifications if network becomes
-                            // valid.
+                            // Clear NO_INTERNET, PARTIAL_CONNECTIVITY and LOST_INTERNET
+                            // notifications if network becomes valid.
                             mNotifier.clearNotification(nai.network.netId,
                                     NotificationType.NO_INTERNET);
                             mNotifier.clearNotification(nai.network.netId,
                                     NotificationType.LOST_INTERNET);
+                            mNotifier.clearNotification(nai.network.netId,
+                                    NotificationType.PARTIAL_CONNECTIVITY);
                         }
                     } else if (partialConnectivityChanged) {
-                        nai.partialConnectivity = partialConnectivity;
                         updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
                     }
                     updateInetCondition(nai);
                     // Let the NetworkAgent know the state of its network
                     Bundle redirectUrlBundle = new Bundle();
                     redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
+                    // TODO: Evaluate to update partial connectivity to status to NetworkAgent.
                     nai.asyncChannel.sendMessage(
                             NetworkAgent.CMD_REPORT_NETWORK_STATUS,
                             (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
                             0, redirectUrlBundle);
+
+                    // If NetworkMonitor detects partial connectivity before
+                    // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification
+                    // immediately. Re-notify partial connectivity silently if no internet
+                    // notification already there.
+                    if (!wasPartial && nai.partialConnectivity) {
+                        // Remove delayed message if there is a pending message.
+                        mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network);
+                        handlePromptUnvalidated(nai.network);
+                    }
+
                     if (wasValidated && !nai.lastValidated) {
                         handleNetworkUnvalidated(nai);
                     }
@@ -2680,9 +2743,6 @@
                         final int oldScore = nai.getCurrentScore();
                         nai.lastCaptivePortalDetected = visible;
                         nai.everCaptivePortalDetected |= visible;
-                        if (visible) {
-                            nai.captivePortalLoginNotified = false;
-                        }
                         if (nai.lastCaptivePortalDetected &&
                             Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
                             if (DBG) log("Avoiding captive portal network: " + nai.name());
@@ -2767,29 +2827,31 @@
     }
 
     private class NetworkMonitorCallbacks extends INetworkMonitorCallbacks.Stub {
-        private final NetworkAgentInfo mNai;
+        private final int mNetId;
+        private final AutodestructReference<NetworkAgentInfo> mNai;
 
         private NetworkMonitorCallbacks(NetworkAgentInfo nai) {
-            mNai = nai;
+            mNetId = nai.network.netId;
+            mNai = new AutodestructReference(nai);
         }
 
         @Override
         public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
             mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT,
-                    new Pair<>(mNai, networkMonitor)));
+                    new Pair<>(mNai.getAndDestroy(), networkMonitor)));
         }
 
         @Override
         public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) {
             mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED,
-                    testResult, mNai.network.netId, redirectUrl));
+                    testResult, mNetId, redirectUrl));
         }
 
         @Override
         public void notifyPrivateDnsConfigResolved(PrivateDnsConfigParcel config) {
             mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
                     EVENT_PRIVATE_DNS_CONFIG_RESOLVED,
-                    0, mNai.network.netId, PrivateDnsConfig.fromParcel(config)));
+                    0, mNetId, PrivateDnsConfig.fromParcel(config)));
         }
 
         @Override
@@ -2807,15 +2869,13 @@
             }
             mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
                     EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_SHOW,
-                    mNai.network.netId,
-                    pendingIntent));
+                    mNetId, pendingIntent));
         }
 
         @Override
         public void hideProvisioningNotification() {
             mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
-                    EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE,
-                    mNai.network.netId));
+                    EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE, mNetId));
         }
 
         @Override
@@ -2857,11 +2917,7 @@
         // Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or
         // schedule DNS resolutions. If a DNS resolution is required the
         // result will be sent back to us.
-        try {
-            nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
-        }
+        nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
 
         // With Private DNS bypass support, we can proceed to update the
         // Private DNS config immediately, even if we're in strict mode
@@ -2966,8 +3022,8 @@
                     final boolean wasDefault = isDefaultNetwork(nai);
                     synchronized (mNetworkForNetId) {
                         mNetworkForNetId.remove(nai.network.netId);
-                        mNetIdInUse.delete(nai.network.netId);
                     }
+                    mNetIdManager.releaseNetId(nai.network.netId);
                     // Just in case.
                     mLegacyTypeTracker.remove(nai, wasDefault);
                 }
@@ -3014,7 +3070,7 @@
             // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence
             // whose timestamps tell how long it takes to recover a default network.
             long now = SystemClock.elapsedRealtime();
-            metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, null, nai);
+            mDeps.getMetricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, null, nai);
         }
         notifyIfacesChangedForNetworkStats();
         // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
@@ -3027,11 +3083,7 @@
             // Disable wakeup packet monitoring for each interface.
             wakeupModifyInterface(iface, nai.networkCapabilities, false);
         }
-        try {
-            nai.networkMonitor().notifyNetworkDisconnected();
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
-        }
+        nai.networkMonitor().notifyNetworkDisconnected();
         mNetworkAgentInfos.remove(nai.messenger);
         nai.clatd.update();
         synchronized (mNetworkForNetId) {
@@ -3072,9 +3124,7 @@
             destroyNativeNetwork(nai);
             mDnsManager.removeNetwork(nai.network);
         }
-        synchronized (mNetworkForNetId) {
-            mNetIdInUse.delete(nai.network.netId);
-        }
+        mNetIdManager.releaseNetId(nai.network.netId);
     }
 
     private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
@@ -3441,11 +3491,10 @@
             // Inform NetworkMonitor that partial connectivity is acceptable. This will likely
             // result in a partial connectivity result which will be processed by
             // maybeHandleNetworkMonitorMessage.
-            try {
-                nai.networkMonitor().setAcceptPartialConnectivity();
-            } catch (RemoteException e) {
-                e.rethrowAsRuntimeException();
-            }
+            //
+            // TODO: NetworkMonitor does not refer to the "never ask again" bit. The bit is stored
+            // per network. Therefore, NetworkMonitor may still do https probe.
+            nai.networkMonitor().setAcceptPartialConnectivity();
         }
     }
 
@@ -3477,11 +3526,7 @@
             NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
             if (nai == null) return;
             if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return;
-            try {
-                nai.networkMonitor().launchCaptivePortalApp();
-            } catch (RemoteException e) {
-                e.rethrowAsRuntimeException();
-            }
+            nai.networkMonitor().launchCaptivePortalApp();
         });
     }
 
@@ -3504,6 +3549,12 @@
                 new CaptivePortal(new CaptivePortalImpl(network).asBinder()));
         appIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
 
+        // This runs on a random binder thread, but getNetworkAgentInfoForNetwork is thread-safe,
+        // and captivePortalValidationPending is volatile.
+        final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+        if (nai != null) {
+            nai.captivePortalValidationPending = true;
+        }
         Binder.withCleanCallingIdentity(() ->
                 mContext.startActivityAsUser(appIntent, UserHandle.CURRENT));
     }
@@ -3516,7 +3567,7 @@
         }
 
         @Override
-        public void appResponse(final int response) throws RemoteException {
+        public void appResponse(final int response) {
             if (response == CaptivePortal.APP_RETURN_WANTED_AS_IS) {
                 enforceSettingsPermission();
             }
@@ -3526,16 +3577,9 @@
             if (nai == null) return;
 
             // nai.networkMonitor() is thread-safe
-            final INetworkMonitor nm = nai.networkMonitor();
+            final NetworkMonitorManager nm = nai.networkMonitor();
             if (nm == null) return;
-
-            final long token = Binder.clearCallingIdentity();
-            try {
-                nm.notifyCaptivePortalAppFinished(response);
-            } finally {
-                // Not using Binder.withCleanCallingIdentity() to keep the checked RemoteException
-                Binder.restoreCallingIdentity(token);
-            }
+            nm.notifyCaptivePortalAppFinished(response);
         }
 
         @Override
@@ -3611,21 +3655,31 @@
 
     private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) {
         final String action;
+        final boolean highPriority;
         switch (type) {
             case LOGGED_IN:
                 action = Settings.ACTION_WIFI_SETTINGS;
                 mHandler.removeMessages(EVENT_TIMEOUT_NOTIFICATION);
                 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NOTIFICATION,
                         nai.network.netId, 0), TIMEOUT_NOTIFICATION_DELAY_MS);
+                // High priority because it is a direct result of the user logging in to a portal.
+                highPriority = true;
                 break;
             case NO_INTERNET:
                 action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
+                // High priority because it is only displayed for explicitly selected networks.
+                highPriority = true;
                 break;
             case LOST_INTERNET:
                 action = ConnectivityManager.ACTION_PROMPT_LOST_VALIDATION;
+                // High priority because it could help the user avoid unexpected data usage.
+                highPriority = true;
                 break;
             case PARTIAL_CONNECTIVITY:
                 action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY;
+                // Don't bother the user with a high-priority notification if the network was not
+                // explicitly selected by the user.
+                highPriority = nai.networkMisc.explicitlySelected;
                 break;
             default:
                 Slog.wtf(TAG, "Unknown notification type " + type);
@@ -3642,25 +3696,50 @@
 
         PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
                 mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
-        mNotifier.showNotification(nai.network.netId, type, nai, null, pendingIntent, true);
+
+        mNotifier.showNotification(nai.network.netId, type, nai, null, pendingIntent, highPriority);
+    }
+
+    private boolean shouldPromptUnvalidated(NetworkAgentInfo nai) {
+        // Don't prompt if the network is validated, and don't prompt on captive portals
+        // because we're already prompting the user to sign in.
+        if (nai.everValidated || nai.everCaptivePortalDetected) {
+            return false;
+        }
+
+        // If a network has partial connectivity, always prompt unless the user has already accepted
+        // partial connectivity and selected don't ask again. This ensures that if the device
+        // automatically connects to a network that has partial Internet access, the user will
+        // always be able to use it, either because they've already chosen "don't ask again" or
+        // because we have prompt them.
+        if (nai.partialConnectivity && !nai.networkMisc.acceptPartialConnectivity) {
+            return true;
+        }
+
+        // If a network has no Internet access, only prompt if the network was explicitly selected
+        // and if the user has not already told us to use the network regardless of whether it
+        // validated or not.
+        if (nai.networkMisc.explicitlySelected && !nai.networkMisc.acceptUnvalidated) {
+            return true;
+        }
+
+        return false;
     }
 
     private void handlePromptUnvalidated(Network network) {
         if (VDBG || DDBG) log("handlePromptUnvalidated " + network);
         NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
 
-        // Only prompt if the network is unvalidated or network has partial internet connectivity
-        // and was explicitly selected by the user, and if we haven't already been told to switch
-        // to it regardless of whether it validated or not. Also don't prompt on captive portals
-        // because we're already prompting the user to sign in.
-        if (nai == null || nai.everValidated || nai.everCaptivePortalDetected
-                || !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated
-                // TODO: Once the value of acceptPartialConnectivity is moved to IpMemoryStore,
-                // we should reevaluate how to handle acceptPartialConnectivity when network just
-                // connected.
-                || nai.networkMisc.acceptPartialConnectivity) {
+        if (nai == null || !shouldPromptUnvalidated(nai)) {
             return;
         }
+
+        // Stop automatically reconnecting to this network in the future. Automatically connecting
+        // to a network that provides no or limited connectivity is not useful, because the user
+        // cannot use that network except through the notification shown by this method, and the
+        // notification is only shown if the network is explicitly selected by the user.
+        nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
+
         // TODO: Evaluate if it's needed to wait 8 seconds for triggering notification when
         // NetworkMonitor detects the network is partial connectivity. Need to change the design to
         // popup the notification immediately when the network is partial connectivity.
@@ -4106,11 +4185,7 @@
         if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) {
             return;
         }
-        try {
-            nai.networkMonitor().forceReevaluation(uid);
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
-        }
+        nai.networkMonitor().forceReevaluation(uid);
     }
 
     /**
@@ -4133,7 +4208,7 @@
                 return null;
             }
             return getLinkPropertiesProxyInfo(activeNetwork);
-        } else if (queryUserAccess(Binder.getCallingUid(), network.netId)) {
+        } else if (mDeps.queryUserAccess(Binder.getCallingUid(), network.netId)) {
             // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
             // caller may not have.
             return getLinkPropertiesProxyInfo(network);
@@ -4142,10 +4217,6 @@
         return null;
     }
 
-    @VisibleForTesting
-    protected boolean queryUserAccess(int uid, int netId) {
-        return NetworkUtils.queryUserAccess(uid, netId);
-    }
 
     private ProxyInfo getLinkPropertiesProxyInfo(Network network) {
         final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
@@ -4377,7 +4448,7 @@
         // the underlyingNetworks list.
         if (underlyingNetworks == null) {
             NetworkAgentInfo defaultNai = getDefaultNetwork();
-            if (defaultNai != null && defaultNai.linkProperties != null) {
+            if (defaultNai != null) {
                 underlyingNetworks = new Network[] { defaultNai.network };
             }
         }
@@ -4386,7 +4457,11 @@
             for (Network network : underlyingNetworks) {
                 LinkProperties lp = getLinkProperties(network);
                 if (lp != null) {
-                    interfaces.add(lp.getInterfaceName());
+                    for (String iface : lp.getAllInterfaceNames()) {
+                        if (!TextUtils.isEmpty(iface)) {
+                            interfaces.add(iface);
+                        }
+                    }
                 }
             }
             if (!interfaces.isEmpty()) {
@@ -4475,7 +4550,7 @@
                     Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
                     return false;
                 }
-                setLockdownTracker(new LockdownVpnTracker(mContext, mNMS, this, vpn, profile));
+                setLockdownTracker(new LockdownVpnTracker(mContext, this, mHandler, vpn, profile));
             } else {
                 setLockdownTracker(null);
             }
@@ -4734,7 +4809,7 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             // Concatenate the range of types onto the range of NetIDs.
-            int id = MAX_NET_ID + 1 + (networkType - ConnectivityManager.TYPE_NONE);
+            int id = NetIdManager.MAX_NET_ID + 1 + (networkType - ConnectivityManager.TYPE_NONE);
             mNotifier.setProvNotificationVisible(visible, id, action);
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -5345,10 +5420,9 @@
     @GuardedBy("mNetworkForNetId")
     private final SparseArray<NetworkAgentInfo> mNetworkForNetId = new SparseArray<>();
     // NOTE: Accessed on multiple threads, synchronized with mNetworkForNetId.
-    // An entry is first added to mNetIdInUse, prior to mNetworkForNetId, so
+    // An entry is first reserved with NetIdManager, prior to being added to mNetworkForNetId, so
     // there may not be a strict 1:1 correlation between the two.
-    @GuardedBy("mNetworkForNetId")
-    private final SparseBooleanArray mNetIdInUse = new SparseBooleanArray();
+    private final NetIdManager mNetIdManager;
 
     // NetworkAgentInfo keyed off its connecting messenger
     // TODO - eval if we can reduce the number of lists/hashmaps/sparsearrays
@@ -5450,9 +5524,9 @@
         // satisfies mDefaultRequest.
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
         final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
-                new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore,
-                mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mDnsResolver,
-                mNMS, factorySerialNumber);
+                new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
+                currentScore, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd,
+                mDnsResolver, mNMS, factorySerialNumber);
         // Make sure the network capabilities reflect what the agent info says.
         nai.setNetworkCapabilities(mixInCapabilities(nai, nc));
         final String extraInfo = networkInfo.getExtraInfo();
@@ -5461,7 +5535,7 @@
         if (DBG) log("registerNetworkAgent " + nai);
         final long token = Binder.clearCallingIdentity();
         try {
-            getNetworkStack().makeNetworkMonitor(
+            mDeps.getNetworkStack().makeNetworkMonitor(
                     nai.network, name, new NetworkMonitorCallbacks(nai));
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -5473,11 +5547,6 @@
         return nai.network.netId;
     }
 
-    @VisibleForTesting
-    protected NetworkStackClient getNetworkStack() {
-        return NetworkStackClient.getInstance();
-    }
-
     private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
         nai.onNetworkMonitorCreated(networkMonitor);
         if (VDBG) log("Got NetworkAgent Messenger");
@@ -5493,7 +5562,6 @@
         }
         nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger);
         NetworkInfo networkInfo = nai.networkInfo;
-        nai.networkInfo = null;
         updateNetworkInfo(nai, networkInfo);
         updateUids(nai, null, nai.networkCapabilities);
     }
@@ -5543,11 +5611,7 @@
             // Start or stop DNS64 detection and 464xlat according to network state.
             networkAgent.clatd.update();
             notifyIfacesChangedForNetworkStats();
-            try {
-                networkAgent.networkMonitor().notifyLinkPropertiesChanged(newLp);
-            } catch (RemoteException e) {
-                e.rethrowAsRuntimeException();
-            }
+            networkAgent.networkMonitor().notifyLinkPropertiesChanged(newLp);
             if (networkAgent.everConnected) {
                 notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
             }
@@ -6291,7 +6355,7 @@
             // Notify system services that this network is up.
             makeDefault(newNetwork);
             // Log 0 -> X and Y -> X default network transitions, where X is the new default.
-            metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(
+            mDeps.getMetricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(
                     now, newNetwork, oldDefaultNetwork);
             // Have a new default network, release the transition wakelock in
             scheduleReleaseNetworkTransitionWakelock();
@@ -6496,8 +6560,7 @@
 
         if (DBG) {
             log(networkAgent.name() + " EVENT_NETWORK_INFO_CHANGED, going from " +
-                    (oldInfo == null ? "null" : oldInfo.getState()) +
-                    " to " + state);
+                    oldInfo.getState() + " to " + state);
         }
 
         if (!networkAgent.created
@@ -6531,15 +6594,11 @@
             // command must be sent after updating LinkProperties to maximize chances of
             // NetworkMonitor seeing the correct LinkProperties when starting.
             // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
-            try {
-                if (networkAgent.networkMisc.acceptPartialConnectivity) {
-                    networkAgent.networkMonitor().setAcceptPartialConnectivity();
-                }
-                networkAgent.networkMonitor().notifyNetworkConnected(
-                        networkAgent.linkProperties, networkAgent.networkCapabilities);
-            } catch (RemoteException e) {
-                e.rethrowAsRuntimeException();
+            if (networkAgent.networkMisc.acceptPartialConnectivity) {
+                networkAgent.networkMonitor().setAcceptPartialConnectivity();
             }
+            networkAgent.networkMonitor().notifyNetworkConnected(
+                    networkAgent.linkProperties, networkAgent.networkCapabilities);
             scheduleUnvalidatedPrompt(networkAgent);
 
             // Whether a particular NetworkRequest listen should cause signal strength thresholds to
@@ -6574,8 +6633,8 @@
                 // TODO(b/122649188): send the broadcast only to VPN users.
                 mProxyTracker.sendProxyBroadcast();
             }
-        } else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
-                state == NetworkInfo.State.SUSPENDED) {
+        } else if (networkAgent.created && (oldInfo.getState() == NetworkInfo.State.SUSPENDED ||
+                state == NetworkInfo.State.SUSPENDED)) {
             // going into or coming out of SUSPEND: re-score and notify
             if (networkAgent.getCurrentScore() != oldScore) {
                 rematchAllNetworksAndRequests(networkAgent, oldScore);
@@ -6782,7 +6841,7 @@
 
     /**
      * Notify NetworkStatsService that the set of active ifaces has changed, or that one of the
-     * properties tracked by NetworkStatsService on an active iface has changed.
+     * active iface's tracked properties has changed.
      */
     private void notifyIfacesChangedForNetworkStats() {
         ensureRunningOnConnectivityServiceThread();
@@ -6791,9 +6850,11 @@
         if (activeLinkProperties != null) {
             activeIface = activeLinkProperties.getInterfaceName();
         }
+
+        final VpnInfo[] vpnInfos = getAllVpnInfo();
         try {
             mStatsService.forceUpdateIfaces(
-                    getDefaultNetworks(), getAllVpnInfo(), getAllNetworkState(), activeIface);
+                    getDefaultNetworks(), getAllNetworkState(), activeIface, vpnInfos);
         } catch (Exception ignored) {
         }
     }
@@ -6897,6 +6958,11 @@
 
         final int userId = UserHandle.getCallingUserId();
 
+        Binder.withCleanCallingIdentity(() -> {
+            final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext);
+            ipMemoryStore.factoryReset();
+        });
+
         // Turn airplane mode off
         setAirplaneMode(false);
 
@@ -6960,27 +7026,6 @@
         return nwm.getWatchlistConfigHash();
     }
 
-    @VisibleForTesting
-    MultinetworkPolicyTracker createMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
-        return new MultinetworkPolicyTracker(c, h, r);
-    }
-
-    @VisibleForTesting
-    public WakeupMessage makeWakeupMessage(Context c, Handler h, String s, int cmd, Object obj) {
-        return new WakeupMessage(c, h, s, cmd, 0, 0, obj);
-    }
-
-    @VisibleForTesting
-    public boolean hasService(String name) {
-        return ServiceManager.checkService(name) != null;
-    }
-
-    @VisibleForTesting
-    protected IpConnectivityMetrics.Logger metricsLogger() {
-        return checkNotNull(LocalServices.getService(IpConnectivityMetrics.Logger.class),
-                "no IpConnectivityMetrics service");
-    }
-
     private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
         int[] transports = nai.networkCapabilities.getTransportTypes();
         mMetricsLog.log(nai.network.netId, transports, new NetworkEvent(evtype));
diff --git a/services/core/java/com/android/server/ExtconStateObserver.java b/services/core/java/com/android/server/ExtconStateObserver.java
new file mode 100644
index 0000000..6b561c7
--- /dev/null
+++ b/services/core/java/com/android/server/ExtconStateObserver.java
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FileUtils;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * A specialized ExtconUEventObserver that on receiving a {@link UEvent} calls {@link
+ * #updateState(ExtconInfo, String, S)} with the value of{@link #parseState(ExtconInfo, String)}.
+ *
+ * @param <S> the type of state to parse and update
+ * @hide
+ */
+public abstract class ExtconStateObserver<S> extends ExtconUEventObserver {
+    private static final String TAG = "ExtconStateObserver";
+    private static final boolean LOG = false;
+
+    /**
+     * Parses the current state from the state file for {@code extconInfo} and calls {@link
+     * #updateState(ExtconInfo, String, Object)}
+     *
+     * @param extconInfo the extconInfo to update state for
+     * @see #parseState(ExtconInfo, String)
+     * @see ExtconInfo#getStatePath()
+     */
+    public void updateStateFromFile(ExtconInfo extconInfo) {
+        String statePath = extconInfo.getStatePath();
+        try {
+            S state =
+                    parseState(
+                            extconInfo,
+                            FileUtils.readTextFile(new File(statePath), 0, null).trim());
+            if (state != null) {
+                updateState(extconInfo, extconInfo.getName(), state);
+            }
+        } catch (FileNotFoundException e) {
+            Slog.w(TAG, statePath + " not found while attempting to determine initial state", e);
+        } catch (IOException e) {
+            Slog.e(
+                    TAG,
+                    "Error reading " + statePath + " while attempting to determine initial state ",
+                    e);
+        }
+    }
+
+    @Override
+    public void onUEvent(ExtconInfo extconInfo, UEvent event) {
+        if (LOG) Slog.d(TAG, extconInfo.getName() + " UEVENT: " + event);
+        String name = event.get("NAME");
+        S state = parseState(extconInfo, event.get("STATE"));
+        if (state != null) {
+            updateState(extconInfo, name, state);
+        }
+    }
+
+    /**
+     * Subclasses of ExtconStateObserver should override this method update state for {@code
+     * exconInfo} from an {@code UEvent}.
+     *
+     * @param extconInfo the external connection
+     * @param eventName the {@code NAME} of the {@code UEvent}
+     * @param state the{@code STATE} as parsed by {@link #parseState(ExtconInfo, String)}.
+     */
+    public abstract void updateState(ExtconInfo extconInfo, String eventName, @NonNull S state);
+
+    /**
+     * Subclasses of ExtconStateObserver should override this method to parse the {@code STATE} from
+     * an UEvent.
+     *
+     * @param extconInfo that matches the {@code DEVPATH} of {@code event}
+     * @param state the {@code STATE} from a {@code UEvent}.
+     * @return the parsed state. Return null if the state can not be parsed.
+     */
+    @Nullable
+    public abstract S parseState(ExtconInfo extconInfo, String state);
+}
diff --git a/services/core/java/com/android/server/ExtconUEventObserver.java b/services/core/java/com/android/server/ExtconUEventObserver.java
new file mode 100644
index 0000000..b3084f5
--- /dev/null
+++ b/services/core/java/com/android/server/ExtconUEventObserver.java
@@ -0,0 +1,128 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.os.UEventObserver;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * A specialized UEventObserver that receives UEvents from the kernel for devices in the {@code
+ * /sys/class/extcon}. directory
+ *
+ * <p>Subclass ExtconUEventObserver, implementing {@link #onUEvent(ExtconInfo, UEvent)}, then call
+ * startObserving() with a ExtconInfo to observe. The UEvent thread will then call your onUEvent()
+ * method when a UEvent occurs that matches the path of your ExtconInfos.
+ *
+ * <p>Call stopObserving() to stop receiving UEvents.
+ *
+ * <p>There is only one UEvent thread per process, even if that process has multiple UEventObserver
+ * subclass instances. The UEvent thread starts when the startObserving() is called for the first
+ * time in that process. Once started the UEvent thread will not stop (although it can stop
+ * notifying UEventObserver's via stopObserving()).
+ *
+ * <p>
+ *
+ * @hide
+ */
+public abstract class ExtconUEventObserver extends UEventObserver {
+    private static final String TAG = "ExtconUEventObserver";
+    private static final boolean LOG = false;
+    private final Map<String, ExtconInfo> mExtconInfos = new ArrayMap<>();
+
+    @Override
+    public final void onUEvent(UEvent event) {
+        String devPath = event.get("DEVPATH");
+        ExtconInfo info = mExtconInfos.get(devPath);
+        if (info != null) {
+            onUEvent(info, event);
+        } else {
+            Slog.w(TAG, "No match found for DEVPATH of " + event + " in " + mExtconInfos);
+        }
+    }
+
+    /**
+     * Subclasses of ExtconUEventObserver should override this method to handle UEvents.
+     *
+     * @param extconInfo that matches the {@code DEVPATH} of {@code event}
+     * @param event the event
+     */
+    protected abstract void onUEvent(ExtconInfo extconInfo, UEvent event);
+
+    /** Starts observing {@link ExtconInfo#getDevicePath()}. */
+    public void startObserving(ExtconInfo extconInfo) {
+        mExtconInfos.put(extconInfo.getDevicePath(), extconInfo);
+        if (LOG) Slog.v(TAG, "Observing  " + extconInfo.getDevicePath());
+        startObserving("DEVPATH=" + extconInfo.getDevicePath());
+    }
+
+    /** An External Connection to watch. */
+    public static final class ExtconInfo {
+        private static final String TAG = "ExtconInfo";
+
+        private final String mName;
+
+        public ExtconInfo(String name) {
+            mName = name;
+        }
+
+        /** The name of the external connection */
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * The path to the device for this external connection.
+         *
+         * <p><b>NOTE</b> getting this path involves resolving a symlink.
+         *
+         * @return the device path, or null if it not found.
+         */
+        @Nullable
+        public String getDevicePath() {
+            try {
+                String extconPath = String.format(Locale.US, "/sys/class/extcon/%s", mName);
+                File devPath = new File(extconPath);
+                if (devPath.exists()) {
+                    String canonicalPath = devPath.getCanonicalPath();
+                    int start = canonicalPath.indexOf("/devices");
+                    return canonicalPath.substring(start);
+                }
+                return null;
+            } catch (IOException e) {
+                Slog.e(TAG, "Could not get the extcon device path for " + mName, e);
+                return null;
+            }
+        }
+
+        /** The path to the state file */
+        public String getStatePath() {
+            return String.format(Locale.US, "/sys/class/extcon/%s/state", mName);
+        }
+    }
+
+    /** Does the {@link /sys/class/extcon} directory exist */
+    public static boolean extconExists() {
+        File extconDir = new File("/sys/class/extcon");
+        return extconDir.exists() && extconDir.isDirectory();
+    }
+}
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 4639d75..70569db 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -191,7 +191,7 @@
             if (!file.getFileDescriptor().valid()) {
                 throw new IllegalStateException("Invalid file descriptor");
             }
-            return new ParcelFileDescriptor(file.getFileDescriptor());
+            return ParcelFileDescriptor.dup(file.getFileDescriptor());
         } catch (IOException ex) {
             throw new IllegalStateException("Failed to get PFD from memory file", ex);
         }
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index b4e1c32..a0946a0 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -750,10 +750,10 @@
         }
     }
 
-    // These values have been reserved in ConnectivityService
+    // These values have been reserved in NetIdManager
     @VisibleForTesting static final int TUN_INTF_NETID_START = 0xFC00;
 
-    @VisibleForTesting static final int TUN_INTF_NETID_RANGE = 0x0400;
+    public static final int TUN_INTF_NETID_RANGE = 0x0400;
 
     private final SparseBooleanArray mTunnelNetIds = new SparseBooleanArray();
     private int mNextTunnelNetIdIndex = 0;
diff --git a/services/core/java/com/android/server/NetIdManager.java b/services/core/java/com/android/server/NetIdManager.java
new file mode 100644
index 0000000..11533be
--- /dev/null
+++ b/services/core/java/com/android/server/NetIdManager.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.annotation.NonNull;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Class used to reserve and release net IDs.
+ *
+ * <p>Instances of this class are thread-safe.
+ */
+public class NetIdManager {
+    // Sequence number for Networks; keep in sync with system/netd/NetworkController.cpp
+    public static final int MIN_NET_ID = 100; // some reserved marks
+    // Top IDs reserved by IpSecService
+    public static final int MAX_NET_ID = 65535 - IpSecService.TUN_INTF_NETID_RANGE;
+
+    @GuardedBy("mNetIdInUse")
+    private final SparseBooleanArray mNetIdInUse = new SparseBooleanArray();
+
+    @GuardedBy("mNetIdInUse")
+    private int mLastNetId = MIN_NET_ID - 1;
+
+    /**
+     * Get the first netId that follows the provided lastId and is available.
+     */
+    private static int getNextAvailableNetIdLocked(
+            int lastId, @NonNull SparseBooleanArray netIdInUse) {
+        int netId = lastId;
+        for (int i = MIN_NET_ID; i <= MAX_NET_ID; i++) {
+            netId = netId < MAX_NET_ID ? netId + 1 : MIN_NET_ID;
+            if (!netIdInUse.get(netId)) {
+                return netId;
+            }
+        }
+        throw new IllegalStateException("No free netIds");
+    }
+
+    /**
+     * Reserve a new ID for a network.
+     */
+    public int reserveNetId() {
+        synchronized (mNetIdInUse) {
+            mLastNetId = getNextAvailableNetIdLocked(mLastNetId, mNetIdInUse);
+            // Make sure NetID unused.  http://b/16815182
+            mNetIdInUse.put(mLastNetId, true);
+            return mLastNetId;
+        }
+    }
+
+    /**
+     * Clear a previously reserved ID for a network.
+     */
+    public void releaseNetId(int id) {
+        synchronized (mNetIdInUse) {
+            mNetIdInUse.delete(id);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 6dbe3ad..7f19f06 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -34,9 +34,7 @@
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.STATS_PER_UID;
-import static android.net.NetworkStats.TAG_ALL;
 import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
 
 import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
@@ -91,7 +89,6 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.Preconditions;
-import com.android.server.net.NetworkStatsFactory;
 
 import com.google.android.collect.Maps;
 
@@ -165,8 +162,6 @@
     private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
             new RemoteCallbackList<>();
 
-    private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
-
     @GuardedBy("mTetheringStatsProviders")
     private final HashMap<ITetheringStatsProvider, String>
             mTetheringStatsProviders = Maps.newHashMap();
@@ -1008,11 +1003,15 @@
 
     @Override
     public void startTethering(String[] dhcpRange) {
+        startTetheringWithConfiguration(true, dhcpRange);
+    }
+
+    @Override
+    public void startTetheringWithConfiguration(boolean usingLegacyDnsProxy, String[] dhcpRange) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         // an odd number of addrs will fail
-
         try {
-            mNetdService.tetherStart(dhcpRange);
+            mNetdService.tetherStartWithConfiguration(usingLegacyDnsProxy, dhcpRange);
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -1208,36 +1207,6 @@
     }
 
     @Override
-    public NetworkStats getNetworkStatsSummaryDev() {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            return mStatsFactory.readNetworkStatsSummaryDev();
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public NetworkStats getNetworkStatsSummaryXt() {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            return mStatsFactory.readNetworkStatsSummaryXt();
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public NetworkStats getNetworkStatsDetail() {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            return mStatsFactory.readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
     public void setInterfaceQuota(String iface, long quotaBytes) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
@@ -1536,16 +1505,6 @@
         return true;
     }
 
-    @Override
-    public NetworkStats getNetworkStatsUidDetail(int uid, String[] ifaces) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            return mStatsFactory.readNetworkStatsDetail(uid, ifaces, TAG_ALL, null);
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
     private class NetdTetheringStatsProvider extends ITetheringStatsProvider.Stub {
         @Override
         public NetworkStats getTetherStats(int how) {
diff --git a/services/core/java/com/android/server/OldNetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
similarity index 98%
rename from services/core/java/com/android/server/OldNetworkTimeUpdateService.java
rename to services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
index 068b83d..b0b45f4 100644
--- a/services/core/java/com/android/server/OldNetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
@@ -55,7 +55,7 @@
  * available.
  * </p>
  */
-public class OldNetworkTimeUpdateService extends Binder implements NetworkTimeUpdateService {
+public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeUpdateService {
 
     private static final String TAG = "NetworkTimeUpdateService";
     private static final boolean DBG = false;
@@ -98,7 +98,7 @@
     // connection to happen.
     private int mTryAgainCounter;
 
-    public OldNetworkTimeUpdateService(Context context) {
+    public NetworkTimeUpdateServiceImpl(Context context) {
         mContext = context;
         mTime = NtpTrustedTime.getInstance(context);
         mAlarmManager = mContext.getSystemService(AlarmManager.class);
diff --git a/services/core/java/com/android/server/NewNetworkTimeUpdateService.java b/services/core/java/com/android/server/NewNetworkTimeUpdateService.java
deleted file mode 100644
index d21741a..0000000
--- a/services/core/java/com/android/server/NewNetworkTimeUpdateService.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.util.Log;
-import android.util.NtpTrustedTime;
-import android.util.TimeUtils;
-
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.DumpUtils;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Monitors the network time and updates the system time if it is out of sync
- * and there hasn't been any NITZ update from the carrier recently.
- * If looking up the network time fails for some reason, it tries a few times with a short
- * interval and then resets to checking on longer intervals.
- * <p>
- * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't
- * available.
- * </p>
- */
-public class NewNetworkTimeUpdateService extends Binder implements NetworkTimeUpdateService {
-
-    private static final String TAG = "NetworkTimeUpdateService";
-    private static final boolean DBG = false;
-
-    private static final int EVENT_AUTO_TIME_CHANGED = 1;
-    private static final int EVENT_POLL_NETWORK_TIME = 2;
-    private static final int EVENT_NETWORK_CHANGED = 3;
-
-    private static final String ACTION_POLL =
-            "com.android.server.NetworkTimeUpdateService.action.POLL";
-
-    private static final int POLL_REQUEST = 0;
-
-    private static final long NOT_SET = -1;
-    private long mNitzTimeSetTime = NOT_SET;
-    private Network mDefaultNetwork = null;
-
-    private final Context mContext;
-    private final NtpTrustedTime mTime;
-    private final AlarmManager mAlarmManager;
-    private final ConnectivityManager mCM;
-    private final PendingIntent mPendingPollIntent;
-    private final PowerManager.WakeLock mWakeLock;
-
-    // NTP lookup is done on this thread and handler
-    private Handler mHandler;
-    private SettingsObserver mSettingsObserver;
-    private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback;
-
-    // Normal polling frequency
-    private final long mPollingIntervalMs;
-    // Try-again polling interval, in case the network request failed
-    private final long mPollingIntervalShorterMs;
-    // Number of times to try again
-    private final int mTryAgainTimesMax;
-    // If the time difference is greater than this threshold, then update the time.
-    private final int mTimeErrorThresholdMs;
-    // Keeps track of how many quick attempts were made to fetch NTP time.
-    // During bootup, the network may not have been up yet, or it's taking time for the
-    // connection to happen.
-    private int mTryAgainCounter;
-
-    public NewNetworkTimeUpdateService(Context context) {
-        mContext = context;
-        mTime = NtpTrustedTime.getInstance(context);
-        mAlarmManager = mContext.getSystemService(AlarmManager.class);
-        mCM = mContext.getSystemService(ConnectivityManager.class);
-
-        Intent pollIntent = new Intent(ACTION_POLL, null);
-        mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
-
-        mPollingIntervalMs = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_ntpPollingInterval);
-        mPollingIntervalShorterMs = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_ntpPollingIntervalShorter);
-        mTryAgainTimesMax = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_ntpRetry);
-        mTimeErrorThresholdMs = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_ntpThreshold);
-
-        mWakeLock = context.getSystemService(PowerManager.class).newWakeLock(
-                PowerManager.PARTIAL_WAKE_LOCK, TAG);
-    }
-
-    @Override
-    public void systemRunning() {
-        registerForTelephonyIntents();
-        registerForAlarms();
-
-        HandlerThread thread = new HandlerThread(TAG);
-        thread.start();
-        mHandler = new MyHandler(thread.getLooper());
-        mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback();
-        mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler);
-
-        mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
-        mSettingsObserver.observe(mContext);
-    }
-
-    private void registerForTelephonyIntents() {
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
-        mContext.registerReceiver(mNitzReceiver, intentFilter);
-    }
-
-    private void registerForAlarms() {
-        mContext.registerReceiver(
-            new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
-                }
-            }, new IntentFilter(ACTION_POLL));
-    }
-
-    private void onPollNetworkTime(int event) {
-        // If Automatic time is not set, don't bother. Similarly, if we don't
-        // have any default network, don't bother.
-        if (mDefaultNetwork == null) return;
-        mWakeLock.acquire();
-        try {
-            onPollNetworkTimeUnderWakeLock(event);
-        } finally {
-            mWakeLock.release();
-        }
-    }
-
-    private void onPollNetworkTimeUnderWakeLock(int event) {
-        // Force an NTP fix when outdated
-        if (mTime.getCacheAge() >= mPollingIntervalMs) {
-            if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
-            mTime.forceRefresh();
-        }
-
-        if (mTime.getCacheAge() < mPollingIntervalMs) {
-            // Obtained fresh fix; schedule next normal update
-            resetAlarm(mPollingIntervalMs);
-            if (isAutomaticTimeRequested()) {
-                updateSystemClock(event);
-            }
-
-        } else {
-            // No fresh fix; schedule retry
-            mTryAgainCounter++;
-            if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
-                resetAlarm(mPollingIntervalShorterMs);
-            } else {
-                // Try much later
-                mTryAgainCounter = 0;
-                resetAlarm(mPollingIntervalMs);
-            }
-        }
-    }
-
-    private long getNitzAge() {
-        if (mNitzTimeSetTime == NOT_SET) {
-            return Long.MAX_VALUE;
-        } else {
-            return SystemClock.elapsedRealtime() - mNitzTimeSetTime;
-        }
-    }
-
-    /**
-     * Consider updating system clock based on current NTP fix, if requested by
-     * user, significant enough delta, and we don't have a recent NITZ.
-     */
-    private void updateSystemClock(int event) {
-        final boolean forceUpdate = (event == EVENT_AUTO_TIME_CHANGED);
-        if (!forceUpdate) {
-            if (getNitzAge() < mPollingIntervalMs) {
-                if (DBG) Log.d(TAG, "Ignoring NTP update due to recent NITZ");
-                return;
-            }
-
-            final long skew = Math.abs(mTime.currentTimeMillis() - System.currentTimeMillis());
-            if (skew < mTimeErrorThresholdMs) {
-                if (DBG) Log.d(TAG, "Ignoring NTP update due to low skew");
-                return;
-            }
-        }
-
-        SystemClock.setCurrentTimeMillis(mTime.currentTimeMillis());
-    }
-
-    /**
-     * Cancel old alarm and starts a new one for the specified interval.
-     *
-     * @param interval when to trigger the alarm, starting from now.
-     */
-    private void resetAlarm(long interval) {
-        mAlarmManager.cancel(mPendingPollIntent);
-        long now = SystemClock.elapsedRealtime();
-        long next = now + interval;
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
-    }
-
-    /**
-     * Checks if the user prefers to automatically set the time.
-     */
-    private boolean isAutomaticTimeRequested() {
-        return Settings.Global.getInt(
-                mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0) != 0;
-    }
-
-    /** Receiver for Nitz time events */
-    private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (DBG) Log.d(TAG, "Received " + action);
-            if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
-                mNitzTimeSetTime = SystemClock.elapsedRealtime();
-            }
-        }
-    };
-
-    /** Handler to do the network accesses on */
-    private class MyHandler extends Handler {
-
-        public MyHandler(Looper l) {
-            super(l);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case EVENT_AUTO_TIME_CHANGED:
-                case EVENT_POLL_NETWORK_TIME:
-                case EVENT_NETWORK_CHANGED:
-                    onPollNetworkTime(msg.what);
-                    break;
-            }
-        }
-    }
-
-    private class NetworkTimeUpdateCallback extends NetworkCallback {
-        @Override
-        public void onAvailable(Network network) {
-            Log.d(TAG, String.format("New default network %s; checking time.", network));
-            mDefaultNetwork = network;
-            // Running on mHandler so invoke directly.
-            onPollNetworkTime(EVENT_NETWORK_CHANGED);
-        }
-
-        @Override
-        public void onLost(Network network) {
-            if (network.equals(mDefaultNetwork)) mDefaultNetwork = null;
-        }
-    }
-
-    /** Observer to watch for changes to the AUTO_TIME setting */
-    private static class SettingsObserver extends ContentObserver {
-
-        private int mMsg;
-        private Handler mHandler;
-
-        SettingsObserver(Handler handler, int msg) {
-            super(handler);
-            mHandler = handler;
-            mMsg = msg;
-        }
-
-        void observe(Context context) {
-            ContentResolver resolver = context.getContentResolver();
-            resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
-                    false, this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            mHandler.obtainMessage(mMsg).sendToTarget();
-        }
-    }
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-        pw.print("PollingIntervalMs: ");
-        TimeUtils.formatDuration(mPollingIntervalMs, pw);
-        pw.print("\nPollingIntervalShorterMs: ");
-        TimeUtils.formatDuration(mPollingIntervalShorterMs, pw);
-        pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax);
-        pw.print("TimeErrorThresholdMs: ");
-        TimeUtils.formatDuration(mTimeErrorThresholdMs, pw);
-        pw.println("\nTryAgainCounter: " + mTryAgainCounter);
-        pw.println("NTP cache age: " + mTime.getCacheAge());
-        pw.println("NTP cache certainty: " + mTime.getCacheCertainty());
-        pw.println();
-    }
-}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 371e517..d46758c 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1452,10 +1452,11 @@
     }
 
     private void start() {
-        connect();
+        connectStoraged();
+        connectVold();
     }
 
-    private void connect() {
+    private void connectStoraged() {
         IBinder binder = ServiceManager.getService("storaged");
         if (binder != null) {
             try {
@@ -1464,7 +1465,7 @@
                     public void binderDied() {
                         Slog.w(TAG, "storaged died; reconnecting");
                         mStoraged = null;
-                        connect();
+                        connectStoraged();
                     }
                 }, 0);
             } catch (RemoteException e) {
@@ -1478,7 +1479,17 @@
             Slog.w(TAG, "storaged not found; trying again");
         }
 
-        binder = ServiceManager.getService("vold");
+        if (mStoraged == null) {
+            BackgroundThread.getHandler().postDelayed(() -> {
+                connectStoraged();
+            }, DateUtils.SECOND_IN_MILLIS);
+        } else {
+            onDaemonConnected();
+        }
+    }
+
+    private void connectVold() {
+        IBinder binder = ServiceManager.getService("vold");
         if (binder != null) {
             try {
                 binder.linkToDeath(new DeathRecipient() {
@@ -1486,7 +1497,7 @@
                     public void binderDied() {
                         Slog.w(TAG, "vold died; reconnecting");
                         mVold = null;
-                        connect();
+                        connectVold();
                     }
                 }, 0);
             } catch (RemoteException e) {
@@ -1506,9 +1517,9 @@
             Slog.w(TAG, "vold not found; trying again");
         }
 
-        if (mStoraged == null || mVold == null) {
+        if (mVold == null) {
             BackgroundThread.getHandler().postDelayed(() -> {
-                connect();
+                connectVold();
             }, DateUtils.SECOND_IN_MILLIS);
         } else {
             onDaemonConnected();
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 99365de..52ec0ad 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1027,7 +1027,12 @@
                 log(str);
             }
             mLocalLog.log(str);
-            if (validatePhoneId(phoneId)) {
+            // for service state updates, don't notify clients when subId is invalid. This prevents
+            // us from sending incorrect notifications like b/133140128
+            // In the future, we can remove this logic for every notification here and add a
+            // callback so listeners know when their PhoneStateListener's subId becomes invalid, but
+            // for now we use the simplest fix.
+            if (validatePhoneId(phoneId) && SubscriptionManager.isValidSubscriptionId(subId)) {
                 mServiceState[phoneId] = state;
 
                 for (Record r : mRecords) {
@@ -1059,7 +1064,8 @@
                     }
                 }
             } else {
-                log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId);
+                log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId
+                        + " or subId=" + subId);
             }
             handleRemoveListLocked();
         }
@@ -1315,12 +1321,12 @@
             return;
         }
         if (VDBG) {
-            log("notifyUserMobileDataStateChangedForSubscriberPhoneID: subId=" + phoneId
-                    + " state=" + state);
+            log("notifyUserMobileDataStateChangedForSubscriberPhoneID: PhoneId=" + phoneId
+                    + " subId=" + subId + " state=" + state);
         }
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
-                mMessageWaiting[phoneId] = state;
+                mUserMobileDataState[phoneId] = state;
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
                             PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) &&
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index f1ab0be..a909843 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -91,16 +91,17 @@
     };
 
     public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
-        "android.hardware.audio@2.0::IDevicesFactory",
-        "android.hardware.audio@4.0::IDevicesFactory",
-        "android.hardware.bluetooth@1.0::IBluetoothHci",
-        "android.hardware.camera.provider@2.4::ICameraProvider",
-        "android.hardware.graphics.composer@2.1::IComposer",
-        "android.hardware.health@2.0::IHealth",
-        "android.hardware.media.omx@1.0::IOmx",
-        "android.hardware.media.omx@1.0::IOmxStore",
-        "android.hardware.sensors@1.0::ISensors",
-        "android.hardware.vr@1.0::IVr"
+            "android.hardware.audio@2.0::IDevicesFactory",
+            "android.hardware.audio@4.0::IDevicesFactory",
+            "android.hardware.bluetooth@1.0::IBluetoothHci",
+            "android.hardware.camera.provider@2.4::ICameraProvider",
+            "android.hardware.graphics.composer@2.1::IComposer",
+            "android.hardware.health@2.0::IHealth",
+            "android.hardware.media.omx@1.0::IOmx",
+            "android.hardware.media.omx@1.0::IOmxStore",
+            "android.hardware.sensors@1.0::ISensors",
+            "android.hardware.vr@1.0::IVr",
+            "android.system.suspend@1.0::ISystemSuspend"
     );
 
     static Watchdog sWatchdog;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5ebd173..e3cb5ad 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -452,20 +452,10 @@
 import com.android.server.SystemServiceManager;
 import com.android.server.ThreadPriorityBooster;
 import com.android.server.Watchdog;
-import com.android.server.am.ActivityManagerServiceDumpActivitiesProto;
-import com.android.server.am.ActivityManagerServiceDumpBroadcastsProto;
-import com.android.server.am.ActivityManagerServiceDumpProcessesProto;
 import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
-import com.android.server.am.ActivityManagerServiceDumpServicesProto;
 import com.android.server.am.ActivityStack.ActivityState;
-import com.android.server.am.GrantUriProto;
-import com.android.server.am.ImportanceTokenProto;
-import com.android.server.am.MemInfoDumpProto;
 import com.android.server.am.MemoryStatUtil.MemoryStat;
-import com.android.server.am.NeededUriGrantsProto;
-import com.android.server.am.ProcessOomProto;
-import com.android.server.am.ProcessToGcProto;
-import com.android.server.am.StickyBroadcastProto;
+import com.android.server.compat.CompatConfig;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.pm.Installer;
@@ -478,12 +468,12 @@
 
 import dalvik.system.VMRuntime;
 
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
 
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -4212,6 +4202,7 @@
             }
             checkTime(startTime, "startProcess: done removing from pids map");
             app.setPid(0);
+            app.startSeq = 0;
         }
 
         if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
@@ -4309,6 +4300,9 @@
             if ("1".equals(SystemProperties.get("debug.assert"))) {
                 runtimeFlags |= Zygote.DEBUG_ENABLE_ASSERT;
             }
+            if ("1".equals(SystemProperties.get("debug.ignoreappsignalhandler"))) {
+                runtimeFlags |= Zygote.DEBUG_IGNORE_APP_SIGNAL_HANDLER;
+            }
             if (mNativeDebuggingApp != null && mNativeDebuggingApp.equals(app.processName)) {
                 // Enable all debug flags required by the native debugger.
                 runtimeFlags |= Zygote.DEBUG_ALWAYS_JIT;          // Don't interpret anything
@@ -4400,6 +4394,14 @@
         app.killedByAm = false;
         app.removed = false;
         app.killed = false;
+        if (app.startSeq != 0) {
+            Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
+                    + " with non-zero startSeq:" + app.startSeq);
+        }
+        if (app.pid != 0) {
+            Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
+                    + " with non-zero pid:" + app.pid);
+        }
         final long startSeq = app.startSeq = ++mProcStartSeqCounter;
         app.setStartParams(uid, hostingType, hostingNameStr, seInfo, startTime);
         if (mConstants.FLAG_PROCESS_START_ASYNC) {
@@ -4585,8 +4587,11 @@
         // If there is already an app occupying that pid that hasn't been cleaned up
         if (oldApp != null && !app.isolated) {
             // Clean up anything relating to this pid first
-            Slog.w(TAG, "Reusing pid " + pid
-                    + " while app is still mapped to it");
+          Slog.wtf(TAG, "handleProcessStartedLocked process:" + app.processName
+                  + " startSeq:" + app.startSeq
+                  + " pid:" + pid
+                  + " belongs to another existing app:" + oldApp.processName
+                  + " startSeq:" + oldApp.startSeq);
             cleanUpApplicationRecordLocked(oldApp, false, false, -1,
                     true /*replacingPid*/);
         }
@@ -7442,6 +7447,26 @@
             synchronized (mPidsSelfLocked) {
                 app = mPidsSelfLocked.get(pid);
             }
+            if (app != null && (app.startUid != callingUid || app.startSeq != startSeq)) {
+                String processName = null;
+                final ProcessRecord pending = mPendingStarts.get(startSeq);
+                if (pending != null) {
+                    processName = pending.processName;
+                }
+                final String msg = "attachApplicationLocked process:" + processName
+                      + " startSeq:" + startSeq
+                      + " pid:" + pid
+                      + " belongs to another existing app:" + app.processName
+                      + " startSeq:" + app.startSeq;
+                Slog.wtf(TAG, msg);
+                // SafetyNet logging for b/131105245.
+                EventLog.writeEvent(0x534e4554, "131105245", app.startUid, msg);
+                // If there is already an app occupying that pid that hasn't been cleaned up
+                cleanUpApplicationRecordLocked(app, false, false, -1,
+                    true /*replacingPid*/);
+                mPidsSelfLocked.remove(pid);
+                app = null;
+            }
         } else {
             app = null;
         }
@@ -7450,7 +7475,7 @@
         // update the internal state.
         if (app == null && startSeq > 0) {
             final ProcessRecord pending = mPendingStarts.get(startSeq);
-            if (pending != null && pending.startUid == callingUid
+            if (pending != null && pending.startUid == callingUid && pending.startSeq == startSeq
                     && handleProcessStartedLocked(pending, pid, pending.usingWrapper,
                             startSeq, true)) {
                 app = pending;
@@ -7670,6 +7695,7 @@
             checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");
             bindApplicationTimeMillis = SystemClock.elapsedRealtime();
             mStackSupervisor.getActivityMetricsLogger().notifyBindApplication(app);
+            long[] disabledCompatChanges = CompatConfig.get().getDisabledChanges(app.info);
             if (app.isolatedEntryPoint != null) {
                 // This is an isolated process which should just call an entry point instead of
                 // being bound to an application.
@@ -7685,7 +7711,7 @@
                         new Configuration(getGlobalConfiguration()), app.compat,
                         getCommonServicesLocked(app.isolated),
                         mCoreSettingsObserver.getCoreSettingsLocked(),
-                        buildSerial, isAutofillCompatEnabled);
+                        buildSerial, isAutofillCompatEnabled, disabledCompatChanges);
             } else {
                 thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                         null, null, null, testMode,
@@ -7694,7 +7720,7 @@
                         new Configuration(getGlobalConfiguration()), app.compat,
                         getCommonServicesLocked(app.isolated),
                         mCoreSettingsObserver.getCoreSettingsLocked(),
-                        buildSerial, isAutofillCompatEnabled);
+                        buildSerial, isAutofillCompatEnabled, disabledCompatChanges);
             }
             if (profilerInfo != null) {
                 profilerInfo.closeFd();
@@ -7904,6 +7930,10 @@
             }
         }
 
+        // Let the ART runtime in zygote and system_server know that the boot completed.
+        ZYGOTE_PROCESS.bootCompleted();
+        VMRuntime.bootCompleted();
+
         IntentFilter pkgFilter = new IntentFilter();
         pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
         pkgFilter.addDataScheme("package");
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 3399a76..6596cff 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -16,6 +16,15 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
+import static android.app.ActivityManager.RESIZE_MODE_USER;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
@@ -74,6 +83,7 @@
 import com.android.internal.util.HexDump;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.Preconditions;
+import com.android.server.compat.CompatConfig;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -96,15 +106,6 @@
 import javax.microedition.khronos.egl.EGLDisplay;
 import javax.microedition.khronos.egl.EGLSurface;
 
-import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
-import static android.app.ActivityManager.RESIZE_MODE_USER;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.Display.INVALID_DISPLAY;
-
-import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
-
 final class ActivityManagerShellCommand extends ShellCommand {
     public static final String NO_CLASS_ERROR_CODE = "Error type 3";
     private static final String SHELL_PACKAGE_NAME = "com.android.shell";
@@ -277,6 +278,8 @@
                     return runNoHomeScreen(pw);
                 case "wait-for-broadcast-idle":
                     return runWaitForBroadcastIdle(pw);
+                case "compat":
+                    return runCompat(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -2794,6 +2797,50 @@
         return 0;
     }
 
+    private int runCompat(PrintWriter pw) {
+        final CompatConfig config = CompatConfig.get();
+        String toggleValue = getNextArgRequired();
+        long changeId;
+        String changeIdString = getNextArgRequired();
+        try {
+            changeId = Long.parseLong(changeIdString);
+        } catch (NumberFormatException e) {
+            changeId = config.lookupChangeId(changeIdString);
+        }
+        if (changeId == -1) {
+            pw.println("Unknown or invalid change: '" + changeIdString + "'.");
+        }
+        String packageName = getNextArgRequired();
+        switch(toggleValue) {
+            case "enable":
+                if (!config.addOverride(changeId, packageName, true)) {
+                    pw.println("Warning! Change " + changeId + " is not known yet. Enabling it"
+                            + " could have no effect.");
+                }
+                pw.println("Enabled change " + changeId + " for " + packageName + ".");
+                return 0;
+            case "disable":
+                if (!config.addOverride(changeId, packageName, false)) {
+                    pw.println("Warning! Change " + changeId + " is not known yet. Disabling it"
+                            + " could have no effect.");
+                }
+                pw.println("Disabled change " + changeId + " for " + packageName + ".");
+                return 0;
+            case "reset":
+                if (config.removeOverride(changeId, packageName)) {
+                    pw.println("Reset change " + changeId + " for " + packageName
+                            + " to default value.");
+                } else {
+                    pw.println("No override exists for changeId " + changeId + ".");
+                }
+                return 0;
+            default:
+                pw.println("Invalid toggle value: '" + toggleValue + "'.");
+        }
+        return -1;
+    }
+
+
     private Resources getResources(PrintWriter pw) throws RemoteException {
         // system resources does not contain all the device configuration, construct it manually.
         Configuration config = mInterface.getConfiguration();
@@ -3090,6 +3137,8 @@
             pw.println("      without restarting any processes.");
             pw.println("  write");
             pw.println("      Write all pending state to storage.");
+            pw.println("  compat enable|disable|reset <CHANGE_ID|CHANGE_NAME> <PACKAGE_NAME>");
+            pw.println("      Toggles a change either by id or by name for <PACKAGE_NAME>.");
             pw.println();
             Intent.printIntentArgsHelp(pw, "");
         }
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index 151ef49..bd0506b 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -777,18 +777,24 @@
      * leaves the pinned mode.
      */
     private void lockKeyguardIfNeeded() {
+        if (shouldLockKeyguard()) {
+            mWindowManager.lockNow(null);
+            mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
+            getLockPatternUtils().requireCredentialEntry(USER_ALL);
+        }
+    }
+
+    private boolean shouldLockKeyguard() {
+        // This functionality should be kept consistent with
+        // com.android.settings.security.ScreenPinningSettings (see b/127605586)
         try {
-            boolean shouldLockKeyguard = Settings.Secure.getIntForUser(
+            return Settings.Secure.getIntForUser(
                     mContext.getContentResolver(),
-                    Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
-                    USER_CURRENT) != 0;
-            if (shouldLockKeyguard) {
-                mWindowManager.lockNow(null);
-                mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
-                getLockPatternUtils().requireCredentialEntry(USER_ALL);
-            }
+                    Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, USER_CURRENT) != 0;
         } catch (Settings.SettingNotFoundException e) {
-            // No setting, don't lock.
+            // Log to SafetyNet for b/127605586
+            android.util.EventLog.writeEvent(0x534e4554, "127605586", -1, "");
+            return getLockPatternUtils().isSecure(USER_CURRENT);
         }
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e698b84..a83b337 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -363,7 +363,7 @@
         AudioSystem.STREAM_MUSIC,       // STREAM_MUSIC
         AudioSystem.STREAM_MUSIC,       // STREAM_ALARM
         AudioSystem.STREAM_MUSIC,       // STREAM_NOTIFICATION
-        AudioSystem.STREAM_MUSIC,       // STREAM_BLUETOOTH_SCO
+        AudioSystem.STREAM_BLUETOOTH_SCO,       // STREAM_BLUETOOTH_SCO
         AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM_ENFORCED
         AudioSystem.STREAM_MUSIC,       // STREAM_DTMF
         AudioSystem.STREAM_MUSIC,       // STREAM_TTS
@@ -2520,8 +2520,9 @@
             AudioSystem.muteMicrophone(on);
             Binder.restoreCallingIdentity(identity);
             if (on != currentMute) {
-                mContext.sendBroadcast(new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED)
-                        .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY));
+                mContext.sendBroadcastAsUser(
+                        new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED)
+                                .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL);
             }
         }
     }
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
new file mode 100644
index 0000000..bc5973d
--- /dev/null
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.compat;
+
+import android.annotation.Nullable;
+import android.compat.annotation.EnabledAfter;
+import android.content.pm.ApplicationInfo;
+
+import com.android.server.compat.config.Change;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents the state of a single compatibility change.
+ *
+ * <p>A compatibility change has a default setting, determined by the {@code enableAfterTargetSdk}
+ * and {@code disabled} constructor parameters. If a change is {@code disabled}, this overrides any
+ * target SDK criteria set. These settings can be overridden for a specific package using
+ * {@link #addPackageOverride(String, boolean)}.
+ *
+ * <p>Note, this class is not thread safe so callers must ensure thread safety.
+ */
+public final class CompatChange {
+
+    private final long mChangeId;
+    @Nullable private final String mName;
+    private final int mEnableAfterTargetSdk;
+    private final boolean mDisabled;
+    private Map<String, Boolean> mPackageOverrides;
+
+    public CompatChange(long changeId) {
+        this(changeId, null, -1, false);
+    }
+
+    /**
+     * @param changeId Unique ID for the change. See {@link android.compat.Compatibility}.
+     * @param name Short descriptive name.
+     * @param enableAfterTargetSdk {@code targetSdkVersion} restriction. See {@link EnabledAfter};
+     *                             -1 if the change is always enabled.
+     * @param disabled If {@code true}, overrides any {@code enableAfterTargetSdk} set.
+     */
+    public CompatChange(long changeId, @Nullable String name, int enableAfterTargetSdk,
+            boolean disabled) {
+        mChangeId = changeId;
+        mName = name;
+        mEnableAfterTargetSdk = enableAfterTargetSdk;
+        mDisabled = disabled;
+    }
+
+    /**
+     * @param change an object generated by services/core/xsd/platform-compat-config.xsd
+     */
+    public CompatChange(Change change) {
+        mChangeId = change.getId();
+        mName = change.getName();
+        mEnableAfterTargetSdk = change.getEnableAfterTargetSdk();
+        mDisabled = change.getDisabled();
+    }
+
+    long getId() {
+        return mChangeId;
+    }
+
+    @Nullable
+    String getName() {
+        return mName;
+    }
+
+    /**
+     * Force the enabled state of this change for a given package name. The change will only take
+     * effect after that packages process is killed and restarted.
+     *
+     * <p>Note, this method is not thread safe so callers must ensure thread safety.
+     *
+     * @param pname Package name to enable the change for.
+     * @param enabled Whether or not to enable the change.
+     */
+    void addPackageOverride(String pname, boolean enabled) {
+        if (mPackageOverrides == null) {
+            mPackageOverrides = new HashMap<>();
+        }
+        mPackageOverrides.put(pname, enabled);
+    }
+
+    /**
+     * Remove any package override for the given package name, restoring the default behaviour.
+     *
+     * <p>Note, this method is not thread safe so callers must ensure thread safety.
+     *
+     * @param pname Package name to reset to defaults for.
+     */
+    void removePackageOverride(String pname) {
+        if (mPackageOverrides != null) {
+            mPackageOverrides.remove(pname);
+        }
+    }
+
+    /**
+     * Find if this change is enabled for the given package, taking into account any overrides that
+     * exist.
+     *
+     * @param app Info about the app in question
+     * @return {@code true} if the change should be enabled for the package.
+     */
+    boolean isEnabled(ApplicationInfo app) {
+        if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) {
+            return mPackageOverrides.get(app.packageName);
+        }
+        if (mDisabled) {
+            return false;
+        }
+        if (mEnableAfterTargetSdk != -1) {
+            return app.targetSdkVersion > mEnableAfterTargetSdk;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("ChangeId(")
+                .append(mChangeId);
+        if (mName != null) {
+            sb.append("; name=").append(mName);
+        }
+        if (mEnableAfterTargetSdk != -1) {
+            sb.append("; enableAfterTargetSdk=").append(mEnableAfterTargetSdk);
+        }
+        if (mDisabled) {
+            sb.append("; disabled");
+        }
+        if (mPackageOverrides != null && mPackageOverrides.size() > 0) {
+            sb.append("; packageOverrides=").append(mPackageOverrides);
+        }
+        return sb.append(")").toString();
+    }
+}
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
new file mode 100644
index 0000000..027e2fb
--- /dev/null
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.compat;
+
+import android.content.pm.ApplicationInfo;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.LongArray;
+import android.util.LongSparseArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.compat.config.Change;
+import com.android.server.compat.config.XmlParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+/**
+ * This class maintains state relating to platform compatibility changes.
+ *
+ * <p>It stores the default configuration for each change, and any per-package overrides that have
+ * been configured.
+ */
+public final class CompatConfig {
+
+    private static final String TAG = "CompatConfig";
+
+    private static final CompatConfig sInstance = new CompatConfig().initConfigFromLib(
+            Environment.buildPath(
+                    Environment.getRootDirectory(), "etc", "compatconfig"));
+
+    @GuardedBy("mChanges")
+    private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
+
+    @VisibleForTesting
+    public CompatConfig() {
+    }
+
+    /**
+     * @return The static instance of this class to be used within the system server.
+     */
+    public static CompatConfig get() {
+        return sInstance;
+    }
+
+    /**
+     * Add a change. This is intended to be used by code that reads change config from the
+     * filesystem. This should be done at system startup time.
+     *
+     * @param change The change to add. Any change with the same ID will be overwritten.
+     */
+    public void addChange(CompatChange change) {
+        synchronized (mChanges) {
+            mChanges.put(change.getId(), change);
+        }
+    }
+
+    /**
+     * Retrieves the set of disabled changes for a given app. Any change ID not in the returned
+     * array is by default enabled for the app.
+     *
+     * @param app The app in question
+     * @return A sorted long array of change IDs. We use a primitive array to minimize memory
+     *      footprint: Every app process will store this array statically so we aim to reduce
+     *      overhead as much as possible.
+     */
+    public long[] getDisabledChanges(ApplicationInfo app) {
+        LongArray disabled = new LongArray();
+        synchronized (mChanges) {
+            for (int i = 0; i < mChanges.size(); ++i) {
+                CompatChange c = mChanges.valueAt(i);
+                if (!c.isEnabled(app)) {
+                    disabled.add(c.getId());
+                }
+            }
+        }
+        // Note: we don't need to explicitly sort the array, as the behaviour of LongSparseArray
+        // (mChanges) ensures it's already sorted.
+        return disabled.toArray();
+    }
+
+    /**
+     * Look up a change ID by name.
+     *
+     * @param name Name of the change to look up
+     * @return The change ID, or {@code -1} if no change with that name exists.
+     */
+    public long lookupChangeId(String name) {
+        synchronized (mChanges) {
+            for (int i = 0; i < mChanges.size(); ++i) {
+                if (TextUtils.equals(mChanges.valueAt(i).getName(), name)) {
+                    return mChanges.keyAt(i);
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Find if a given change is enabled for a given application.
+     *
+     * @param changeId The ID of the change in question
+     * @param app App to check for
+     * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the
+     *      change ID is not known, as unknown changes are enabled by default.
+     */
+    public boolean isChangeEnabled(long changeId, ApplicationInfo app) {
+        synchronized (mChanges) {
+            CompatChange c = mChanges.get(changeId);
+            if (c == null) {
+                // we know nothing about this change: default behaviour is enabled.
+                return true;
+            }
+            return c.isEnabled(app);
+        }
+    }
+
+    /**
+     * Overrides the enabled state for a given change and app. This method is intended to be used
+     * *only* for debugging purposes, ultimately invoked either by an adb command, or from some
+     * developer settings UI.
+     *
+     * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
+     *
+     * @param changeId The ID of the change to be overridden. Note, this call will succeed even if
+     *                 this change is not known; it will only have any effect if any code in the
+     *                 platform is gated on the ID given.
+     * @param packageName The app package name to override the change for.
+     * @param enabled If the change should be enabled or disabled.
+     * @return {@code true} if the change existed before adding the override.
+     */
+    public boolean addOverride(long changeId, String packageName, boolean enabled) {
+        boolean alreadyKnown = true;
+        synchronized (mChanges) {
+            CompatChange c = mChanges.get(changeId);
+            if (c == null) {
+                alreadyKnown = false;
+                c = new CompatChange(changeId);
+                addChange(c);
+            }
+            c.addPackageOverride(packageName, enabled);
+        }
+        return alreadyKnown;
+    }
+
+    /**
+     * Removes an override previously added via {@link #addOverride(long, String, boolean)}. This
+     * restores the default behaviour for the given change and app, once any app processes have been
+     * restarted.
+     *
+     * @param changeId The ID of the change that was overridden.
+     * @param packageName The app package name that was overridden.
+     * @return {@code true} if an override existed;
+     */
+    public boolean removeOverride(long changeId, String packageName) {
+        boolean overrideExists = false;
+        synchronized (mChanges) {
+            CompatChange c = mChanges.get(changeId);
+            if (c != null) {
+                overrideExists = true;
+                c.removePackageOverride(packageName);
+            }
+        }
+        return overrideExists;
+    }
+
+    /**
+    * Dumps the current list of compatibility config information.
+    *
+    * @param pw The {@link PrintWriter} instance to which the information will be dumped.
+    */
+    public void dumpConfig(PrintWriter pw) {
+        synchronized (mChanges) {
+            if (mChanges.size() == 0) {
+                pw.println("No compat overrides.");
+                return;
+            }
+            for (int i = 0; i < mChanges.size(); ++i) {
+                CompatChange c = mChanges.valueAt(i);
+                pw.println(c.toString());
+            }
+        }
+    }
+
+    CompatConfig initConfigFromLib(File libraryDir) {
+        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
+            Slog.e(TAG, "No directory " + libraryDir + ", skipping");
+            return this;
+        }
+        for (File f : libraryDir.listFiles()) {
+            Slog.d(TAG, "Found a config file: " + f.getPath());
+            //TODO(b/138222363): Handle duplicate ids across config files.
+            readConfig(f);
+        }
+        return this;
+    }
+
+    private void readConfig(File configFile) {
+        try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
+            for (Change change : XmlParser.read(in).getCompatChange()) {
+                Slog.d(TAG, "Adding: " + change.toString());
+                addChange(new CompatChange(change));
+            }
+        } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
+            Slog.e(TAG, "Encountered an error while reading/parsing compat config file", e);
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
new file mode 100644
index 0000000..fc38735
--- /dev/null
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.compat;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.util.Slog;
+
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.util.DumpUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * System server internal API for gating and reporting compatibility changes.
+ */
+public class PlatformCompat extends IPlatformCompat.Stub {
+
+    private static final String TAG = "Compatibility";
+
+    private final Context mContext;
+
+    public PlatformCompat(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void reportChange(long changeId, ApplicationInfo appInfo) {
+        Slog.d(TAG, "Compat change reported: " + changeId + "; UID " + appInfo.uid);
+        // TODO log via StatsLog
+    }
+
+    @Override
+    public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
+        if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
+            reportChange(changeId, appInfo);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
+        CompatConfig.get().dumpConfig(pw);
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/AutodestructReference.java b/services/core/java/com/android/server/connectivity/AutodestructReference.java
new file mode 100644
index 0000000..009a43e
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/AutodestructReference.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.annotation.NonNull;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A ref that autodestructs at the first usage of it.
+ * @param <T> The type of the held object
+ * @hide
+ */
+public class AutodestructReference<T> {
+    private final AtomicReference<T> mHeld;
+    public AutodestructReference(@NonNull T obj) {
+        if (null == obj) throw new NullPointerException("Autodestruct reference to null");
+        mHeld = new AtomicReference<>(obj);
+    }
+
+    /** Get the ref and destruct it. NPE if already destructed. */
+    @NonNull
+    public T getAndDestroy() {
+        final T obj = mHeld.getAndSet(null);
+        if (null == obj) throw new NullPointerException("Already autodestructed");
+        return obj;
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 227ab23..e6a4428 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -21,6 +21,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
@@ -39,15 +41,19 @@
 
     private final Context mContext;
     private final IBatteryStats mBatteryStats;
+    private final Handler mListenerHandler;
+    private final PhoneStateListener mPhoneStateListener;
 
     private IccCardConstants.State mSimState = IccCardConstants.State.READY;
     private SignalStrength mSignalStrength;
     private ServiceState mServiceState;
     private int mDataState = TelephonyManager.DATA_DISCONNECTED;
 
-    public DataConnectionStats(Context context) {
+    public DataConnectionStats(Context context, Handler listenerHandler) {
         mContext = context;
         mBatteryStats = BatteryStatsService.getService();
+        mListenerHandler = listenerHandler;
+        mPhoneStateListener = new PhoneStateListenerImpl(listenerHandler.getLooper());
     }
 
     public void startMonitoring() {
@@ -63,7 +69,7 @@
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
-        mContext.registerReceiver(this, filter);
+        mContext.registerReceiver(this, filter, null /* broadcastPermission */, mListenerHandler);
     }
 
     @Override
@@ -128,7 +134,11 @@
                 && mServiceState.getState() != ServiceState.STATE_POWER_OFF;
     }
 
-    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+    private class PhoneStateListenerImpl extends PhoneStateListener {
+        PhoneStateListenerImpl(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
             mSignalStrength = signalStrength;
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index e10d737..9bae902 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -216,6 +216,7 @@
 
         public String toString() {
             return "KeepaliveInfo ["
+                    + " type=" + mType
                     + " network=" + mNai.network
                     + " startedState=" + startedStateString(mStartedState)
                     + " "
@@ -561,7 +562,7 @@
         if (KeepaliveInfo.STARTING == ki.mStartedState) {
             if (SUCCESS == reason) {
                 // Keepalive successfully started.
-                if (DBG) Log.d(TAG, "Started keepalive " + slot + " on " + nai.name());
+                Log.d(TAG, "Started keepalive " + slot + " on " + nai.name());
                 ki.mStartedState = KeepaliveInfo.STARTED;
                 try {
                     ki.mCallback.onStarted(slot);
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index e40949b..dbc339b 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -27,21 +27,19 @@
 import android.net.metrics.ConnectStats;
 import android.net.metrics.DnsEvent;
 import android.net.metrics.INetdEventListener;
-import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkMetrics;
 import android.net.metrics.WakeupEvent;
 import android.net.metrics.WakeupStats;
 import android.os.RemoteException;
 import android.text.format.DateUtils;
-import android.util.Log;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.SparseArray;
 import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.BitUtils;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.RingBuffer;
 import com.android.internal.util.TokenBucket;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
@@ -307,6 +305,11 @@
         }
     }
 
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        return this.VERSION;
+    }
+
     private void addWakeupEvent(WakeupEvent event) {
         String iface = event.iface;
         mWakeupEvents.append(event);
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 34772d0..96b7cb3 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.net.IDnsResolver;
 import android.net.INetd;
@@ -25,12 +26,12 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkMisc;
+import android.net.NetworkMonitorManager;
 import android.net.NetworkRequest;
 import android.net.NetworkState;
 import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.Messenger;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
@@ -116,7 +117,7 @@
 // not, ConnectivityService disconnects the NetworkAgent's AsyncChannel.
 public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
 
-    public NetworkInfo networkInfo;
+    @NonNull public NetworkInfo networkInfo;
     // This Network object should always be used if possible, so as to encourage reuse of the
     // enclosed socket factory and connection pool.  Avoid creating other Network objects.
     // This Network object is always valid.
@@ -155,9 +156,9 @@
     // Whether a captive portal was found during the last network validation attempt.
     public boolean lastCaptivePortalDetected;
 
-    // Indicates the user was notified of a successful captive portal login since a portal was
-    // last detected.
-    public boolean captivePortalLoginNotified;
+    // Indicates the captive portal app was opened to show a login UI to the user, but the network
+    // has not validated yet.
+    public volatile boolean captivePortalValidationPending;
 
     // Set to true when partial connectivity was detected.
     public boolean partialConnectivity;
@@ -247,7 +248,7 @@
     public final Nat464Xlat clatd;
 
     // Set after asynchronous creation of the NetworkMonitor.
-    private volatile INetworkMonitor mNetworkMonitor;
+    private volatile NetworkMonitorManager mNetworkMonitor;
 
     private static final String TAG = ConnectivityService.class.getSimpleName();
     private static final boolean VDBG = false;
@@ -278,7 +279,7 @@
      * Inform NetworkAgentInfo that a new NetworkMonitor was created.
      */
     public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
-        mNetworkMonitor = networkMonitor;
+        mNetworkMonitor = new NetworkMonitorManager(networkMonitor);
     }
 
     /**
@@ -290,13 +291,9 @@
      */
     public void setNetworkCapabilities(NetworkCapabilities nc) {
         networkCapabilities = nc;
-        final INetworkMonitor nm = mNetworkMonitor;
+        final NetworkMonitorManager nm = mNetworkMonitor;
         if (nm != null) {
-            try {
-                nm.notifyNetworkCapabilitiesChanged(nc);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error notifying NetworkMonitor of updated NetworkCapabilities", e);
-            }
+            nm.notifyNetworkCapabilitiesChanged(nc);
         }
     }
 
@@ -317,11 +314,11 @@
     }
 
     /**
-     * Get the INetworkMonitor in this NetworkAgentInfo.
+     * Get the NetworkMonitorManager in this NetworkAgentInfo.
      *
      * <p>This will be null before {@link #onNetworkMonitorCreated(INetworkMonitor)} is called.
      */
-    public INetworkMonitor networkMonitor() {
+    public NetworkMonitorManager networkMonitor() {
         return mNetworkMonitor;
     }
 
@@ -583,10 +580,12 @@
         }
 
         if (newExpiry > 0) {
-            mLingerMessage = mConnService.makeWakeupMessage(
+            mLingerMessage = new WakeupMessage(
                     mContext, mHandler,
-                    "NETWORK_LINGER_COMPLETE." + network.netId,
-                    EVENT_NETWORK_LINGER_COMPLETE, this);
+                    "NETWORK_LINGER_COMPLETE." + network.netId /* cmdName */,
+                    EVENT_NETWORK_LINGER_COMPLETE /* cmd */,
+                    0 /* arg1 (unused) */, 0 /* arg2 (unused) */,
+                    this /* obj (NetworkAgentInfo) */);
             mLingerMessage.schedule(newExpiry);
         }
 
@@ -633,7 +632,7 @@
                 + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
                 + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
                 + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
-                + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} "
+                + "captivePortalValidationPending{" + captivePortalValidationPending + "} "
                 + "partialConnectivity{" + partialConnectivity + "} "
                 + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} "
                 + "clat{" + clatd + "} "
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 0910dac2..077c405 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -232,14 +232,25 @@
             title = r.getString(R.string.network_switch_metered, toTransport);
             details = r.getString(R.string.network_switch_metered_detail, toTransport,
                     fromTransport);
+        } else if (notifyType == NotificationType.NO_INTERNET
+                    || notifyType == NotificationType.PARTIAL_CONNECTIVITY) {
+            // NO_INTERNET and PARTIAL_CONNECTIVITY notification for non-WiFi networks
+            // are sent, but they are not implemented yet.
+            return;
         } else {
             Slog.wtf(TAG, "Unknown notification type " + notifyType + " on network transport "
                     + getTransportName(transportType));
             return;
         }
-
-        final String channelId = highPriority ? SystemNotificationChannels.NETWORK_ALERTS :
-                SystemNotificationChannels.NETWORK_STATUS;
+        // When replacing an existing notification for a given network, don't alert, just silently
+        // update the existing notification. Note that setOnlyAlertOnce() will only work for the
+        // same id, and the id used here is the NotificationType which is different in every type of
+        // notification. This is required because the notification metrics only track the ID but not
+        // the tag.
+        final boolean hasPreviousNotification = previousNotifyType != null;
+        final String channelId = (highPriority && !hasPreviousNotification)
+                ? SystemNotificationChannels.NETWORK_ALERTS
+                : SystemNotificationChannels.NETWORK_STATUS;
         Notification.Builder builder = new Notification.Builder(mContext, channelId)
                 .setWhen(System.currentTimeMillis())
                 .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH)
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index d05369e9..29c4bad 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -130,6 +130,11 @@
         }
 
         @Override
+        public void onPackageChanged(@NonNull String packageName, int uid) {
+            sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
+        }
+
+        @Override
         public void onPackageRemoved(String packageName, int uid) {
             sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
         }
@@ -199,15 +204,13 @@
             ArraySet<String> perms = systemPermission.valueAt(i);
             int uid = systemPermission.keyAt(i);
             int netdPermission = 0;
-            // Get the uids of native services that have UPDATE_DEVICE_STATS permission.
+            // Get the uids of native services that have UPDATE_DEVICE_STATS or INTERNET permission.
             if (perms != null) {
                 netdPermission |= perms.contains(UPDATE_DEVICE_STATS)
                         ? INetd.PERMISSION_UPDATE_DEVICE_STATS : 0;
+                netdPermission |= perms.contains(INTERNET)
+                        ? INetd.PERMISSION_INTERNET : 0;
             }
-            // For internet permission, the native services have their own selinux domains and
-            // sepolicy will control the socket creation during run time. netd cannot block the
-            // socket creation based on the permission information here.
-            netdPermission |= INetd.PERMISSION_INTERNET;
             netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
         }
         log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 33c84d1..5fd5c4b 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -44,6 +44,7 @@
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_UNSPECIFIED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static com.android.server.ConnectivityService.SHORT_ARG;
 
@@ -89,6 +90,8 @@
 import android.os.UserManager;
 import android.os.UserManagerInternal;
 import android.os.UserManagerInternal.UserRestrictionsListener;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -97,7 +100,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.MessageUtils;
@@ -182,12 +184,13 @@
     // into a single coherent structure.
     private final HashSet<IpServer> mForwardedDownstreams;
     private final VersionedBroadcastListener mCarrierConfigChange;
-    private final VersionedBroadcastListener mDefaultSubscriptionChange;
     private final TetheringDependencies mDeps;
     private final EntitlementManager mEntitlementMgr;
     private final Handler mHandler;
     private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
             new RemoteCallbackList<>();
+    private final PhoneStateListener mPhoneStateListener;
+    private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
 
     private volatile TetheringConfiguration mConfig;
     private InterfaceSet mCurrentUpstreamIfaceSet;
@@ -238,7 +241,6 @@
             stopTethering(downstream);
         });
         mEntitlementMgr.setTetheringConfigurationFetcher(() -> {
-            maybeDefaultDataSubChanged();
             return mConfig;
         });
 
@@ -250,22 +252,26 @@
                     mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
                 });
 
-        filter = new IntentFilter();
-        filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
-        mDefaultSubscriptionChange = new VersionedBroadcastListener(
-                "DefaultSubscriptionChangeListener", mContext, mHandler, filter,
-                (Intent ignored) -> {
-                    mLog.log("OBSERVED default data subscription change");
-                    maybeDefaultDataSubChanged();
-                    // To avoid launch unexpected provisioning checks, ignore re-provisioning when
-                    // no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning() will be
-                    // triggered again when CarrierConfig is loaded.
-                    if (mEntitlementMgr.getCarrierConfig(mConfig) != null) {
-                        mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
-                    } else {
-                        mLog.log("IGNORED reevaluate provisioning due to no carrier config loaded");
-                    }
-                });
+        mPhoneStateListener = new PhoneStateListener(mLooper) {
+            @Override
+            public void onActiveDataSubscriptionIdChanged(int subId) {
+                mLog.log("OBSERVED active data subscription change, from " + mActiveDataSubId
+                        + " to " + subId);
+                if (subId == mActiveDataSubId) return;
+
+                mActiveDataSubId = subId;
+                updateConfiguration();
+                // To avoid launching unexpected provisioning checks, ignore re-provisioning when
+                // no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning() will be
+                // triggered again when CarrierConfig is loaded.
+                if (mEntitlementMgr.getCarrierConfig(mConfig) != null) {
+                    mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
+                } else {
+                    mLog.log("IGNORED reevaluate provisioning due to no carrier config loaded");
+                }
+            }
+        };
+
         mStateReceiver = new StateReceiver();
 
         // Load tethering configuration.
@@ -276,7 +282,8 @@
 
     private void startStateMachineUpdaters(Handler handler) {
         mCarrierConfigChange.startListening();
-        mDefaultSubscriptionChange.startListening();
+        TelephonyManager.from(mContext).listen(mPhoneStateListener,
+                PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(UsbManager.ACTION_USB_STATE);
@@ -304,27 +311,17 @@
 
     // NOTE: This is always invoked on the mLooper thread.
     private void updateConfiguration() {
-        final int subId = mDeps.getDefaultDataSubscriptionId();
-        updateConfiguration(subId);
-    }
-
-    private void updateConfiguration(final int subId) {
-        mConfig = new TetheringConfiguration(mContext, mLog, subId);
+        mConfig = mDeps.generateTetheringConfiguration(mContext, mLog, mActiveDataSubId);
         mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
     }
 
     private void maybeDunSettingChanged() {
-        final boolean isDunRequired = TetheringConfiguration.checkDunRequired(mContext);
+        final boolean isDunRequired = TetheringConfiguration.checkDunRequired(
+                mContext, mActiveDataSubId);
         if (isDunRequired == mConfig.isDunRequired) return;
         updateConfiguration();
     }
 
-    private void maybeDefaultDataSubChanged() {
-        final int subId = mDeps.getDefaultDataSubscriptionId();
-        if (subId == mConfig.subId) return;
-        updateConfiguration(subId);
-    }
-
     @Override
     public void interfaceStatusChanged(String iface, boolean up) {
         // Never called directly: only called from interfaceLinkStateChanged.
@@ -458,7 +455,20 @@
 
             @Override
             public void onServiceConnected(int profile, BluetoothProfile proxy) {
-                ((BluetoothPan) proxy).setBluetoothTethering(enable);
+                // Clear identify is fine because caller already pass tethering permission at
+                // ConnectivityService#startTethering()(or stopTethering) before the control comes
+                // here. Bluetooth will check tethering permission again that there is
+                // Context#getOpPackageName() under BluetoothPan#setBluetoothTethering() to get
+                // caller's package name for permission check.
+                // Calling BluetoothPan#setBluetoothTethering() here means the package name always
+                // be system server. If calling identity is not cleared, that package's uid might
+                // not match calling uid and end up in permission denied.
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    ((BluetoothPan) proxy).setBluetoothTethering(enable);
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
                 // TODO: Enabling bluetooth tethering can fail asynchronously here.
                 // We should figure out a way to bubble up that failure instead of sending success.
                 final int result = (((BluetoothPan) proxy).isTetheringOn() == enable)
@@ -775,7 +785,6 @@
                     case WifiManager.WIFI_AP_STATE_FAILED:
                     default:
                         disableWifiIpServingLocked(ifname, curState);
-                        mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_WIFI);
                         break;
                 }
             }
@@ -1318,11 +1327,15 @@
             mOffload.excludeDownstreamInterface(who.interfaceName());
             mForwardedDownstreams.remove(who);
 
-            // If this is a Wi-Fi interface, tell WifiManager of any errors.
+            // If this is a Wi-Fi interface, tell WifiManager of any errors
+            // or the inactive serving state.
             if (who.interfaceType() == TETHERING_WIFI) {
                 if (who.lastError() != TETHER_ERROR_NO_ERROR) {
                     getWifiManager().updateInterfaceIpState(
                             who.interfaceName(), IFACE_IP_MODE_CONFIGURATION_ERROR);
+                } else {
+                    getWifiManager().updateInterfaceIpState(
+                            who.interfaceName(), IFACE_IP_MODE_UNSPECIFIED);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1275302..1bd29e5 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -58,7 +58,6 @@
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkMisc;
-import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.VpnService;
@@ -114,7 +113,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -902,38 +900,6 @@
     }
 
     /**
-     * Analyzes the passed LinkedProperties to figure out whether it routes to most of the IP space.
-     *
-     * This returns true if the passed LinkedProperties contains routes to either most of the IPv4
-     * space or to most of the IPv6 address space, where "most" is defined by the value of the
-     * MOST_IPV{4,6}_ADDRESSES_COUNT constants : if more than this number of addresses are matched
-     * by any of the routes, then it's decided that most of the space is routed.
-     * @hide
-     */
-    @VisibleForTesting
-    static boolean providesRoutesToMostDestinations(LinkProperties lp) {
-        final List<RouteInfo> routes = lp.getAllRoutes();
-        if (routes.size() > MAX_ROUTES_TO_EVALUATE) return true;
-        final Comparator<IpPrefix> prefixLengthComparator = IpPrefix.lengthComparator();
-        TreeSet<IpPrefix> ipv4Prefixes = new TreeSet<>(prefixLengthComparator);
-        TreeSet<IpPrefix> ipv6Prefixes = new TreeSet<>(prefixLengthComparator);
-        for (final RouteInfo route : routes) {
-            if (route.getType() == RouteInfo.RTN_UNREACHABLE) continue;
-            IpPrefix destination = route.getDestination();
-            if (destination.isIPv4()) {
-                ipv4Prefixes.add(destination);
-            } else {
-                ipv6Prefixes.add(destination);
-            }
-        }
-        if (NetworkUtils.routedIPv4AddressCount(ipv4Prefixes) > MOST_IPV4_ADDRESSES_COUNT) {
-            return true;
-        }
-        return NetworkUtils.routedIPv6AddressCount(ipv6Prefixes)
-                .compareTo(MOST_IPV6_ADDRESSES_COUNT) >= 0;
-    }
-
-    /**
      * Attempt to perform a seamless handover of VPNs by only updating LinkProperties without
      * registering a new NetworkAgent. This is not always possible if the new VPN configuration
      * has certain changes, in which case this method would just return {@code false}.
@@ -953,30 +919,22 @@
             return false;
         }
 
-        LinkProperties lp = makeLinkProperties();
-        final boolean hadInternetCapability = mNetworkCapabilities.hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        final boolean willHaveInternetCapability = providesRoutesToMostDestinations(lp);
-        if (hadInternetCapability != willHaveInternetCapability) {
-            // A seamless handover would have led to a change to INTERNET capability, which
-            // is supposed to be immutable for a given network. In this case bail out and do not
-            // perform handover.
-            Log.i(TAG, "Handover not possible due to changes to INTERNET capability");
-            return false;
-        }
-
-        agent.sendLinkProperties(lp);
+        agent.sendLinkProperties(makeLinkProperties());
         return true;
     }
 
     private void agentConnect() {
         LinkProperties lp = makeLinkProperties();
 
-        if (providesRoutesToMostDestinations(lp)) {
-            mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        } else {
-            mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        }
+        // VPN either provide a default route (IPv4 or IPv6 or both), or they are a split tunnel
+        // that falls back to the default network, which by definition provides INTERNET (unless
+        // there is no default network, in which case none of this matters in any sense).
+        // Also, always setting the INTERNET bit guarantees that when a VPN applies to an app,
+        // the VPN will always be reported as the network by getDefaultNetwork and callbacks
+        // registered with registerDefaultNetworkCallback. This in turn protects the invariant
+        // that an app calling ConnectivityManager#bindProcessToNetwork(getDefaultNetwork())
+        // behaves the same as when it uses the default network.
+        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
 
         mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null);
 
@@ -1087,7 +1045,8 @@
             // TEMP use the old jni calls until there is support for netd address setting
             StringBuilder builder = new StringBuilder();
             for (LinkAddress address : config.addresses) {
-                builder.append(" " + address);
+                builder.append(" ");
+                builder.append(address);
             }
             if (jniSetAddresses(interfaze, builder.toString()) < 1) {
                 throw new IllegalArgumentException("At least one address must be specified");
@@ -1171,7 +1130,7 @@
 
     // Note: Return type guarantees results are deduped and sorted, which callers require.
     private SortedSet<Integer> getAppsUids(List<String> packageNames, int userHandle) {
-        SortedSet<Integer> uids = new TreeSet<Integer>();
+        SortedSet<Integer> uids = new TreeSet<>();
         for (String app : packageNames) {
             int uid = getAppUid(app, userHandle);
             if (uid != -1) uids.add(uid);
@@ -1274,7 +1233,7 @@
         // UidRange#createForUser returns the entire range of UIDs available to a macro-user.
         // This is something like 0-99999 ; {@see UserHandle#PER_USER_RANGE}
         final UidRange userRange = UidRange.createForUser(userHandle);
-        final List<UidRange> ranges = new ArrayList<UidRange>();
+        final List<UidRange> ranges = new ArrayList<>();
         for (UidRange range : existingRanges) {
             if (userRange.containsRange(range)) {
                 ranges.add(range);
@@ -1773,7 +1732,7 @@
             byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
             serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
         }
-        if (privateKey == null || userCert == null || caCert == null || serverCert == null) {
+        if (userCert == null || caCert == null || serverCert == null) {
             throw new IllegalStateException("Cannot load credentials");
         }
 
@@ -1850,10 +1809,11 @@
         if (!profile.searchDomains.isEmpty()) {
             config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
         }
-        startLegacyVpn(config, racoon, mtpd);
+        startLegacyVpn(config, racoon, mtpd, profile);
     }
 
-    private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
+    private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd,
+            VpnProfile profile) {
         stopLegacyVpnPrivileged();
 
         // Prepare for the new request.
@@ -1861,7 +1821,7 @@
         updateState(DetailedState.CONNECTING, "startLegacyVpn");
 
         // Start a new LegacyVpnRunner and we are done!
-        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
+        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile);
         mLegacyVpnRunner.start();
     }
 
@@ -1891,7 +1851,7 @@
      * Return the information of the current ongoing legacy VPN.
      * Callers are responsible for checking permissions if needed.
      */
-    public synchronized LegacyVpnInfo getLegacyVpnInfoPrivileged() {
+    private synchronized LegacyVpnInfo getLegacyVpnInfoPrivileged() {
         if (mLegacyVpnRunner == null) return null;
 
         final LegacyVpnInfo info = new LegacyVpnInfo();
@@ -1927,6 +1887,7 @@
         private final String mOuterInterface;
         private final AtomicInteger mOuterConnection =
                 new AtomicInteger(ConnectivityManager.TYPE_NONE);
+        private final VpnProfile mProfile;
 
         private long mBringupStartTime = -1;
 
@@ -1953,7 +1914,7 @@
             }
         };
 
-        public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
+        LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) {
             super(TAG);
             mConfig = config;
             mDaemons = new String[] {"racoon", "mtpd"};
@@ -1969,6 +1930,8 @@
             // registering
             mOuterInterface = mConfig.interfaze;
 
+            mProfile = profile;
+
             if (!TextUtils.isEmpty(mOuterInterface)) {
                 final ConnectivityManager cm = ConnectivityManager.from(mContext);
                 for (Network network : cm.getAllNetworks()) {
@@ -2042,7 +2005,6 @@
 
         private void bringup() {
             // Catch all exceptions so we can clean up a few things.
-            boolean initFinished = false;
             try {
                 // Initialize the timer.
                 mBringupStartTime = SystemClock.elapsedRealtime();
@@ -2061,7 +2023,6 @@
                     throw new IllegalStateException("Cannot delete the state");
                 }
                 new File("/data/misc/vpn/abort").delete();
-                initFinished = true;
 
                 // Check if we need to restart any of the daemons.
                 boolean restart = false;
@@ -2181,7 +2142,7 @@
                 }
 
                 // Add a throw route for the VPN server endpoint, if one was specified.
-                String endpoint = parameters[5];
+                String endpoint = parameters[5].isEmpty() ? mProfile.server : parameters[5];
                 if (!endpoint.isEmpty()) {
                     try {
                         InetAddress addr = InetAddress.parseNumericAddress(endpoint);
diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
index 836f1e6..f952bce 100644
--- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -161,6 +161,12 @@
      * Check if cellular upstream is permitted.
      */
     public boolean isCellularUpstreamPermitted() {
+        // If provisioning is required and EntitlementManager don't know any downstream,
+        // cellular upstream should not be allowed.
+        final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
+        if (mCurrentTethers.size() == 0 && isTetherProvisioningRequired(config)) {
+            return false;
+        }
         return mCellularUpstreamPermitted;
     }
 
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 8427b6e..1907892 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -112,7 +112,7 @@
         tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs);
         tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
 
-        isDunRequired = checkDunRequired(ctx);
+        isDunRequired = checkDunRequired(ctx, subId);
 
         chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
@@ -228,9 +228,9 @@
     }
 
     /** Check whether dun is required. */
-    public static boolean checkDunRequired(Context ctx) {
+    public static boolean checkDunRequired(Context ctx, int id) {
         final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
-        return (tm != null) ? tm.getTetherApnRequired() : false;
+        return (tm != null) ? tm.getTetherApnRequired(id) : false;
     }
 
     private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
index a0aad7c..4ad7ac4 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -21,7 +21,6 @@
 import android.net.ip.IpServer;
 import android.net.util.SharedLog;
 import android.os.Handler;
-import android.telephony.SubscriptionManager;
 
 import com.android.internal.util.StateMachine;
 import com.android.server.connectivity.MockableSystemProperties;
@@ -88,9 +87,10 @@
     }
 
     /**
-     * Get default data subscription id to build TetheringConfiguration.
+     * Generate a new TetheringConfiguration according to input sub Id.
      */
-    public int getDefaultDataSubscriptionId() {
-        return SubscriptionManager.getDefaultDataSubscriptionId();
+    public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log,
+            int subId) {
+        return new TetheringConfiguration(ctx, log, subId);
     }
 }
diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS
index 98e3299..e2f06bf 100644
--- a/services/core/java/com/android/server/display/OWNERS
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -1,5 +1,6 @@
 michaelwr@google.com
 hackbod@google.com
 ogunwale@google.com
+santoscordon@google.com
 
 per-file ColorDisplayService.java=christyfranks@google.com
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
index adc1cd7..8148216 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
@@ -290,8 +290,7 @@
             new KeycodeEntry(KeyEvent.KEYCODE_CHANNEL_UP, CEC_KEYCODE_CHANNEL_UP),
             new KeycodeEntry(KeyEvent.KEYCODE_CHANNEL_DOWN, CEC_KEYCODE_CHANNEL_DOWN),
             new KeycodeEntry(KeyEvent.KEYCODE_LAST_CHANNEL, CEC_KEYCODE_PREVIOUS_CHANNEL),
-            // No Android keycode defined for CEC_KEYCODE_SOUND_SELECT
-            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SOUND_SELECT),
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK, CEC_KEYCODE_SOUND_SELECT),
             new KeycodeEntry(KeyEvent.KEYCODE_TV_INPUT, CEC_KEYCODE_INPUT_SELECT),
             new KeycodeEntry(KeyEvent.KEYCODE_INFO, CEC_KEYCODE_DISPLAY_INFORMATION),
             // No Android keycode defined for CEC_KEYCODE_HELP
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index e9538b6..cb8ca16 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -850,7 +850,11 @@
                 mSystemAudioActivated = on;
                 mService.announceSystemAudioModeChange(on);
             }
-            startArcAction(on);
+            if (on && !mArcEstablished) {
+                startArcAction(true);
+            } else if (!on) {
+                startArcAction(false);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 4f8b1dc..dad435b 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -492,6 +492,9 @@
             if (jobStatus.hasBatteryNotLowConstraint()) {
                 out.attribute(null, "battery-not-low", Boolean.toString(true));
             }
+            if (jobStatus.hasStorageNotLowConstraint()) {
+                out.attribute(null, "storage-not-low", Boolean.toString(true));
+            }
             out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
         }
 
@@ -852,6 +855,15 @@
             jobBuilder.setExtras(extras);
             parser.nextTag(); // Consume </extras>
 
+            final JobInfo builtJob;
+            try {
+                builtJob = jobBuilder.build();
+            } catch (Exception e) {
+                Slog.w(TAG, "Unable to build job from XML, ignoring: "
+                        + jobBuilder.summarize());
+                return null;
+            }
+
             // Migrate sync jobs forward from earlier, incomplete representation
             if ("android".equals(sourcePackageName)
                     && extras != null
@@ -935,6 +947,14 @@
             if (val != null) {
                 jobBuilder.setRequiresCharging(true);
             }
+            val = parser.getAttributeValue(null, "battery-not-low");
+            if (val != null) {
+                jobBuilder.setRequiresBatteryNotLow(true);
+            }
+            val = parser.getAttributeValue(null, "storage-not-low");
+            if (val != null) {
+                jobBuilder.setRequiresStorageNotLow(true);
+            }
         }
 
         /**
diff --git a/services/core/java/com/android/server/media/OWNERS b/services/core/java/com/android/server/media/OWNERS
index 4bc9373..b460cb5 100644
--- a/services/core/java/com/android/server/media/OWNERS
+++ b/services/core/java/com/android/server/media/OWNERS
@@ -2,5 +2,6 @@
 hdmoon@google.com
 insun@google.com
 jaewan@google.com
+klhyun@google.com
 lajos@google.com
 sungsoo@google.com
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 3f15b38..77fbe41 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -19,6 +19,8 @@
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.provider.Settings.ACTION_VPN_SETTINGS;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -32,7 +34,7 @@
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
-import android.os.INetworkManagementService;
+import android.os.Handler;
 import android.security.Credentials;
 import android.security.KeyStore;
 import android.text.TextUtils;
@@ -63,22 +65,19 @@
 
     private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
 
-    private static final int ROOT_UID = 0;
+    @NonNull private final Context mContext;
+    @NonNull private final ConnectivityService mConnService;
+    @NonNull private final Handler mHandler;
+    @NonNull private final Vpn mVpn;
+    @NonNull private final VpnProfile mProfile;
 
-    private final Context mContext;
-    private final INetworkManagementService mNetService;
-    private final ConnectivityService mConnService;
-    private final Vpn mVpn;
-    private final VpnProfile mProfile;
+    @NonNull private final Object mStateLock = new Object();
 
-    private final Object mStateLock = new Object();
+    @NonNull private final PendingIntent mConfigIntent;
+    @NonNull private final PendingIntent mResetIntent;
 
-    private final PendingIntent mConfigIntent;
-    private final PendingIntent mResetIntent;
-
+    @Nullable
     private String mAcceptedEgressIface;
-    private String mAcceptedIface;
-    private List<LinkAddress> mAcceptedSourceAddr;
 
     private int mErrorCount;
 
@@ -86,11 +85,14 @@
         return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
     }
 
-    public LockdownVpnTracker(Context context, INetworkManagementService netService,
-            ConnectivityService connService, Vpn vpn, VpnProfile profile) {
+    public LockdownVpnTracker(@NonNull Context context,
+            @NonNull ConnectivityService connService,
+            @NonNull Handler handler,
+            @NonNull Vpn vpn,
+            @NonNull VpnProfile profile) {
         mContext = Preconditions.checkNotNull(context);
-        mNetService = Preconditions.checkNotNull(netService);
         mConnService = Preconditions.checkNotNull(connService);
+        mHandler = Preconditions.checkNotNull(handler);
         mVpn = Preconditions.checkNotNull(vpn);
         mProfile = Preconditions.checkNotNull(profile);
 
@@ -176,11 +178,6 @@
             final String iface = vpnConfig.interfaze;
             final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
 
-            if (TextUtils.equals(iface, mAcceptedIface)
-                  && sourceAddrs.equals(mAcceptedSourceAddr)) {
-                return;
-            }
-
             Slog.d(TAG, "VPN connected using iface=" + iface +
                     ", sourceAddr=" + sourceAddrs.toString());
             EventLogTags.writeLockdownVpnConnected(egressType);
@@ -205,7 +202,7 @@
         mVpn.setLockdown(true);
 
         final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
-        mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
+        mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, mHandler);
 
         handleStateChangedLocked();
     }
diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/services/core/java/com/android/server/net/NetworkStatsAccess.java
index cebc472..7c1c1c7 100644
--- a/services/core/java/com/android/server/net/NetworkStatsAccess.java
+++ b/services/core/java/com/android/server/net/NetworkStatsAccess.java
@@ -109,7 +109,7 @@
         final TelephonyManager tm = (TelephonyManager)
                 context.getSystemService(Context.TELEPHONY_SERVICE);
         boolean hasCarrierPrivileges = tm != null &&
-                tm.checkCarrierPrivilegesForPackage(callingPackage) ==
+                tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) ==
                         TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
         boolean isDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
                 DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
index 2e64965..3ca1803 100644
--- a/services/core/java/com/android/server/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.server.net;
 
+import static android.net.NetworkStats.INTERFACES_ALL;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.TAG_ALL;
 import static android.net.NetworkStats.TAG_NONE;
@@ -33,6 +34,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ProcFileReader;
 
@@ -66,31 +68,59 @@
     /** Path to {@code /proc/net/xt_qtaguid/stats}. */
     private final File mStatsXtUid;
 
-    private boolean mUseBpfStats;
+    private final boolean mUseBpfStats;
 
     private INetd mNetdService;
 
-    // A persistent Snapshot since device start for eBPF stats
-    @GuardedBy("mPersistSnapshot")
-    private final NetworkStats mPersistSnapshot;
+    /**
+     * Guards persistent data access in this class
+     *
+     * <p>In order to prevent deadlocks, critical sections protected by this lock SHALL NOT call out
+     * to other code that will acquire other locks within the system server. See b/134244752.
+     */
+    private final Object mPersistentDataLock = new Object();
 
-    // TODO: only do adjustments in NetworkStatsService and remove this.
+    /** Set containing info about active VPNs and their underlying networks. */
+    private volatile VpnInfo[] mVpnInfos = new VpnInfo[0];
+
+    // A persistent snapshot of cumulative stats since device start
+    @GuardedBy("mPersistentDataLock")
+    private NetworkStats mPersistSnapshot;
+
+    // The persistent snapshot of tun and 464xlat adjusted stats since device start
+    @GuardedBy("mPersistentDataLock")
+    private NetworkStats mTunAnd464xlatAdjustedStats;
+
     /**
      * (Stacked interface) -> (base interface) association for all connected ifaces since boot.
      *
      * Because counters must never roll backwards, once a given interface is stacked on top of an
      * underlying interface, the stacked interface can never be stacked on top of
      * another interface. */
-    private static final ConcurrentHashMap<String, String> sStackedIfaces
+    private final ConcurrentHashMap<String, String> mStackedIfaces
             = new ConcurrentHashMap<>();
 
-    public static void noteStackedIface(String stackedIface, String baseIface) {
+    /** Informs the factory of a new stacked interface. */
+    public void noteStackedIface(String stackedIface, String baseIface) {
         if (stackedIface != null && baseIface != null) {
-            sStackedIfaces.put(stackedIface, baseIface);
+            mStackedIfaces.put(stackedIface, baseIface);
         }
     }
 
     /**
+     * Set active VPN information for data usage migration purposes
+     *
+     * <p>Traffic on TUN-based VPNs inherently all appear to be originated from the VPN providing
+     * app's UID. This method is used to support migration of VPN data usage, ensuring data is
+     * accurately billed to the real owner of the traffic.
+     *
+     * @param vpnArray The snapshot of the currently-running VPNs.
+     */
+    public void updateVpnInfos(VpnInfo[] vpnArray) {
+        mVpnInfos = vpnArray.clone();
+    }
+
+    /**
      * Get a set of interfaces containing specified ifaces and stacked interfaces.
      *
      * <p>The added stacked interfaces are ifaces stacked on top of the specified ones, or ifaces
@@ -98,7 +128,7 @@
      * {@link #noteStackedIface(String, String)}, but only interfaces noted before this method
      * is called are guaranteed to be included.
      */
-    public static String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
+    public String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
         if (requiredIfaces == NetworkStats.INTERFACES_ALL) {
             return null;
         }
@@ -108,7 +138,7 @@
         // elements as they existed upon construction exactly once, and may
         // (but are not guaranteed to) reflect any modifications subsequent to construction".
         // This is enough here.
-        for (Map.Entry<String, String> entry : sStackedIfaces.entrySet()) {
+        for (Map.Entry<String, String> entry : mStackedIfaces.entrySet()) {
             if (relatedIfaces.contains(entry.getKey())) {
                 relatedIfaces.add(entry.getValue());
             } else if (relatedIfaces.contains(entry.getValue())) {
@@ -124,17 +154,12 @@
      * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
      * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map, boolean)
      */
-    public static void apply464xlatAdjustments(NetworkStats baseTraffic,
+    public void apply464xlatAdjustments(NetworkStats baseTraffic,
             NetworkStats stackedTraffic, boolean useBpfStats) {
-        NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces,
+        NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces,
                 useBpfStats);
     }
 
-    @VisibleForTesting
-    public static void clearStackedIfaces() {
-        sStackedIfaces.clear();
-    }
-
     public NetworkStatsFactory() {
         this(new File("/proc/"), new File("/sys/fs/bpf/map_netd_app_uid_stats_map").exists());
     }
@@ -145,7 +170,10 @@
         mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
         mUseBpfStats = useBpfStats;
-        mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
+        synchronized (mPersistentDataLock) {
+            mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
+            mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
+        }
     }
 
     public NetworkStats readBpfNetworkStatsDev() throws IOException {
@@ -263,53 +291,44 @@
         return stats;
     }
 
-    /**
-     * @deprecated Use NetworkStatsService#getDetailedUidStats which also accounts for
-     * VPN traffic
-     */
-    @Deprecated
     public NetworkStats readNetworkStatsDetail() throws IOException {
-        return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
+        return readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
     }
 
-    public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag,
-            NetworkStats lastStats) throws IOException {
-        final NetworkStats stats =
-              readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag, lastStats);
-
-        // No locking here: apply464xlatAdjustments behaves fine with an add-only ConcurrentHashMap.
-        // TODO: remove this and only apply adjustments in NetworkStatsService.
-        stats.apply464xlatAdjustments(sStackedIfaces, mUseBpfStats);
-
-        return stats;
-    }
-
-    @GuardedBy("mPersistSnapshot")
+    @GuardedBy("mPersistentDataLock")
     private void requestSwapActiveStatsMapLocked() throws RemoteException {
         // Ask netd to do a active map stats swap. When the binder call successfully returns,
         // the system server should be able to safely read and clean the inactive map
         // without race problem.
-        if (mUseBpfStats) {
-            if (mNetdService == null) {
-                mNetdService = NetdService.getInstance();
-            }
-            mNetdService.trafficSwapActiveStatsMap();
+        if (mNetdService == null) {
+            mNetdService = NetdService.getInstance();
         }
+        mNetdService.trafficSwapActiveStatsMap();
     }
 
-    // TODO: delete the lastStats parameter
-    private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces,
-            int limitTag, NetworkStats lastStats) throws IOException {
-        if (USE_NATIVE_PARSING) {
-            final NetworkStats stats;
-            if (lastStats != null) {
-                stats = lastStats;
-                stats.setElapsedRealtime(SystemClock.elapsedRealtime());
-            } else {
-                stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
-            }
-            if (mUseBpfStats) {
-                synchronized (mPersistSnapshot) {
+    /**
+     * Reads the detailed UID stats based on the provided parameters
+     *
+     * @param limitUid the UID to limit this query to
+     * @param limitIfaces the interfaces to limit this query to. Use {@link
+     *     NetworkStats.INTERFACES_ALL} to select all interfaces
+     * @param limitTag the tags to limit this query to
+     * @return the NetworkStats instance containing network statistics at the present time.
+     */
+    public NetworkStats readNetworkStatsDetail(
+            int limitUid, String[] limitIfaces, int limitTag) throws IOException {
+        // In order to prevent deadlocks, anything protected by this lock MUST NOT call out to other
+        // code that will acquire other locks within the system server. See b/134244752.
+        synchronized (mPersistentDataLock) {
+            // Take a reference. If this gets swapped out, we still have the old reference.
+            final VpnInfo[] vpnArray = mVpnInfos;
+            // Take a defensive copy. mPersistSnapshot is mutated in some cases below
+            final NetworkStats prev = mPersistSnapshot.clone();
+
+            if (USE_NATIVE_PARSING) {
+                final NetworkStats stats =
+                        new NetworkStats(SystemClock.elapsedRealtime(), 0 /* initialSize */);
+                if (mUseBpfStats) {
                     try {
                         requestSwapActiveStatsMapLocked();
                     } catch (RemoteException e) {
@@ -318,32 +337,66 @@
                     // Stats are always read from the inactive map, so they must be read after the
                     // swap
                     if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
-                            null, TAG_ALL, mUseBpfStats) != 0) {
+                            INTERFACES_ALL, TAG_ALL, mUseBpfStats) != 0) {
                         throw new IOException("Failed to parse network stats");
                     }
+
+                    // BPF stats are incremental; fold into mPersistSnapshot.
                     mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
                     mPersistSnapshot.combineAllValues(stats);
-                    NetworkStats result = mPersistSnapshot.clone();
-                    result.filter(limitUid, limitIfaces, limitTag);
-                    return result;
+                } else {
+                    if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
+                            INTERFACES_ALL, TAG_ALL, mUseBpfStats) != 0) {
+                        throw new IOException("Failed to parse network stats");
+                    }
+                    if (SANITY_CHECK_NATIVE) {
+                        final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid,
+                                UID_ALL, INTERFACES_ALL, TAG_ALL);
+                        assertEquals(javaStats, stats);
+                    }
+
+                    mPersistSnapshot = stats;
                 }
             } else {
-                if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
-                        limitIfaces, limitTag, mUseBpfStats) != 0) {
-                    throw new IOException("Failed to parse network stats");
-                }
-                if (SANITY_CHECK_NATIVE) {
-                    final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
-                            limitIfaces, limitTag);
-                    assertEquals(javaStats, stats);
-                }
-                return stats;
+                mPersistSnapshot = javaReadNetworkStatsDetail(mStatsXtUid, UID_ALL, INTERFACES_ALL,
+                        TAG_ALL);
             }
-        } else {
-            return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag);
+
+            NetworkStats adjustedStats = adjustForTunAnd464Xlat(mPersistSnapshot, prev, vpnArray);
+
+            // Filter return values
+            adjustedStats.filter(limitUid, limitIfaces, limitTag);
+            return adjustedStats;
         }
     }
 
+    @GuardedBy("mPersistentDataLock")
+    private NetworkStats adjustForTunAnd464Xlat(
+            NetworkStats uidDetailStats, NetworkStats previousStats, VpnInfo[] vpnArray) {
+        // Calculate delta from last snapshot
+        final NetworkStats delta = uidDetailStats.subtract(previousStats);
+
+        // Apply 464xlat adjustments before VPN adjustments. If VPNs are using v4 on a v6 only
+        // network, the overhead is their fault.
+        // No locking here: apply464xlatAdjustments behaves fine with an add-only
+        // ConcurrentHashMap.
+        delta.apply464xlatAdjustments(mStackedIfaces, mUseBpfStats);
+
+        // Migrate data usage over a VPN to the TUN network.
+        for (VpnInfo info : vpnArray) {
+            delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces);
+        }
+
+        // Filter out debug entries as that may lead to over counting.
+        delta.filterDebugEntries();
+
+        // Update mTunAnd464xlatAdjustedStats with migrated delta.
+        mTunAnd464xlatAdjustedStats.combineAllValues(delta);
+        mTunAnd464xlatAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime());
+
+        return mTunAnd464xlatAdjustedStats.clone();
+    }
+
     /**
      * Parse and return {@link NetworkStats} with UID-level details. Values are
      * expected to monotonically increase since device boot.
diff --git a/services/core/java/com/android/server/net/NetworkStatsObservers.java b/services/core/java/com/android/server/net/NetworkStatsObservers.java
index d840873..2564dae 100644
--- a/services/core/java/com/android/server/net/NetworkStatsObservers.java
+++ b/services/core/java/com/android/server/net/NetworkStatsObservers.java
@@ -39,7 +39,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.net.VpnInfo;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -104,9 +103,9 @@
     public void updateStats(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
                 ArrayMap<String, NetworkIdentitySet> activeIfaces,
                 ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
-                VpnInfo[] vpnArray, long currentTime) {
+                long currentTime) {
         StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
-                activeUidIfaces, vpnArray, currentTime);
+                activeUidIfaces, currentTime);
         getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
     }
 
@@ -354,7 +353,7 @@
             // thread will update it. We pass a null VPN array because usage is aggregated by uid
             // for this snapshot, so VPN traffic can't be reattributed to responsible apps.
             mRecorder.recordSnapshotLocked(statsContext.mXtSnapshot, statsContext.mActiveIfaces,
-                    null /* vpnArray */, statsContext.mCurrentTime);
+                    statsContext.mCurrentTime);
         }
 
         /**
@@ -396,7 +395,7 @@
             // thread will update it. We pass the VPN info so VPN traffic is reattributed to
             // responsible apps.
             mRecorder.recordSnapshotLocked(statsContext.mUidSnapshot, statsContext.mActiveUidIfaces,
-                    statsContext.mVpnArray, statsContext.mCurrentTime);
+                    statsContext.mCurrentTime);
         }
 
         /**
@@ -427,18 +426,16 @@
         NetworkStats mUidSnapshot;
         ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
         ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
-        VpnInfo[] mVpnArray;
         long mCurrentTime;
 
         StatsContext(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
                 ArrayMap<String, NetworkIdentitySet> activeIfaces,
                 ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
-                VpnInfo[] vpnArray, long currentTime) {
+                long currentTime) {
             mXtSnapshot = xtSnapshot;
             mUidSnapshot = uidSnapshot;
             mActiveIfaces = activeIfaces;
             mActiveUidIfaces = activeUidIfaces;
-            mVpnArray = vpnArray;
             mCurrentTime = currentTime;
         }
     }
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
index bdff500..06ec341 100644
--- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
@@ -23,7 +23,6 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
-import android.annotation.Nullable;
 import android.net.NetworkStats;
 import android.net.NetworkStats.NonMonotonicObserver;
 import android.net.NetworkStatsHistory;
@@ -37,7 +36,6 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.net.VpnInfo;
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -202,18 +200,12 @@
     }
 
     /**
-     * Record any delta that occurred since last {@link NetworkStats} snapshot,
-     * using the given {@link Map} to identify network interfaces. First
-     * snapshot is considered bootstrap, and is not counted as delta.
-     *
-     * @param vpnArray Optional info about the currently active VPN, if any. This is used to
-     *                 redistribute traffic from the VPN app to the underlying responsible apps.
-     *                 This should always be set to null if the provided snapshot is aggregated
-     *                 across all UIDs (e.g. contains UID_ALL buckets), regardless of VPN state.
+     * Record any delta that occurred since last {@link NetworkStats} snapshot, using the given
+     * {@link Map} to identify network interfaces. First snapshot is considered bootstrap, and is
+     * not counted as delta.
      */
     public void recordSnapshotLocked(NetworkStats snapshot,
-            Map<String, NetworkIdentitySet> ifaceIdent, @Nullable VpnInfo[] vpnArray,
-            long currentTimeMillis) {
+            Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
         final HashSet<String> unknownIfaces = Sets.newHashSet();
 
         // skip recording when snapshot missing
@@ -232,12 +224,6 @@
         final long end = currentTimeMillis;
         final long start = end - delta.getElapsedRealtime();
 
-        if (vpnArray != null) {
-            for (VpnInfo info : vpnArray) {
-                delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces);
-            }
-        }
-
         NetworkStats.Entry entry = null;
         for (int i = 0; i < delta.size(); i++) {
             entry = delta.getValues(i, entry);
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 484efd6..90dc700 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -181,6 +181,7 @@
 
     private final Context mContext;
     private final INetworkManagementService mNetworkManager;
+    private final NetworkStatsFactory mStatsFactory;
     private final AlarmManager mAlarmManager;
     private final Clock mClock;
     private final TelephonyManager mTeleManager;
@@ -266,10 +267,6 @@
     @GuardedBy("mStatsLock")
     private Network[] mDefaultNetworks = new Network[0];
 
-    /** Set containing info about active VPNs and their underlying networks. */
-    @GuardedBy("mStatsLock")
-    private VpnInfo[] mVpnInfos = new VpnInfo[0];
-
     private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
             new DropBoxNonMonotonicObserver();
 
@@ -292,22 +289,6 @@
     /** Data layer operation counters for splicing into other structures. */
     private NetworkStats mUidOperations = new NetworkStats(0L, 10);
 
-    /**
-     * Snapshot containing most recent network stats for all UIDs across all interfaces and tags
-     * since boot.
-     *
-     * <p>Maintains migrated VPN stats which are result of performing TUN migration on {@link
-     * #mLastUidDetailSnapshot}.
-     */
-    @GuardedBy("mStatsLock")
-    private NetworkStats mTunAdjustedStats;
-    /**
-     * Used by {@link #mTunAdjustedStats} to migrate VPN traffic over delta between this snapshot
-     * and latest snapshot.
-     */
-    @GuardedBy("mStatsLock")
-    private NetworkStats mLastUidDetailSnapshot;
-
     /** Must be set in factory by calling #setHandler. */
     private Handler mHandler;
     private Handler.Callback mHandlerCallback;
@@ -350,8 +331,8 @@
 
         NetworkStatsService service = new NetworkStatsService(context, networkManager, alarmManager,
                 wakeLock, getDefaultClock(), TelephonyManager.getDefault(),
-                new DefaultNetworkStatsSettings(context), new NetworkStatsObservers(),
-                getDefaultSystemDir(), getDefaultBaseDir());
+                new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(),
+                new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir());
         service.registerLocalService();
 
         HandlerThread handlerThread = new HandlerThread(TAG);
@@ -368,7 +349,8 @@
     NetworkStatsService(Context context, INetworkManagementService networkManager,
             AlarmManager alarmManager, PowerManager.WakeLock wakeLock, Clock clock,
             TelephonyManager teleManager, NetworkStatsSettings settings,
-            NetworkStatsObservers statsObservers, File systemDir, File baseDir) {
+            NetworkStatsFactory factory, NetworkStatsObservers statsObservers, File systemDir,
+            File baseDir) {
         mContext = checkNotNull(context, "missing Context");
         mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
         mAlarmManager = checkNotNull(alarmManager, "missing AlarmManager");
@@ -376,6 +358,7 @@
         mSettings = checkNotNull(settings, "missing NetworkStatsSettings");
         mTeleManager = checkNotNull(teleManager, "missing TelephonyManager");
         mWakeLock = checkNotNull(wakeLock, "missing WakeLock");
+        mStatsFactory = checkNotNull(factory, "missing factory");
         mStatsObservers = checkNotNull(statsObservers, "missing NetworkStatsObservers");
         mSystemDir = checkNotNull(systemDir, "missing systemDir");
         mBaseDir = checkNotNull(baseDir, "missing baseDir");
@@ -394,14 +377,9 @@
     }
 
     public void systemReady() {
-        mSystemReady = true;
-
-        if (!isBandwidthControlEnabled()) {
-            Slog.w(TAG, "bandwidth controls disabled, unable to track stats");
-            return;
-        }
-
         synchronized (mStatsLock) {
+            mSystemReady = true;
+
             // create data recorders along with historical rotators
             mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
             mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false);
@@ -447,7 +425,14 @@
             // ignored; service lives in system_server
         }
 
-        registerPollAlarmLocked();
+        //  schedule periodic pall alarm based on {@link NetworkStatsSettings#getPollInterval()}.
+        final PendingIntent pollIntent =
+                PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
+
+        final long currentRealtime = SystemClock.elapsedRealtime();
+        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
+                mSettings.getPollInterval(), pollIntent);
+
         registerGlobalAlert();
     }
 
@@ -508,23 +493,6 @@
     }
 
     /**
-     * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
-     * reschedule based on current {@link NetworkStatsSettings#getPollInterval()}.
-     */
-    private void registerPollAlarmLocked() {
-        if (mPollIntent != null) {
-            mAlarmManager.cancel(mPollIntent);
-        }
-
-        mPollIntent = PendingIntent.getBroadcast(
-                mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
-
-        final long currentRealtime = SystemClock.elapsedRealtime();
-        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
-                mSettings.getPollInterval(), mPollIntent);
-    }
-
-    /**
      * Register for a global alert that is delivered through
      * {@link INetworkManagementEventObserver} once a threshold amount of data
      * has been transferred.
@@ -569,8 +537,6 @@
     }
 
     private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) {
-        assertBandwidthControlEnabled();
-
         final int callingUid = Binder.getCallingUid();
         final int usedFlags = isRateLimitedForPoll(callingUid)
                 ? flags & (~NetworkStatsManager.FLAG_POLL_ON_OPEN)
@@ -763,7 +729,6 @@
 
     private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
         assertSystemReady();
-        assertBandwidthControlEnabled();
 
         // NOTE: if callers want to get non-augmented data, they should go
         // through the public API
@@ -774,7 +739,6 @@
 
     private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
         assertSystemReady();
-        assertBandwidthControlEnabled();
 
         final NetworkStatsCollection uidComplete;
         synchronized (mStatsLock) {
@@ -789,18 +753,10 @@
         if (Binder.getCallingUid() != uid) {
             mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
         }
-        assertBandwidthControlEnabled();
 
         // TODO: switch to data layer stats once kernel exports
         // for now, read network layer stats and flatten across all ifaces
-        final long token = Binder.clearCallingIdentity();
-        final NetworkStats networkLayer;
-        try {
-            networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid,
-                    NetworkStats.INTERFACES_ALL);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        final NetworkStats networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
 
         // splice in operation counts
         networkLayer.spliceOperationsFrom(mUidOperations);
@@ -821,39 +777,15 @@
     @Override
     public NetworkStats getDetailedUidStats(String[] requiredIfaces) {
         try {
-            // Get the latest snapshot from NetworkStatsFactory.
-            // TODO: Querying for INTERFACES_ALL may incur performance penalty. Consider restricting
-            // this to limited set of ifaces.
-            NetworkStats uidDetailStats = getNetworkStatsUidDetail(INTERFACES_ALL);
-
-            // Migrate traffic from VPN UID over delta and update mTunAdjustedStats.
-            NetworkStats result;
-            synchronized (mStatsLock) {
-                migrateTunTraffic(uidDetailStats, mVpnInfos);
-                result = mTunAdjustedStats.clone();
-            }
-
-            // Apply filter based on ifacesToQuery.
             final String[] ifacesToQuery =
-                    NetworkStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
-            result.filter(UID_ALL, ifacesToQuery, TAG_ALL);
-            return result;
+                    mStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
+            return getNetworkStatsUidDetail(ifacesToQuery);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error compiling UID stats", e);
             return new NetworkStats(0L, 0);
         }
     }
 
-    @VisibleForTesting
-    NetworkStats getTunAdjustedStats() {
-        synchronized (mStatsLock) {
-            if (mTunAdjustedStats == null) {
-                return null;
-            }
-            return mTunAdjustedStats.clone();
-        }
-    }
-
     @Override
     public String[] getMobileIfaces() {
         return mMobileIfaces;
@@ -897,24 +829,27 @@
     @Override
     public void forceUpdateIfaces(
             Network[] defaultNetworks,
-            VpnInfo[] vpnArray,
             NetworkState[] networkStates,
-            String activeIface) {
+            String activeIface,
+            VpnInfo[] vpnInfos) {
         checkNetworkStackPermission(mContext);
-        assertBandwidthControlEnabled();
 
         final long token = Binder.clearCallingIdentity();
         try {
-            updateIfaces(defaultNetworks, vpnArray, networkStates, activeIface);
+            updateIfaces(defaultNetworks, networkStates, activeIface);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
+
+        // Update the VPN underlying interfaces only after the poll is made and tun data has been
+        // migrated. Otherwise the migration would use the new interfaces instead of the ones that
+        // were current when the polled data was transferred.
+        mStatsFactory.updateVpnInfos(vpnInfos);
     }
 
     @Override
     public void forceUpdate() {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
-        assertBandwidthControlEnabled();
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -925,8 +860,6 @@
     }
 
     private void advisePersistThreshold(long thresholdBytes) {
-        assertBandwidthControlEnabled();
-
         // clamp threshold into safe range
         mPersistThreshold = MathUtils.constrain(thresholdBytes, 128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
         if (LOGV) {
@@ -1171,13 +1104,11 @@
 
     private void updateIfaces(
             Network[] defaultNetworks,
-            VpnInfo[] vpnArray,
             NetworkState[] networkStates,
             String activeIface) {
         synchronized (mStatsLock) {
             mWakeLock.acquire();
             try {
-                mVpnInfos = vpnArray;
                 mActiveIface = activeIface;
                 updateIfacesLocked(defaultNetworks, networkStates);
             } finally {
@@ -1187,10 +1118,9 @@
     }
 
     /**
-     * Inspect all current {@link NetworkState} to derive mapping from {@code
-     * iface} to {@link NetworkStatsHistory}. When multiple {@link NetworkInfo}
-     * are active on a single {@code iface}, they are combined under a single
-     * {@link NetworkIdentitySet}.
+     * Inspect all current {@link NetworkState} to derive mapping from {@code iface} to {@link
+     * NetworkStatsHistory}. When multiple {@link NetworkInfo} are active on a single {@code iface},
+     * they are combined under a single {@link NetworkIdentitySet}.
      */
     @GuardedBy("mStatsLock")
     private void updateIfacesLocked(Network[] defaultNetworks, NetworkState[] states) {
@@ -1250,20 +1180,28 @@
                     }
                 }
 
-                // Traffic occurring on stacked interfaces is usually clatd,
-                // which is already accounted against its final egress interface
-                // by the kernel. Thus, we only need to collect stacked
-                // interface stats at the UID level.
+                // Traffic occurring on stacked interfaces is usually clatd.
+                // UID stats are always counted on the stacked interface and never
+                // on the base interface, because the packets on the base interface
+                // do not actually match application sockets until they are translated.
+                //
+                // Interface stats are more complicated. Packets subject to BPF offload
+                // never appear on the base interface and only appear on the stacked
+                // interface, so to ensure those packets increment interface stats, interface
+                // stats from stacked interfaces must be collected.
                 final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks();
                 for (LinkProperties stackedLink : stackedLinks) {
                     final String stackedIface = stackedLink.getInterfaceName();
                     if (stackedIface != null) {
+                        if (mUseBpfTrafficStats) {
+                            findOrCreateNetworkIdentitySet(mActiveIfaces, stackedIface).add(ident);
+                        }
                         findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident);
                         if (isMobile) {
                             mobileIfaces.add(stackedIface);
                         }
 
-                        NetworkStatsFactory.noteStackedIface(stackedIface, baseIface);
+                        mStatsFactory.noteStackedIface(stackedIface, baseIface);
                     }
                 }
             }
@@ -1293,7 +1231,7 @@
         final NetworkStats xtSnapshot = getNetworkStatsXt();
         Trace.traceEnd(TRACE_TAG_NETWORK);
         Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotDev");
-        final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev();
+        final NetworkStats devSnapshot = readNetworkStatsSummaryDev();
         Trace.traceEnd(TRACE_TAG_NETWORK);
 
         // Tethering snapshot for dev and xt stats. Counts per-interface data from tethering stats
@@ -1307,55 +1245,24 @@
         // For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
         // can't be reattributed to responsible apps.
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev");
-        mDevRecorder.recordSnapshotLocked(
-                devSnapshot, mActiveIfaces, null /* vpnArray */, currentTime);
+        mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
         Trace.traceEnd(TRACE_TAG_NETWORK);
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordXt");
-        mXtRecorder.recordSnapshotLocked(
-                xtSnapshot, mActiveIfaces, null /* vpnArray */, currentTime);
+        mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime);
         Trace.traceEnd(TRACE_TAG_NETWORK);
 
         // For per-UID stats, pass the VPN info so VPN traffic is reattributed to responsible apps.
-        VpnInfo[] vpnArray = mVpnInfos;
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordUid");
-        mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime);
+        mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
         Trace.traceEnd(TRACE_TAG_NETWORK);
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordUidTag");
-        mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime);
+        mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
         Trace.traceEnd(TRACE_TAG_NETWORK);
 
         // We need to make copies of member fields that are sent to the observer to avoid
         // a race condition between the service handler thread and the observer's
         mStatsObservers.updateStats(xtSnapshot, uidSnapshot, new ArrayMap<>(mActiveIfaces),
-                new ArrayMap<>(mActiveUidIfaces), vpnArray, currentTime);
-
-        migrateTunTraffic(uidSnapshot, vpnArray);
-    }
-
-    /**
-     * Updates {@link #mTunAdjustedStats} with the delta containing traffic migrated off of VPNs.
-     */
-    @GuardedBy("mStatsLock")
-    private void migrateTunTraffic(NetworkStats uidDetailStats, VpnInfo[] vpnInfoArray) {
-        if (mTunAdjustedStats == null) {
-            // Either device booted or system server restarted, hence traffic cannot be migrated
-            // correctly without knowing the past state of VPN's underlying networks.
-            mTunAdjustedStats = uidDetailStats;
-            mLastUidDetailSnapshot = uidDetailStats;
-            return;
-        }
-        // Migrate delta traffic from VPN to other apps.
-        NetworkStats delta = uidDetailStats.subtract(mLastUidDetailSnapshot);
-        for (VpnInfo info : vpnInfoArray) {
-            delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces);
-        }
-        // Filter out debug entries as that may lead to over counting.
-        delta.filterDebugEntries();
-        // Update #mTunAdjustedStats with migrated delta.
-        mTunAdjustedStats.combineAllValues(delta);
-        mTunAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime());
-        // Update last snapshot.
-        mLastUidDetailSnapshot = uidDetailStats;
+                new ArrayMap<>(mActiveUidIfaces), currentTime);
     }
 
     /**
@@ -1718,6 +1625,30 @@
         }
     }
 
+    private NetworkStats readNetworkStatsSummaryDev() {
+        try {
+            return mStatsFactory.readNetworkStatsSummaryDev();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private NetworkStats readNetworkStatsSummaryXt() {
+        try {
+            return mStatsFactory.readNetworkStatsSummaryXt();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private NetworkStats readNetworkStatsUidDetail(int uid, String[] ifaces, int tag) {
+        try {
+            return mStatsFactory.readNetworkStatsDetail(uid, ifaces, tag);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
     /**
      * Return snapshot of current UID statistics, including any
      * {@link TrafficStats#UID_TETHERING}, video calling data usage, and {@link #mUidOperations}
@@ -1728,15 +1659,12 @@
      */
     private NetworkStats getNetworkStatsUidDetail(String[] ifaces)
             throws RemoteException {
-
-        // TODO: remove 464xlat adjustments from NetworkStatsFactory and apply all at once here.
-        final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL,
-                ifaces);
+        final NetworkStats uidSnapshot = readNetworkStatsUidDetail(UID_ALL,  ifaces, TAG_ALL);
 
         // fold tethering stats and operations into uid snapshot
         final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_UID);
         tetherSnapshot.filter(UID_ALL, ifaces, TAG_ALL);
-        NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot,
+        mStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot,
                 mUseBpfTrafficStats);
         uidSnapshot.combineAllValues(tetherSnapshot);
 
@@ -1747,7 +1675,7 @@
         final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID);
         if (vtStats != null) {
             vtStats.filter(UID_ALL, ifaces, TAG_ALL);
-            NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats,
+            mStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats,
                     mUseBpfTrafficStats);
             uidSnapshot.combineAllValues(vtStats);
         }
@@ -1761,7 +1689,7 @@
      * Return snapshot of current XT statistics with video calling data usage statistics.
      */
     private NetworkStats getNetworkStatsXt() throws RemoteException {
-        final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt();
+        final NetworkStats xtSnapshot = readNetworkStatsSummaryXt();
 
         final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
                 Context.TELEPHONY_SERVICE);
@@ -1821,24 +1749,6 @@
         }
     }
 
-    private void assertBandwidthControlEnabled() {
-        if (!isBandwidthControlEnabled()) {
-            throw new IllegalStateException("Bandwidth module disabled");
-        }
-    }
-
-    private boolean isBandwidthControlEnabled() {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            return mNetworkManager.isBandwidthControlEnabled();
-        } catch (RemoteException e) {
-            // ignored; service lives in system_server
-            return false;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> {
         @Override
         public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right,
diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java
index 766d8ca..3b24f46 100644
--- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java
+++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java
@@ -17,8 +17,6 @@
 package com.android.server.net.watchlist;
 
 import android.content.Context;
-import android.content.Intent;
-import android.net.NetworkWatchlistManager;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -26,7 +24,6 @@
 import android.provider.Settings;
 
 import java.io.FileInputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
 
@@ -74,10 +71,12 @@
         try {
             final String configXmlPath = getNextArgRequired();
             final ParcelFileDescriptor pfd = openFileForSystem(configXmlPath, "r");
-            if (pfd != null) {
-                final InputStream fileStream = new FileInputStream(pfd.getFileDescriptor());
-                WatchlistConfig.getInstance().setTestMode(fileStream);
+            if (pfd == null) {
+                pw.println("Error: can't open input file " + configXmlPath);
+                return -1;
             }
+            final InputStream fileStream = new FileInputStream(pfd.getFileDescriptor());
+            WatchlistConfig.getInstance().setTestMode(fileStream);
             pw.println("Success!");
         } catch (Exception ex) {
             pw.println("Error: " + ex.toString());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 58a1dc1..b241a67 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -795,8 +795,22 @@
         @Override
         public void onNotificationError(int callingUid, int callingPid, String pkg, String tag, int id,
                 int uid, int initialPid, String message, int userId) {
-            cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
-                    REASON_ERROR, null);
+            final boolean fgService;
+            synchronized (mNotificationLock) {
+                NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
+                fgService = r != null && (r.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0;
+            }
+            cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0,
+                    false, userId, REASON_ERROR, null);
+            if (fgService) {
+                // Still crash for foreground services, preventing the not-crash behaviour abused
+                // by apps to give us a garbage notification and silently start a fg service.
+                Binder.withCleanCallingIdentity(
+                        () -> mAm.crashApplication(uid, initialPid, pkg, -1,
+                            "Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
+                                + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
+                                + message));
+            }
         }
 
         @Override
@@ -2189,6 +2203,11 @@
         @Override
         public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
             checkCallerIsSystemOrSameApp(pkg);
+            if (UserHandle.getCallingUserId() != UserHandle.getUserId(uid)) {
+                getContext().enforceCallingPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        "canNotifyAsPackage for uid " + uid);
+            }
 
             return mRankingHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
         }
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index e40dad6..e839616 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteFullException;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -1265,9 +1266,13 @@
                 sNumWrites = 0;
                 sLastPruneMs = nowMs;
                 long horizonStartMs = nowMs - HORIZON_MS;
-                int deletedRows = db.delete(TAB_LOG, COL_EVENT_TIME + " < ?",
-                        new String[] { String.valueOf(horizonStartMs) });
-                Log.d(TAG, "Pruned event entries: " + deletedRows);
+                try {
+                    int deletedRows = db.delete(TAB_LOG, COL_EVENT_TIME + " < ?",
+                            new String[]{String.valueOf(horizonStartMs)});
+                    Log.d(TAG, "Pruned event entries: " + deletedRows);
+                } catch (SQLiteFullException e) {
+                    Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index c98a79a..714bbb9 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -297,19 +297,5 @@
             }
             mDs.asBinder().unlinkToDeath(this, 0);
         }
-
-        // Old methods; unused in the API flow.
-        @Override
-        public void onProgressUpdated(int progress) throws RemoteException {
-        }
-
-        @Override
-        public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
-        }
-
-        @Override
-        public void onSectionComplete(String title, int status, int size, int durationMs)
-                throws RemoteException {
-        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index d6ab5f7..c712431 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -27,24 +27,30 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
 import android.os.BatteryManager;
 import android.os.Environment;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.StatsLog;
 
-import com.android.server.pm.dex.DexManager;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.PinnerService;
+import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 
 import java.io.File;
+import java.nio.file.Paths;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 
 /**
  * {@hide}
@@ -52,7 +58,7 @@
 public class BackgroundDexOptService extends JobService {
     private static final String TAG = "BackgroundDexOptService";
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int JOB_IDLE_OPTIMIZE = 800;
     private static final int JOB_POST_BOOT_UPDATE = 801;
@@ -97,7 +103,6 @@
     private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
 
     private final File mDataDir = Environment.getDataDirectory();
-
     private static final long mDowngradeUnusedAppsThresholdInMillis =
             getDowngradeUnusedAppsThresholdInMillis();
 
@@ -249,9 +254,16 @@
             @Override
             public void run() {
                 int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this);
-                if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+                if (result == OPTIMIZE_PROCESSED) {
+                    Log.i(TAG, "Idle optimizations completed.");
+                } else if (result == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
                     Log.w(TAG, "Idle optimizations aborted because of space constraints.");
-                    // If we didn't abort we ran to completion (or stopped because of space).
+                } else if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+                    Log.w(TAG, "Idle optimizations aborted by job scheduler.");
+                } else {
+                    Log.w(TAG, "Idle optimizations ended with unexpected code: " + result);
+                }
+                if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                     // Abandon our timeslice and do not reschedule.
                     jobFinished(jobParams, /* reschedule */ false);
                 }
@@ -269,109 +281,179 @@
         mAbortIdleOptimization.set(false);
 
         long lowStorageThreshold = getLowStorageThreshold(context);
-        // Optimize primary apks.
-        int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true,
-                sFailedPackageNamesPrimary);
-
-        if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
-            return result;
-        }
-
-        if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
-            result = reconcileSecondaryDexFiles(pm.getDexManager());
-            if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
-                return result;
-            }
-
-            result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false,
-                    sFailedPackageNamesSecondary);
-        }
+        int result = idleOptimizePackages(pm, pkgs, lowStorageThreshold);
         return result;
     }
 
-    private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
-            long lowStorageThreshold, boolean is_for_primary_dex,
-            ArraySet<String> failedPackageNames) {
+    /**
+     * Get the size of the directory. It uses recursion to go over all files.
+     * @param f
+     * @return
+     */
+    private long getDirectorySize(File f) {
+        long size = 0;
+        if (f.isDirectory()) {
+            for (File file: f.listFiles()) {
+                size += getDirectorySize(file);
+            }
+        } else {
+            size = f.length();
+        }
+        return size;
+    }
+
+    /**
+     * Get the size of a package.
+     * @param pkg
+     */
+    private long getPackageSize(PackageManagerService pm, String pkg) {
+        PackageInfo info = pm.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
+        long size = 0;
+        if (info != null && info.applicationInfo != null) {
+            File path = Paths.get(info.applicationInfo.sourceDir).toFile();
+            if (path.isFile()) {
+                path = path.getParentFile();
+            }
+            size += getDirectorySize(path);
+            if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
+                for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
+                    path = Paths.get(splitSourceDir).toFile();
+                    if (path.isFile()) {
+                        path = path.getParentFile();
+                    }
+                    size += getDirectorySize(path);
+                }
+            }
+            return size;
+        }
+        return 0;
+    }
+
+    private int idleOptimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
+            long lowStorageThreshold) {
         ArraySet<String> updatedPackages = new ArraySet<>();
-        Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
-        // Only downgrade apps when space is low on device.
-        // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
-        // up disk before user hits the actual lowStorageThreshold.
-        final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE *
-                lowStorageThreshold;
-        boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
+
+        try {
+            final boolean supportSecondaryDex = supportSecondaryDex();
+
+            if (supportSecondaryDex) {
+                int result = reconcileSecondaryDexFiles(pm.getDexManager());
+                if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+                    return result;
+                }
+            }
+
+            // Only downgrade apps when space is low on device.
+            // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
+            // up disk before user hits the actual lowStorageThreshold.
+            final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE
+                    * lowStorageThreshold;
+            boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
+            Log.d(TAG, "Should Downgrade " + shouldDowngrade);
+            if (shouldDowngrade) {
+                Set<String> unusedPackages =
+                        pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
+                Log.d(TAG, "Unsused Packages " +  String.join(",", unusedPackages));
+
+                if (!unusedPackages.isEmpty()) {
+                    for (String pkg : unusedPackages) {
+                        int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1);
+                        if (abortCode != OPTIMIZE_CONTINUE) {
+                            // Should be aborted by the scheduler.
+                            return abortCode;
+                        }
+                        if (downgradePackage(pm, pkg, /*isForPrimaryDex*/ true)) {
+                            updatedPackages.add(pkg);
+                        }
+                        if (supportSecondaryDex) {
+                            downgradePackage(pm, pkg, /*isForPrimaryDex*/ false);
+                        }
+                    }
+
+                    pkgs = new ArraySet<>(pkgs);
+                    pkgs.removeAll(unusedPackages);
+                }
+            }
+
+            int primaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
+                    /*isForPrimaryDex*/ true, updatedPackages);
+            if (primaryResult != OPTIMIZE_PROCESSED) {
+                return primaryResult;
+            }
+
+            if (!supportSecondaryDex) {
+                return OPTIMIZE_PROCESSED;
+            }
+
+            int secondaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
+                    /*isForPrimaryDex*/ false, updatedPackages);
+            return secondaryResult;
+        } finally {
+            // Always let the pinner service know about changes.
+            notifyPinService(updatedPackages);
+        }
+    }
+
+    private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
+            long lowStorageThreshold, boolean isForPrimaryDex, ArraySet<String> updatedPackages) {
         for (String pkg : pkgs) {
-            int abort_code = abortIdleOptimizations(lowStorageThreshold);
-            if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
-                return abort_code;
+            int abortCode = abortIdleOptimizations(lowStorageThreshold);
+            if (abortCode != OPTIMIZE_CONTINUE) {
+                // Either aborted by the scheduler or no space left.
+                return abortCode;
             }
 
-            synchronized (failedPackageNames) {
-                if (failedPackageNames.contains(pkg)) {
-                    // Skip previously failing package
-                    continue;
-                }
-            }
-
-            int reason;
-            boolean downgrade;
-            // Downgrade unused packages.
-            if (unusedPackages.contains(pkg) && shouldDowngrade) {
-                // This applies for system apps or if packages location is not a directory, i.e.
-                // monolithic install.
-                if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) {
-                    // For apps that don't have the oat directory, instead of downgrading,
-                    // remove their compiler artifacts from dalvik cache.
-                    pm.deleteOatArtifactsOfPackage(pkg);
-                    continue;
-                } else {
-                    reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
-                    downgrade = true;
-                }
-            } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) {
-                reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
-                downgrade = false;
-            } else {
-                // can't dexopt because of low space.
-                continue;
-            }
-
-            synchronized (failedPackageNames) {
-                // Conservatively add package to the list of failing ones in case
-                // performDexOpt never returns.
-                failedPackageNames.add(pkg);
-            }
-
-            // Optimize package if needed. Note that there can be no race between
-            // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
-            boolean success;
-            int dexoptFlags =
-                    DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
-                    DexoptOptions.DEXOPT_BOOT_COMPLETE |
-                    (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) |
-                    DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
-            if (is_for_primary_dex) {
-                int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
-                        dexoptFlags));
-                success = result != PackageDexOptimizer.DEX_OPT_FAILED;
-                if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
-                    updatedPackages.add(pkg);
-                }
-            } else {
-                success = pm.performDexOpt(new DexoptOptions(pkg,
-                        reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
-            }
-            if (success) {
-                // Dexopt succeeded, remove package from the list of failing ones.
-                synchronized (failedPackageNames) {
-                    failedPackageNames.remove(pkg);
-                }
+            boolean dexOptPerformed = optimizePackage(pm, pkg, isForPrimaryDex);
+            if (dexOptPerformed) {
+                updatedPackages.add(pkg);
             }
         }
-        notifyPinService(updatedPackages);
         return OPTIMIZE_PROCESSED;
     }
 
+    /**
+     * Try to downgrade the package to a smaller compilation filter.
+     * eg. if the package is in speed-profile the package will be downgraded to verify.
+     * @param pm PackageManagerService
+     * @param pkg The package to be downgraded.
+     * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+     * @return true if the package was downgraded.
+     */
+    private boolean downgradePackage(PackageManagerService pm, String pkg,
+            boolean isForPrimaryDex) {
+        Log.d(TAG, "Downgrading " + pkg);
+        boolean dex_opt_performed = false;
+        int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
+        int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+                | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
+                | DexoptOptions.DEXOPT_DOWNGRADE;
+        long package_size_before = getPackageSize(pm, pkg);
+
+        if (isForPrimaryDex) {
+            // This applies for system apps or if packages location is not a directory, i.e.
+            // monolithic install.
+            if (!pm.canHaveOatDir(pkg)) {
+                // For apps that don't have the oat directory, instead of downgrading,
+                // remove their compiler artifacts from dalvik cache.
+                pm.deleteOatArtifactsOfPackage(pkg);
+            } else {
+                dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
+            }
+        } else {
+            dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+        }
+
+        if (dex_opt_performed) {
+            StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
+                    getPackageSize(pm, pkg), /*aggressive=*/ false);
+        }
+        return dex_opt_performed;
+    }
+
+    private boolean supportSecondaryDex() {
+        return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
+    }
+
     private int reconcileSecondaryDexFiles(DexManager dm) {
         // TODO(calin): should we blacklist packages for which we fail to reconcile?
         for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
@@ -383,6 +465,73 @@
         return OPTIMIZE_PROCESSED;
     }
 
+    /**
+     *
+     * Optimize package if needed. Note that there can be no race between
+     * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
+     * @param pm An instance of PackageManagerService
+     * @param pkg The package to be downgraded.
+     * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+     * @return true if the package was downgraded.
+     */
+    private boolean optimizePackage(PackageManagerService pm, String pkg,
+            boolean isForPrimaryDex) {
+        int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
+        int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+                | DexoptOptions.DEXOPT_BOOT_COMPLETE
+                | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+
+        return isForPrimaryDex
+            ? performDexOptPrimary(pm, pkg, reason, dexoptFlags)
+            : performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+    }
+
+    private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
+            int dexoptFlags) {
+        int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
+                () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)));
+        return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+    }
+
+    private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
+            int dexoptFlags) {
+        DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason,
+                dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
+        int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
+                () -> pm.performDexOpt(dexoptOptions)
+                    ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
+        );
+        return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+    }
+
+    /**
+     * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails
+     * the package is added to the list of failed packages.
+     * Return one of following result:
+     *  {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
+     *  {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
+     *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
+     */
+    private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
+            Supplier<Integer> performDexOptWrapper) {
+        ArraySet<String> sFailedPackageNames =
+                isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary;
+        synchronized (sFailedPackageNames) {
+            if (sFailedPackageNames.contains(pkg)) {
+                // Skip previously failing package
+                return PackageDexOptimizer.DEX_OPT_SKIPPED;
+            }
+            sFailedPackageNames.add(pkg);
+        }
+        int result = performDexOptWrapper.get();
+        if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
+            synchronized (sFailedPackageNames) {
+                sFailedPackageNames.remove(pkg);
+            }
+        }
+        return result;
+    }
+
     // Evaluate whether or not idle optimizations should continue.
     private int abortIdleOptimizations(long lowStorageThreshold) {
         if (mAbortIdleOptimization.get()) {
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index fde13ac..eb883c9 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -38,7 +38,6 @@
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
-import android.util.ByteStringUtils;
 import android.util.PackageUtils;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -52,6 +51,7 @@
 import com.android.internal.util.XmlUtils;
 
 import libcore.io.IoUtils;
+import libcore.util.HexEncoding;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -66,7 +66,6 @@
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Locale;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -232,7 +231,7 @@
                                                 @UserIdInt int userId) {
         byte[] randomBytes = new byte[8];
         new SecureRandom().nextBytes(randomBytes);
-        String id = ByteStringUtils.toHexString(randomBytes).toLowerCase(Locale.US);
+        String id = HexEncoding.encodeToString(randomBytes, false /* upperCase */);
         File appDir = getInstantApplicationDir(packageName, userId);
         if (!appDir.exists() && !appDir.mkdirs()) {
             Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 6623526..3ccc4a8 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -16,6 +16,28 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
+
+import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE;
+import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
+import static com.android.server.pm.Installer.DEXOPT_ENABLE_HIDDEN_API_CHECKS;
+import static com.android.server.pm.Installer.DEXOPT_FORCE;
+import static com.android.server.pm.Installer.DEXOPT_GENERATE_APP_IMAGE;
+import static com.android.server.pm.Installer.DEXOPT_GENERATE_COMPACT_DEX;
+import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB;
+import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
+import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
+import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX;
+import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
+import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
+import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
+import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
+import static com.android.server.pm.PackageManagerService.WATCHDOG_TIMEOUT;
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getReasonName;
+
+import static dalvik.system.DexFile.getSafeModeCompilerFilter;
+import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
+
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -25,12 +47,10 @@
 import android.content.pm.dex.DexMetadataHelper;
 import android.os.FileUtils;
 import android.os.PowerManager;
-import android.os.Process;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.WorkSource;
-import android.os.storage.StorageManager;
 import android.util.Log;
 import android.util.Slog;
 
@@ -43,6 +63,8 @@
 import com.android.server.pm.dex.DexoptUtils;
 import com.android.server.pm.dex.PackageDexUsage;
 
+import dalvik.system.DexFile;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -50,32 +72,6 @@
 import java.util.List;
 import java.util.Map;
 
-import dalvik.system.DexFile;
-
-import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
-
-import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE;
-import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
-import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
-import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
-import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX;
-import static com.android.server.pm.Installer.DEXOPT_FORCE;
-import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
-import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
-import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB;
-import static com.android.server.pm.Installer.DEXOPT_ENABLE_HIDDEN_API_CHECKS;
-import static com.android.server.pm.Installer.DEXOPT_GENERATE_COMPACT_DEX;
-import static com.android.server.pm.Installer.DEXOPT_GENERATE_APP_IMAGE;
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
-import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
-
-import static com.android.server.pm.PackageManagerService.WATCHDOG_TIMEOUT;
-
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getReasonName;
-
-import static dalvik.system.DexFile.getSafeModeCompilerFilter;
-import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
-
 /**
  * Helper class for running dexopt command on packages.
  */
@@ -271,10 +267,7 @@
             return DEX_OPT_SKIPPED;
         }
 
-        // TODO(calin): there's no need to try to create the oat dir over and over again,
-        //              especially since it involve an extra installd call. We should create
-        //              if (if supported) on the fly during the dexopt call.
-        String oatDir = createOatDirIfSupported(pkg, isa);
+        String oatDir = getPackageOatDirIfSupported(pkg);
 
         Log.i(TAG, "Running dexopt (dexoptNeeded=" + dexoptNeeded + ") on: " + path
                 + " pkg=" + pkg.applicationInfo.packageName + " isa=" + isa
@@ -508,13 +501,25 @@
      */
     private String getRealCompilerFilter(ApplicationInfo info, String targetCompilerFilter,
             boolean isUsedByOtherApps) {
-        int flags = info.flags;
-        boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
         // When a priv app is configured to run out of box, only verify it.
         if (info.isPrivilegedApp() && DexManager.isPackageSelectedToRunOob(info.packageName)) {
             return "verify";
         }
-        if (vmSafeMode) {
+
+        // We force vmSafeMode on debuggable apps as well:
+        //  - the runtime ignores their compiled code
+        //  - they generally have lots of methods that could make the compiler used run
+        //    out of memory (b/130828957)
+        // Note that forcing the compiler filter here applies to all compilations (even if they
+        // are done via adb shell commands). That's ok because right now the runtime will ignore
+        // the compiled code anyway. The alternative would have been to update either
+        // PackageDexOptimizer#canOptimizePackage or PackageManagerService#getOptimizablePackages
+        // but that would have the downside of possibly producing a big odex files which would
+        // be ignored anyway.
+        boolean vmSafeModeOrDebuggable = ((info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0)
+                || ((info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
+
+        if (vmSafeModeOrDebuggable) {
             return getSafeModeCompilerFilter(targetCompilerFilter);
         }
 
@@ -627,7 +632,7 @@
     }
 
     /**
-     * Creates oat dir for the specified package if needed and supported.
+     * Gets oat dir for the specified package if needed and supported.
      * In certain cases oat directory
      * <strong>cannot</strong> be created:
      * <ul>
@@ -635,29 +640,19 @@
      *      <li>Package location is not a directory, i.e. monolithic install.</li>
      * </ul>
      *
-     * @return Absolute path to the oat directory or null, if oat directory
-     * cannot be created.
+     * @return Absolute path to the oat directory or null, if oat directories
+     * not needed or unsupported for the package.
      */
     @Nullable
-    private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) {
+    private String getPackageOatDirIfSupported(PackageParser.Package pkg) {
         if (!pkg.canHaveOatDir()) {
             return null;
         }
         File codePath = new File(pkg.codePath);
-        if (codePath.isDirectory()) {
-            // TODO(calin): why do we create this only if the codePath is a directory? (i.e for
-            //              cluster packages). It seems that the logic for the folder creation is
-            //              split between installd and here.
-            File oatDir = getOatDir(codePath);
-            try {
-                mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet);
-            } catch (InstallerException e) {
-                Slog.w(TAG, "Failed to create oat dir", e);
-                return null;
-            }
-            return oatDir.getAbsolutePath();
+        if (!codePath.isDirectory()) {
+            return null;
         }
-        return null;
+        return getOatDir(codePath).getAbsolutePath();
     }
 
     static File getOatDir(File codePath) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 57deb3f..fa570b8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -115,6 +115,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -959,7 +960,7 @@
         try {
             IApexService apex = IApexService.Stub.asInterface(
                     ServiceManager.getService("apexservice"));
-            apex.stagePackage(mResolvedBaseFile.toString());
+            apex.stagePackages(Collections.singletonList(mResolvedBaseFile.toString()));
         } catch (Throwable e) {
             // Convert all exceptions into package manager exceptions as only those are handled
             // in the code above
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ba2b37f..5b83f44 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -168,8 +168,8 @@
 import android.content.pm.InstantAppResolveInfo;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
-import android.content.pm.PackageBackwardCompatibility;
 import android.content.pm.KeySet;
+import android.content.pm.PackageBackwardCompatibility;
 import android.content.pm.PackageCleanItem;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
@@ -259,7 +259,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Base64;
-import android.util.ByteStringUtils;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.ExceptionUtils;
@@ -299,8 +298,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
-import com.android.internal.util.function.QuadFunction;
-import com.android.internal.util.function.TriFunction;
 import com.android.server.AttributeCache;
 import com.android.server.DeviceIdleController;
 import com.android.server.EventLogTags;
@@ -336,6 +333,7 @@
 import dalvik.system.VMRuntime;
 
 import libcore.io.IoUtils;
+import libcore.util.HexEncoding;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -380,7 +378,6 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
 import java.util.function.Predicate;
 
 /**
@@ -2158,6 +2155,8 @@
 
             if (allNewUsers && !update) {
                 notifyPackageAdded(packageName, res.uid);
+            } else {
+                notifyPackageChanged(packageName, res.uid);
             }
 
             // Log current value of "unknown sources" setting
@@ -8903,10 +8902,10 @@
                     + " better than this " + pkg.getLongVersionCode());
         }
 
-        // Verify certificates against what was last scanned. If it is an updated priv app, we will
-        // force re-collecting certificate.
-        final boolean forceCollect = PackageManagerServiceUtils.isApkVerificationForced(
-                disabledPkgSetting);
+        // Verify certificates against what was last scanned. If there was an upgrade or this is an
+        // updated priv app, we will force re-collecting certificate.
+        final boolean forceCollect = mIsUpgrade ||
+                PackageManagerServiceUtils.isApkVerificationForced(disabledPkgSetting);
         // Full APK verification can be skipped during certificate collection, only if the file is
         // in verified partition, or can be verified on access (when apk verity is enabled). In both
         // cases, only data in Signing Block is verified instead of the whole file.
@@ -9007,6 +9006,20 @@
         }
     }
 
+    /**
+     * Enforces that only the system UID or root's UID or shell's UID can call
+     * a method exposed via Binder.
+     *
+     * @param message used as message if SecurityException is thrown
+     * @throws SecurityException if the caller is not system or shell
+     */
+    private static void enforceSystemOrRootOrShell(String message) {
+        final int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID && uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
+            throw new SecurityException(message);
+        }
+    }
+
     @Override
     public void performFstrimIfNeeded() {
         enforceSystemOrRoot("Only the system can request fstrim");
@@ -9501,7 +9514,13 @@
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
             return false;
         }
-        return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames);
+        enforceSystemOrRootOrShell("runBackgroundDexoptJob");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     private static List<SharedLibraryInfo> findSharedLibraries(PackageParser.Package p) {
@@ -9972,8 +9991,9 @@
 
                         // lib signing cert could have rotated beyond the one expected, check to see
                         // if the new one has been blessed by the old
-                        if (!libPkg.mSigningDetails.hasSha256Certificate(
-                                ByteStringUtils.fromHexToByteArray(expectedCertDigests[0]))) {
+                        byte[] digestBytes = HexEncoding.decode(
+                                expectedCertDigests[0], false /* allowSingleChar */);
+                        if (!libPkg.mSigningDetails.hasSha256Certificate(digestBytes)) {
                             throw new PackageManagerException(
                                     INSTALL_FAILED_MISSING_SHARED_LIBRARY,
                                     "Package " + packageName + " requires differently signed" +
@@ -13744,6 +13764,22 @@
     }
 
     @Override
+    public void notifyPackageChanged(String packageName, int uid) {
+        final PackageListObserver[] observers;
+        synchronized (mPackages) {
+            if (mPackageListObservers.size() == 0) {
+                return;
+            }
+            final PackageListObserver[] observerArray =
+                    new PackageListObserver[mPackageListObservers.size()];
+            observers = mPackageListObservers.toArray(observerArray);
+        }
+        for (int i = observers.length - 1; i >= 0; --i) {
+            observers[i].onPackageChanged(packageName, uid);
+        }
+    }
+
+    @Override
     public void notifyPackageRemoved(String packageName, int uid) {
         final PackageListObserver[] observers;
         synchronized (mPackages) {
@@ -18307,6 +18343,12 @@
     @Override
     public boolean isPackageDeviceAdminOnAnyUser(String packageName) {
         final int callingUid = Binder.getCallingUid();
+        if (checkUidPermission(android.Manifest.permission.MANAGE_USERS, callingUid)
+                != PERMISSION_GRANTED) {
+            EventLog.writeEvent(0x534e4554, "128599183", -1, "");
+            throw new SecurityException(android.Manifest.permission.MANAGE_USERS
+                    + " permission is required to call this API");
+        }
         if (getInstantAppPackageName(callingUid) != null
                 && !isCallerSameApp(packageName, callingUid)) {
             return false;
@@ -24963,5 +25005,6 @@
     void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
         boolean includeStopped, int appId, int[] userIds, int[] instantUserIds);
     void notifyPackageAdded(String packageName, int uid);
+    void notifyPackageChanged(String packageName, int uid);
     void notifyPackageRemoved(String packageName, int uid);
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e1c1302..242f9c3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1334,6 +1334,7 @@
         }
         boolean result = mInterface.runBackgroundDexoptJob(packageNames.isEmpty() ? null :
                 packageNames);
+        getOutPrintWriter().println(result ? "Success" : "Failure");
         return result ? 0 : -1;
     }
 
diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
index 88d9e52..9de6469 100644
--- a/services/core/java/com/android/server/pm/dex/DexLogger.java
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -16,12 +16,12 @@
 
 package com.android.server.pm.dex;
 
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.os.RemoteException;
-
 import android.util.ArraySet;
-import android.util.ByteStringUtils;
 import android.util.EventLog;
 import android.util.PackageUtils;
 import android.util.Slog;
@@ -31,11 +31,11 @@
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
 
+import libcore.util.HexEncoding;
+
 import java.io.File;
 import java.util.Set;
 
-import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
-
 /**
  * This class is responsible for logging data about secondary dex files.
  * The data logged includes hashes of the name and content of each file.
@@ -91,7 +91,7 @@
         String message = PackageUtils.computeSha256Digest(dexFileName.getBytes());
         // Valid SHA256 will be 256 bits, 32 bytes.
         if (hash.length == 32) {
-            message = message + ' ' + ByteStringUtils.toHexString(hash);
+            message = message + ' ' + HexEncoding.encodeToString(hash);
         }
 
         writeDclEvent(ownerUid, message);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0149d30..caf0b92 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6482,7 +6482,7 @@
             case KeyEvent.KEYCODE_VOLUME_MUTE:
                 return mDockMode != Intent.EXTRA_DOCK_STATE_UNDOCKED;
 
-            // ignore media and camera keys
+            // ignore media keys
             case KeyEvent.KEYCODE_MUTE:
             case KeyEvent.KEYCODE_HEADSETHOOK:
             case KeyEvent.KEYCODE_MEDIA_PLAY:
@@ -6495,7 +6495,6 @@
             case KeyEvent.KEYCODE_MEDIA_RECORD:
             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
             case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
-            case KeyEvent.KEYCODE_CAMERA:
                 return false;
         }
         return true;
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index f7cc443..2700f9d 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.net.TrafficStats;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.FileObserver;
@@ -42,13 +41,13 @@
 import android.util.ArrayMap;
 import android.util.DataUnit;
 import android.util.Slog;
+import android.util.StatsLog;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.EventLogTags;
-import com.android.server.IoThread;
 import com.android.server.SystemService;
 import com.android.server.pm.InstructionSets;
 import com.android.server.pm.PackageManagerService;
@@ -499,9 +498,15 @@
             notification.flags |= Notification.FLAG_NO_CLEAR;
             mNotifManager.notifyAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
                     notification, UserHandle.ALL);
+            StatsLog.write(StatsLog.LOW_STORAGE_STATE_CHANGED,
+                    Objects.toString(vol.getDescription()),
+                    StatsLog.LOW_STORAGE_STATE_CHANGED__STATE__ON);
         } else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
             mNotifManager.cancelAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
                     UserHandle.ALL);
+            StatsLog.write(StatsLog.LOW_STORAGE_STATE_CHANGED,
+                    Objects.toString(vol.getDescription()),
+                    StatsLog.LOW_STORAGE_STATE_CHANGED__STATE__OFF);
         }
     }
 
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index 296a652..28c171b 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerService.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -108,7 +108,7 @@
 
     private static RulesManagerService create(Context context) {
         RulesManagerServiceHelperImpl helper = new RulesManagerServiceHelperImpl(context);
-        File baseVersionFile = new File(TimeZoneDataFiles.getRuntimeModuleTzVersionFile());
+        File baseVersionFile = new File(TimeZoneDataFiles.getTimeZoneModuleTzVersionFile());
         File tzDataDir = new File(TimeZoneDataFiles.getDataTimeZoneRootDir());
         return new RulesManagerService(
                 helper /* permissionHelper */,
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index f08e585..6ea274d 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -403,6 +403,10 @@
                     || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
                 return;
             }
+            ITvInputHardwareCallback callback = connection.getCallbackLocked();
+            if (callback != null) {
+                callback.asBinder().unlinkToDeath(connection, 0);
+            }
             connection.resetLocked(null, null, null, null, null);
         }
     }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index b2677cb..ef57ca1 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -840,6 +840,10 @@
     private void setStateLocked(String inputId, int state, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         TvInputState inputState = userState.inputMap.get(inputId);
+        if (inputState == null) {
+            Slog.e(TAG, "failed to setStateLocked - unknown input id " + inputId);
+            return;
+        }
         ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
         int oldState = inputState.state;
         inputState.state = state;
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 755a571..95051de 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -27,6 +27,7 @@
 import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
 
 import android.content.Context;
+import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.IBinder;
@@ -285,10 +286,27 @@
             if (displayHandle != null) {
                 Surface sur = new Surface();
                 sur.copyFrom(mSurfaceControl);
-                SurfaceControl.screenshot(displayHandle, sur);
-                t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT);
-                t.setAlpha(mSurfaceControl, 0);
-                t.show(mSurfaceControl);
+                GraphicBuffer gb = SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(
+                        new Rect(), 0 /* width */, 0 /* height */, 0 /* minLayer */,
+                        0 /* maxLayer */, false /* useIdentityTransform */, 0 /* rotation */);
+                if (gb != null) {
+                    try {
+                        sur.attachAndQueueBuffer(gb);
+                    } catch (RuntimeException e) {
+                        Slog.w(TAG, "Failed to attach screenshot - " + e.getMessage());
+                    }
+                    // If the screenshot contains secure layers, we have to make sure the
+                    // screenshot surface we display it in also has FLAG_SECURE so that
+                    // the user can not screenshot secure layers via the screenshot surface.
+                    if (gb.doesContainSecureLayers()) {
+                        t.setSecure(mSurfaceControl, true);
+                    }
+                    t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT);
+                    t.setAlpha(mSurfaceControl, 0);
+                    t.show(mSurfaceControl);
+                } else {
+                    Slog.w(TAG, "Unable to take screenshot of display " + displayId);
+                }
                 sur.destroy();
             } else {
                 Slog.w(TAG, "Built-in display " + displayId + " is null.");
diff --git a/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp b/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp
index f7ca363..be3e878 100644
--- a/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp
+++ b/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp
@@ -24,7 +24,7 @@
 
 #include <android/hardware/broadcastradio/1.1/IBroadcastRadio.h>
 #include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
-#include <android/hidl/manager/1.0/IServiceManager.h>
+#include <android/hidl/manager/1.2/IServiceManager.h>
 #include <broadcastradio-utils-1x/Utils.h>
 #include <core_jni_helpers.h>
 #include <hidl/ServiceManagement.h>
@@ -123,13 +123,13 @@
     auto& ctx = getNativeContext(nativeContext);
 
     // Get list of registered HIDL HAL implementations.
-    auto manager = hardware::defaultServiceManager();
+    auto manager = hardware::defaultServiceManager1_2();
     hidl_vec<hidl_string> services;
     if (manager == nullptr) {
         ALOGE("Can't reach service manager, using default service implementation only");
         services = std::vector<hidl_string>({ "default" });
     } else {
-        manager->listByInterface(V1_0::IBroadcastRadioFactory::descriptor,
+        manager->listManifestByInterface(V1_0::IBroadcastRadioFactory::descriptor,
                 [&services](const hidl_vec<hidl_string> &registered) {
             services = registered;
         });
diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp
index 98e4343..8b2cbbd 100644
--- a/services/core/xsd/Android.bp
+++ b/services/core/xsd/Android.bp
@@ -4,3 +4,11 @@
     api_dir: "schema",
     package_name: "com.android.server.pm.permission.configfile",
 }
+
+
+xsd_config {
+    name: "platform-compat-config",
+    srcs: ["platform-compat-config.xsd"],
+    api_dir: "platform-compat-schema",
+    package_name: "com.android.server.compat.config",
+}
diff --git a/services/core/xsd/platform-compat-config.xsd b/services/core/xsd/platform-compat-config.xsd
new file mode 100644
index 0000000..ee39e50
--- /dev/null
+++ b/services/core/xsd/platform-compat-config.xsd
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 defines the format of the XML file generated by
+  ~ com.android.compat.annotation.ChangeIdProcessor annotation processor (from
+  ~ tools/platform-compat), and is parsed in com/android/server/compat/CompatConfig.java.
+-->
+<xs:schema version="2.0" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <xs:complexType name="change">
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute type="xs:long" name="id" use="required"/>
+                <xs:attribute type="xs:string" name="name" use="required"/>
+                <xs:attribute type="xs:boolean" name="disabled"/>
+                <xs:attribute type="xs:int" name="enableAfterTargetSdk"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:element name="config">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="compat-change" type="change" maxOccurs="unbounded"
+                            minOccurs="0"/>
+            </xs:sequence>
+        </xs:complexType>
+        <xs:unique name="UniqueId">
+            <xs:selector xpath="compat-change" />
+            <xs:field xpath="@id" />
+        </xs:unique>
+    </xs:element>
+</xs:schema>
+
+
+
+
diff --git a/services/core/xsd/platform-compat-schema/current.txt b/services/core/xsd/platform-compat-schema/current.txt
new file mode 100644
index 0000000..8456785
--- /dev/null
+++ b/services/core/xsd/platform-compat-schema/current.txt
@@ -0,0 +1,31 @@
+// Signature format: 2.0
+package com.android.server.compat.config {
+
+  public class Change {
+    ctor public Change();
+    method public boolean getDisabled();
+    method public int getEnableAfterTargetSdk();
+    method public long getId();
+    method public String getName();
+    method public String getValue();
+    method public void setDisabled(boolean);
+    method public void setEnableAfterTargetSdk(int);
+    method public void setId(long);
+    method public void setName(String);
+    method public void setValue(String);
+  }
+
+  public class Config {
+    ctor public Config();
+    method public java.util.List<com.android.server.compat.config.Change> getCompatChange();
+  }
+
+  public class XmlParser {
+    ctor public XmlParser();
+    method public static com.android.server.compat.config.Config read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+  }
+
+}
+
diff --git a/packages/ExtServices/MODULE_LICENSE_APACHE2 b/services/core/xsd/platform-compat-schema/last_current.txt
similarity index 100%
copy from packages/ExtServices/MODULE_LICENSE_APACHE2
copy to services/core/xsd/platform-compat-schema/last_current.txt
diff --git a/packages/ExtServices/MODULE_LICENSE_APACHE2 b/services/core/xsd/platform-compat-schema/last_removed.txt
similarity index 100%
copy from packages/ExtServices/MODULE_LICENSE_APACHE2
copy to services/core/xsd/platform-compat-schema/last_removed.txt
diff --git a/services/core/xsd/platform-compat-schema/removed.txt b/services/core/xsd/platform-compat-schema/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/services/core/xsd/platform-compat-schema/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 1c9782f..f2560b3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -15,7 +15,6 @@
  */
 package com.android.server.devicepolicy;
 
-import android.annotation.UserIdInt;
 import android.app.admin.IDevicePolicyManager;
 import android.content.ComponentName;
 import android.os.PersistableBundle;
@@ -159,4 +158,9 @@
     @Override
     public void setDefaultSmsApplication(ComponentName admin, String packageName) {
     }
+
+    @Override
+    public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) {
+        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 9c6b52f..d1128af 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -60,7 +60,6 @@
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
-
 import static android.provider.Telephony.Carriers.DPC_URI;
 import static android.provider.Telephony.Carriers.ENFORCE_KEY;
 import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
@@ -69,11 +68,10 @@
         .PROVISIONING_ENTRY_POINT_ADB;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
         .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
-
-import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
-import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
-
-
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager
+        .ADMIN_TYPE_DEVICE_OWNER;
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager
+        .ADMIN_TYPE_PROFILE_OWNER;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -219,11 +217,11 @@
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.StatLogger;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
-import com.android.internal.util.StatLogger;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -3934,6 +3932,9 @@
 
     @Override
     public boolean isSeparateProfileChallengeAllowed(int userHandle) {
+        if (!isCallerWithSystemUid()) {
+            throw new SecurityException("Caller must be system");
+        }
         ComponentName profileOwner = getProfileOwner(userHandle);
         // Profile challenge is supported on N or newer release.
         return profileOwner != null &&
@@ -5185,7 +5186,8 @@
     @Override
     public void enforceCanManageCaCerts(ComponentName who, String callerPackage) {
         if (who == null) {
-            if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) {
+            if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(),
+                    DELEGATION_CERT_INSTALL)) {
                 mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
             }
         } else {
@@ -5361,7 +5363,8 @@
             if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) {
                 throw new SecurityException("Caller not from device owner user");
             }
-            if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) {
+            if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(),
+                    DELEGATION_CERT_INSTALL)) {
                 throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() +
                         "has no permission to generate keys.");
             }
@@ -5763,15 +5766,14 @@
      * @param scope the delegation scope to be checked.
      * @return {@code true} if the calling process is a delegate of {@code scope}.
      */
-    private boolean isCallerDelegate(String callerPackage, String scope) {
+    private boolean isCallerDelegate(String callerPackage, int callerUid, String scope) {
         Preconditions.checkNotNull(callerPackage, "callerPackage is null");
         if (!Arrays.asList(DELEGATIONS).contains(scope)) {
             throw new IllegalArgumentException("Unexpected delegation scope: " + scope);
         }
 
         // Retrieve the UID and user ID of the calling process.
-        final int callingUid = mInjector.binderGetCallingUid();
-        final int userId = UserHandle.getUserId(callingUid);
+        final int userId = UserHandle.getUserId(callerUid);
         synchronized (getLockObject()) {
             // Retrieve user policy data.
             final DevicePolicyData policy = getUserData(userId);
@@ -5784,7 +5786,7 @@
                     final int uid = mInjector.getPackageManager()
                             .getPackageUidAsUser(callerPackage, userId);
                     // Return true if the caller is actually callerPackage.
-                    return uid == callingUid;
+                    return uid == callerUid;
                 } catch (NameNotFoundException e) {
                     // Ignore.
                 }
@@ -5815,7 +5817,7 @@
                 getActiveAdminForCallerLocked(who, reqPolicy);
             }
         // If no ComponentName is given ensure calling process has scope delegation.
-        } else if (!isCallerDelegate(callerPackage, scope)) {
+        } else if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(), scope)) {
             throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid()
                     + " is not a delegate of scope " + scope + ".");
         }
@@ -7780,6 +7782,13 @@
     }
 
     @Override
+    public ComponentName getProfileOwnerAsUser(int userHandle) {
+        enforceCrossUsersPermission(userHandle);
+
+        return getProfileOwner(userHandle);
+    }
+
+    @Override
     public ComponentName getProfileOwner(int userHandle) {
         if (!mHasFeature) {
             return null;
@@ -7822,6 +7831,68 @@
         return getApplicationLabel(profileOwner.getPackageName(), userHandle);
     }
 
+    @Override
+    public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) {
+        // If the caller is not a system app then it should only be able to check its own device
+        // identifier access.
+        int callingUid = mInjector.binderGetCallingUid();
+        int callingPid = mInjector.binderGetCallingPid();
+        if (UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID
+                && (callingUid != uid || callingPid != pid)) {
+            String message = String.format(
+                    "Calling uid %d, pid %d cannot check device identifier access for package %s "
+                            + "(uid=%d, pid=%d)", callingUid, callingPid, packageName, uid, pid);
+            Log.w(LOG_TAG, message);
+            throw new SecurityException(message);
+        }
+        // Verify that the specified packages matches the provided uid.
+        int userId = UserHandle.getUserId(uid);
+        try {
+            ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0, userId);
+            // Since this call goes directly to PackageManagerService a NameNotFoundException is not
+            // thrown but null data can be returned; if the appInfo for the specified package cannot
+            // be found then return false to prevent crashing the app.
+            if (appInfo == null) {
+                Log.w(LOG_TAG,
+                        String.format("appInfo could not be found for package %s", packageName));
+                return false;
+            } else if (uid != appInfo.uid) {
+                String message = String.format("Package %s (uid=%d) does not match provided uid %d",
+                        packageName, appInfo.uid, uid);
+                Log.w(LOG_TAG, message);
+                throw new SecurityException(message);
+            }
+        } catch (RemoteException e) {
+            // If an exception is caught obtaining the appInfo just return false to prevent crashing
+            // apps due to an internal error.
+            Log.e(LOG_TAG, "Exception caught obtaining appInfo for package " + packageName, e);
+            return false;
+        }
+        // A device or profile owner must also have the READ_PHONE_STATE permission to access device
+        // identifiers. If the package being checked does not have this permission then deny access.
+        if (mContext.checkPermission(android.Manifest.permission.READ_PHONE_STATE, pid, uid)
+                != PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+
+        // Allow access to the device owner or delegate cert installer.
+        ComponentName deviceOwner = getDeviceOwnerComponent(true);
+        if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
+                || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
+            return true;
+        }
+        // Allow access to the profile owner for the specified user, or delegate cert installer
+        ComponentName profileOwner = getProfileOwnerAsUser(userId);
+        if (profileOwner != null && (profileOwner.getPackageName().equals(packageName)
+                || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
+            return true;
+        }
+
+        Log.w(LOG_TAG, String.format("Package %s (uid=%d, pid=%d) cannot access Device IDs",
+                packageName, uid, pid));
+        return false;
+    }
+
     /**
      * Canonical name for a given package.
      */
@@ -8263,7 +8334,8 @@
 
     @Override
     public boolean isCallerApplicationRestrictionsManagingPackage(String callerPackage) {
-        return isCallerDelegate(callerPackage, DELEGATION_APP_RESTRICTIONS);
+        return isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(),
+                DELEGATION_APP_RESTRICTIONS);
     }
 
     @Override
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f0292aa..1ce72c5 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -37,6 +37,7 @@
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
 import android.hardware.display.DisplayManagerInternal;
+import android.net.ConnectivityModuleConnector;
 import android.net.NetworkStackClient;
 import android.os.BaseBundle;
 import android.os.Binder;
@@ -79,6 +80,7 @@
 import com.android.server.broadcastradio.BroadcastRadioService;
 import com.android.server.camera.CameraServiceProxy;
 import com.android.server.clipboard.ClipboardService;
+import com.android.server.compat.PlatformCompat;
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.coverage.CoverageService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
@@ -561,6 +563,13 @@
         SystemServerInitThreadPool.get().submit(SystemConfig::getInstance, TAG_SYSTEM_CONFIG);
         traceEnd();
 
+        // Platform compat service is used by ActivityManagerService, PackageManagerService, and
+        // possibly others in the future. b/135010838.
+        traceBeginAndSlog("PlatformCompat");
+        ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE,
+                new PlatformCompat(mSystemContext));
+        traceEnd();
+
         // Wait for installd to finish starting up so that it has a chance to
         // create critical directories such as /data/user with the appropriate
         // permissions.  We need this to complete before we initialize other services.
@@ -975,6 +984,7 @@
             traceBeginAndSlog("PinnerService");
             mSystemServiceManager.startService(PinnerService.class);
             traceEnd();
+
         } catch (RuntimeException e) {
             Slog.e("System", "******************************************");
             Slog.e("System", "************ Failure starting core service", e);
@@ -1123,6 +1133,14 @@
             mSystemServiceManager.startService(ClipboardService.class);
             traceEnd();
 
+            traceBeginAndSlog("InitConnectivityModuleConnector");
+            try {
+                ConnectivityModuleConnector.getInstance().init(context);
+            } catch (Throwable e) {
+                reportWtf("initializing ConnectivityModuleConnector", e);
+            }
+            traceEnd();
+
             traceBeginAndSlog("InitNetworkStackClient");
             try {
                 NetworkStackClient.getInstance().init();
@@ -1303,16 +1321,13 @@
             }
             traceEnd();
 
-            final boolean useNewTimeServices = true;
-            if (useNewTimeServices) {
-                traceBeginAndSlog("StartTimeDetectorService");
-                try {
-                    mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
-                } catch (Throwable e) {
-                    reportWtf("starting StartTimeDetectorService service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartTimeDetectorService");
+            try {
+                mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
+            } catch (Throwable e) {
+                reportWtf("starting StartTimeDetectorService service", e);
             }
+            traceEnd();
 
             if (!isWatch) {
                 traceBeginAndSlog("StartSearchManagerService");
@@ -1487,12 +1502,7 @@
             if (!isWatch) {
                 traceBeginAndSlog("StartNetworkTimeUpdateService");
                 try {
-                    if (useNewTimeServices) {
-                        networkTimeUpdater = new NewNetworkTimeUpdateService(context);
-                    } else {
-                        networkTimeUpdater = new OldNetworkTimeUpdateService(context);
-                    }
-                    Slog.d(TAG, "Using networkTimeUpdater class=" + networkTimeUpdater.getClass());
+                    networkTimeUpdater = new NetworkTimeUpdateServiceImpl(context);
                     ServiceManager.addService("network_time_update_service", networkTimeUpdater);
                 } catch (Throwable e) {
                     reportWtf("starting NetworkTimeUpdate service", e);
@@ -1679,6 +1689,10 @@
         mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
         traceEnd();
 
+        if (safeMode) {
+            mActivityManagerService.enterSafeMode();
+        }
+
         // MMS service broker
         traceBeginAndSlog("StartMmsService");
         mmsService = mSystemServiceManager.startService(MmsServiceBroker.class);
@@ -1935,7 +1949,7 @@
                 // ActivityManagerService.mSystemReady and ActivityManagerService.mProcessesReady
                 // are set to true. Be careful if moving this to a different place in the
                 // startup sequence.
-                NetworkStackClient.getInstance().start(context);
+                NetworkStackClient.getInstance().start();
             } catch (Throwable e) {
                 reportWtf("starting Network Stack", e);
             }
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 4b0a27b..8f8f9f9 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -65,10 +65,10 @@
     name: "services.net",
     srcs: ["java/**/*.java"],
     static_libs: [
-        "dnsresolver_aidl_interface-java",
+        "dnsresolver_aidl_interface-V2-java",
         "ipmemorystore-client",
         "netd_aidl_interface-java",
-        "networkstack-aidl-interfaces-java",
+        "networkstack-aidl-interfaces-V3-java",
     ],
 }
 
@@ -81,7 +81,7 @@
         "java/android/net/ipmemorystore/**/*.java",
     ],
     static_libs: [
-        "ipmemorystore-aidl-interfaces-java",
+        "ipmemorystore-aidl-interfaces-V3-java",
     ],
 }
 
diff --git a/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStore.aidl b/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStore.aidl
index 1e688d0..30893b2 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStore.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStore.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 interface IIpMemoryStore {
   oneway void storeNetworkAttributes(String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnStatusListener listener);
diff --git a/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStoreCallbacks.aidl b/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStoreCallbacks.aidl
index cf02c26..535ae2c 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStoreCallbacks.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStoreCallbacks.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 interface IIpMemoryStoreCallbacks {
   oneway void onIpMemoryStoreFetched(in android.net.IIpMemoryStore ipMemoryStore);
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/Blob.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/Blob.aidl
index 291dbef..6d2dc0c 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/Blob.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/Blob.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ipmemorystore;
 parcelable Blob {
   byte[] data;
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
index 52f40d4..48c1fb8 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ipmemorystore;
 interface IOnBlobRetrievedListener {
   oneway void onBlobRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in String name, in android.net.ipmemorystore.Blob data);
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
index 7853514..aebc724 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ipmemorystore;
 interface IOnL2KeyResponseListener {
   oneway void onL2KeyResponse(in android.net.ipmemorystore.StatusParcelable status, in String l2Key);
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
index 3dd2ae6..b66db5a 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ipmemorystore;
 interface IOnNetworkAttributesRetrievedListener {
   oneway void onNetworkAttributesRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes);
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
index 46d4ecb..e9f2db4 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ipmemorystore;
 interface IOnSameL3NetworkResponseListener {
   oneway void onSameL3NetworkResponse(in android.net.ipmemorystore.StatusParcelable status, in android.net.ipmemorystore.SameL3NetworkResponseParcelable response);
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnStatusListener.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnStatusListener.aidl
index 54e654b..49172ce 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnStatusListener.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ipmemorystore;
 interface IOnStatusListener {
   oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status);
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
index 9531ea3..188db20 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ipmemorystore;
 parcelable NetworkAttributesParcelable {
   byte[] assignedV4Address;
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
index 414272b..7a2ed48 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ipmemorystore;
 parcelable SameL3NetworkResponseParcelable {
   String l2Key1;
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/StatusParcelable.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/StatusParcelable.aidl
index 92c6779..d9b0678 100644
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/StatusParcelable.aidl
+++ b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/StatusParcelable.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ipmemorystore;
 parcelable StatusParcelable {
   int resultCode;
diff --git a/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl b/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
index 31891de..07ff321 100644
--- a/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
+++ b/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 parcelable DhcpResultsParcelable {
   android.net.StaticIpConfiguration baseConfiguration;
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl b/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl
index 029968b..8aa68bd 100644
--- a/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl
+++ b/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 interface INetworkMonitor {
   oneway void start();
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
index ee9871d..ea93729 100644
--- a/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
+++ b/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 interface INetworkMonitorCallbacks {
   oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor);
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl b/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
index 7da11e4..e3a83d1 100644
--- a/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
+++ b/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 interface INetworkStackConnector {
   oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl b/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
index f6ca6f7..3112a08 100644
--- a/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
+++ b/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 interface INetworkStackStatusCallback {
   oneway void onStatusAvailable(int statusCode);
diff --git a/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl b/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
index c80a787..f846b26 100644
--- a/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
+++ b/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 parcelable InitialConfigurationParcelable {
   android.net.LinkAddress[] ipAddresses;
diff --git a/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
index 65de883..de75940 100644
--- a/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
+++ b/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 parcelable NattKeepalivePacketDataParcelable {
   byte[] srcAddress;
diff --git a/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl b/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
index 2de790b..cf0fbce9 100644
--- a/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
+++ b/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 parcelable PrivateDnsConfigParcel {
   String hostname;
diff --git a/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl b/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
index 3a6c304..c0f2d4d 100644
--- a/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
+++ b/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 parcelable ProvisioningConfigurationParcelable {
   boolean enableIPv4;
diff --git a/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
index e121c06..5926794 100644
--- a/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
+++ b/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net;
 parcelable TcpKeepalivePacketDataParcelable {
   byte[] srcAddress;
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
index 67193ae..7ab156f 100644
--- a/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
+++ b/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.dhcp;
 parcelable DhcpServingParamsParcel {
   int serverAddr;
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
index 9143158..d281ecf 100644
--- a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
+++ b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.dhcp;
 interface IDhcpServer {
   oneway void start(in android.net.INetworkStackStatusCallback cb);
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
index dcc4489..98be0ab 100644
--- a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
+++ b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.dhcp;
 interface IDhcpServerCallbacks {
   oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
diff --git a/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl b/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl
index 176a5ce..85c8676 100644
--- a/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl
+++ b/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ip;
 interface IIpClient {
   oneway void completedPreDhcpAction();
diff --git a/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
index d6bc808..7fe39ed 100644
--- a/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
+++ b/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
@@ -1,3 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
 package android.net.ip;
 interface IIpClientCallbacks {
   oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
diff --git a/services/net/java/android/net/ConnectivityModuleConnector.java b/services/net/java/android/net/ConnectivityModuleConnector.java
new file mode 100644
index 0000000..19ade33
--- /dev/null
+++ b/services/net/java/android/net/ConnectivityModuleConnector.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.util.SharedLog;
+import android.os.Build;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.format.DateUtils;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.PrintWriter;
+
+/**
+ * Class used to communicate to the various networking mainline modules running in the network stack
+ * process from {@link com.android.server.SystemServer}.
+ * @hide
+ */
+public class ConnectivityModuleConnector {
+    private static final String TAG = ConnectivityModuleConnector.class.getSimpleName();
+    private static final String IN_PROCESS_SUFFIX = ".InProcess";
+
+    private static final String PREFS_FILE = "ConnectivityModuleConnector.xml";
+    private static final String PREF_KEY_LAST_CRASH_TIME = "lastcrash_time";
+    private static final String CONFIG_MIN_CRASH_INTERVAL_MS = "min_crash_interval";
+    private static final String CONFIG_MIN_UPTIME_BEFORE_CRASH_MS = "min_uptime_before_crash";
+    private static final String CONFIG_ALWAYS_RATELIMIT_NETWORKSTACK_CRASH =
+            "always_ratelimit_networkstack_crash";
+
+    // Even if the network stack is lost, do not crash the system more often than this.
+    // Connectivity would be broken, but if the user needs the device for something urgent
+    // (like calling emergency services) we should not bootloop the device.
+    // This is the default value: the actual value can be adjusted via device config.
+    private static final long DEFAULT_MIN_CRASH_INTERVAL_MS = 6 * DateUtils.HOUR_IN_MILLIS;
+
+    // Even if the network stack is lost, do not crash the system server if it was less than
+    // this much after boot. This avoids bootlooping the device, and crashes should address very
+    // infrequent failures, not failures on boot.
+    private static final long DEFAULT_MIN_UPTIME_BEFORE_CRASH_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
+
+    private static ConnectivityModuleConnector sInstance;
+
+    private Context mContext;
+    @GuardedBy("mLog")
+    private final SharedLog mLog = new SharedLog(TAG);
+    @GuardedBy("mHealthListeners")
+    private final ArraySet<ConnectivityModuleHealthListener> mHealthListeners = new ArraySet<>();
+    @NonNull
+    private final Dependencies mDeps;
+
+    private ConnectivityModuleConnector() {
+        this(new DependenciesImpl());
+    }
+
+    @VisibleForTesting
+    ConnectivityModuleConnector(@NonNull Dependencies deps) {
+        mDeps = deps;
+    }
+
+    /**
+     * Get the {@link ConnectivityModuleConnector} singleton instance.
+     */
+    public static synchronized ConnectivityModuleConnector getInstance() {
+        if (sInstance == null) {
+            sInstance = new ConnectivityModuleConnector();
+        }
+        return sInstance;
+    }
+
+    /**
+     * Initialize the network stack connector. Should be called only once on device startup, before
+     * any client attempts to use the network stack.
+     */
+    public void init(Context context) {
+        log("Network stack init");
+        mContext = context;
+    }
+
+    /**
+     * Callback interface for severe failures of the NetworkStack.
+     *
+     * <p>Useful for health monitors such as PackageWatchdog.
+     */
+    public interface ConnectivityModuleHealthListener {
+        /**
+         * Called when there is a severe failure of the network stack.
+         * @param packageName Package name of the network stack.
+         */
+        void onNetworkStackFailure(@NonNull String packageName);
+    }
+
+    /**
+     * Callback invoked by the connector once the connection to the corresponding module is
+     * established.
+     */
+    public interface ModuleServiceCallback {
+        /**
+         * Invoked when the corresponding service has connected.
+         *
+         * @param iBinder Binder object for the service.
+         */
+        void onModuleServiceConnected(@NonNull IBinder iBinder);
+    }
+
+    /**
+     * Interface used to determine the intent to use to bind to the module. Useful for testing.
+     */
+    @VisibleForTesting
+    protected interface Dependencies {
+        /**
+         * Determine the intent to use to bind to the module.
+         * @return null if the intent could not be resolved, the intent otherwise.
+         */
+        @Nullable
+        Intent getModuleServiceIntent(
+                @NonNull PackageManager pm, @NonNull String serviceIntentBaseAction,
+                @NonNull String servicePermissionName, boolean inSystemProcess);
+    }
+
+    private static class DependenciesImpl implements Dependencies {
+        @Nullable
+        @Override
+        public Intent getModuleServiceIntent(
+                @NonNull PackageManager pm, @NonNull String serviceIntentBaseAction,
+                @NonNull String servicePermissionName, boolean inSystemProcess) {
+            final Intent intent =
+                    new Intent(inSystemProcess
+                            ? serviceIntentBaseAction + IN_PROCESS_SUFFIX
+                            : serviceIntentBaseAction);
+            final ComponentName comp = intent.resolveSystemService(pm, 0);
+            if (comp == null) {
+                return null;
+            }
+            intent.setComponent(comp);
+
+            final int uid;
+            try {
+                uid = pm.getPackageUidAsUser(comp.getPackageName(), UserHandle.USER_SYSTEM);
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new SecurityException(
+                        "Could not check network stack UID; package not found.", e);
+            }
+
+            final int expectedUid =
+                    inSystemProcess ? Process.SYSTEM_UID : Process.NETWORK_STACK_UID;
+            if (uid != expectedUid) {
+                throw new SecurityException("Invalid network stack UID: " + uid);
+            }
+
+            if (!inSystemProcess) {
+                checkModuleServicePermission(pm, comp, servicePermissionName);
+            }
+
+            return intent;
+        }
+    }
+
+
+    /**
+     * Add a {@link ConnectivityModuleHealthListener} to listen to network stack health events.
+     */
+    public void registerHealthListener(@NonNull ConnectivityModuleHealthListener listener) {
+        synchronized (mHealthListeners) {
+            mHealthListeners.add(listener);
+        }
+    }
+
+    /**
+     * Start a module running in the network stack or system_server process. Should be called only
+     * once for each module per device startup.
+     *
+     * <p>This method will start a networking module either in the network stack
+     * process, or inside the system server on devices that do not support the corresponding
+     * mainline network . The corresponding networking module service's binder
+     * object will then be delivered asynchronously via the provided {@link ModuleServiceCallback}.
+     *
+     * @param serviceIntentBaseAction Base action to use for constructing the intent needed to
+     *                                bind to the corresponding module.
+     * @param servicePermissionName Permission to be held by the corresponding module.
+     */
+    public void startModuleService(
+            @NonNull String serviceIntentBaseAction,
+            @NonNull String servicePermissionName,
+            @NonNull ModuleServiceCallback callback) {
+        log("Starting networking module " + serviceIntentBaseAction);
+
+        final PackageManager pm = mContext.getPackageManager();
+
+        // Try to bind in-process if the device was shipped with an in-process version
+        Intent intent = mDeps.getModuleServiceIntent(pm, serviceIntentBaseAction,
+                servicePermissionName, true /* inSystemProcess */);
+
+        // Otherwise use the updatable module version
+        if (intent == null) {
+            intent = mDeps.getModuleServiceIntent(pm, serviceIntentBaseAction,
+                    servicePermissionName, false /* inSystemProcess */);
+            log("Starting networking module in network_stack process");
+        } else {
+            log("Starting networking module in system_server process");
+        }
+
+        if (intent == null) {
+            maybeCrashWithTerribleFailure("Could not resolve the networking module", null);
+            return;
+        }
+
+        final String packageName = intent.getComponent().getPackageName();
+
+        // Start the network stack. The service will be added to the service manager by the
+        // corresponding client in ModuleServiceCallback.onModuleServiceConnected().
+        if (!mContext.bindServiceAsUser(
+                intent, new ModuleServiceConnection(packageName, callback),
+                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) {
+            maybeCrashWithTerribleFailure(
+                    "Could not bind to networking module in-process, or in app with "
+                            + intent, packageName);
+            return;
+        }
+
+        log("Networking module service start requested");
+    }
+
+    private class ModuleServiceConnection implements ServiceConnection {
+        @NonNull
+        private final String mPackageName;
+        @NonNull
+        private final ModuleServiceCallback mModuleServiceCallback;
+
+        private ModuleServiceConnection(
+                @NonNull String packageName,
+                @NonNull ModuleServiceCallback moduleCallback) {
+            mPackageName = packageName;
+            mModuleServiceCallback = moduleCallback;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            logi("Networking module service connected");
+            mModuleServiceCallback.onModuleServiceConnected(service);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            // onServiceDisconnected is not being called on device shutdown, so this method being
+            // called always indicates a bad state for the system server.
+            // This code path is only run by the system server: only the system server binds
+            // to the NetworkStack as a service. Other processes get the NetworkStack from
+            // the ServiceManager.
+            maybeCrashWithTerribleFailure("Lost network stack", mPackageName);
+        }
+    }
+
+    private static void checkModuleServicePermission(
+            @NonNull PackageManager pm, @NonNull ComponentName comp,
+            @NonNull String servicePermissionName) {
+        final int hasPermission =
+                pm.checkPermission(servicePermissionName, comp.getPackageName());
+        if (hasPermission != PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    "Networking module does not have permission " + servicePermissionName);
+        }
+    }
+
+    private synchronized void maybeCrashWithTerribleFailure(@NonNull String message,
+            @Nullable String packageName) {
+        logWtf(message, null);
+        // Called DeviceConfig to minimize merge conflicts
+        final DeviceConfigStub DeviceConfig = new DeviceConfigStub(mContext);
+        // uptime is monotonic even after a framework restart
+        final long uptime = SystemClock.elapsedRealtime();
+        final long now = System.currentTimeMillis();
+        final long minCrashIntervalMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_CONNECTIVITY,
+                CONFIG_MIN_CRASH_INTERVAL_MS, DEFAULT_MIN_CRASH_INTERVAL_MS);
+        final long minUptimeBeforeCrash = DeviceConfig.getLong(DeviceConfig.NAMESPACE_CONNECTIVITY,
+                CONFIG_MIN_UPTIME_BEFORE_CRASH_MS, DEFAULT_MIN_UPTIME_BEFORE_CRASH_MS);
+        final boolean alwaysRatelimit = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CONNECTIVITY,
+                CONFIG_ALWAYS_RATELIMIT_NETWORKSTACK_CRASH, false);
+
+        final SharedPreferences prefs = getSharedPreferences();
+        final long lastCrashTime = tryGetLastCrashTime(prefs);
+
+        // Only crash if there was enough time since boot, and (if known) enough time passed since
+        // the last crash.
+        // time and lastCrashTime may be unreliable if devices have incorrect clock time, but they
+        // are only used to limit the number of crashes compared to only using the time since boot,
+        // which would also be OK behavior by itself.
+        // - If lastCrashTime is incorrectly more than the current time, only look at uptime
+        // - If it is much less than current time, only look at uptime
+        // - If current time is during the next few hours after last crash time, don't crash.
+        //   Considering that this only matters if last boot was some time ago, it's likely that
+        //   time will be set correctly. Otherwise, not crashing is not a big problem anyway. Being
+        //   in this last state would also not last for long since the window is only a few hours.
+        final boolean alwaysCrash = Build.IS_DEBUGGABLE && !alwaysRatelimit;
+        final boolean justBooted = uptime < minUptimeBeforeCrash;
+        final boolean haveLastCrashTime = (lastCrashTime != 0) && (lastCrashTime < now);
+        final boolean haveKnownRecentCrash =
+                haveLastCrashTime && (now < lastCrashTime + minCrashIntervalMs);
+        if (alwaysCrash || (!justBooted && !haveKnownRecentCrash)) {
+            // The system is not bound to its network stack (for example due to a crash in the
+            // network stack process): better crash rather than stay in a bad state where all
+            // networking is broken.
+            // Using device-encrypted SharedPreferences as DeviceConfig does not have a synchronous
+            // API to persist settings before a crash.
+            tryWriteLastCrashTime(prefs, now);
+            throw new IllegalStateException(message);
+        }
+
+        // Here the system crashed recently already. Inform listeners that something is
+        // definitely wrong.
+        if (packageName != null) {
+            final ArraySet<ConnectivityModuleHealthListener> listeners;
+            synchronized (mHealthListeners) {
+                listeners = new ArraySet<>(mHealthListeners);
+            }
+            for (ConnectivityModuleHealthListener listener : listeners) {
+                listener.onNetworkStackFailure(packageName);
+            }
+        }
+    }
+
+    @Nullable
+    private SharedPreferences getSharedPreferences() {
+        try {
+            final File prefsFile = new File(
+                    Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), PREFS_FILE);
+            return mContext.createDeviceProtectedStorageContext()
+                    .getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+        } catch (Throwable e) {
+            logWtf("Error loading shared preferences", e);
+            return null;
+        }
+    }
+
+    private long tryGetLastCrashTime(@Nullable SharedPreferences prefs) {
+        if (prefs == null) return 0L;
+        try {
+            return prefs.getLong(PREF_KEY_LAST_CRASH_TIME, 0L);
+        } catch (Throwable e) {
+            logWtf("Error getting last crash time", e);
+            return 0L;
+        }
+    }
+
+    private void tryWriteLastCrashTime(@Nullable SharedPreferences prefs, long value) {
+        if (prefs == null) return;
+        try {
+            prefs.edit().putLong(PREF_KEY_LAST_CRASH_TIME, value).commit();
+        } catch (Throwable e) {
+            logWtf("Error writing last crash time", e);
+        }
+    }
+
+    private void log(@NonNull String message) {
+        Slog.d(TAG, message);
+        synchronized (mLog) {
+            mLog.log(message);
+        }
+    }
+
+    private void logWtf(@NonNull String message, @Nullable Throwable e) {
+        Slog.wtf(TAG, message, e);
+        synchronized (mLog) {
+            mLog.e(message);
+        }
+    }
+
+    private void loge(@NonNull String message, @Nullable Throwable e) {
+        Slog.e(TAG, message, e);
+        synchronized (mLog) {
+            mLog.e(message);
+        }
+    }
+
+    private void logi(@NonNull String message) {
+        Slog.i(TAG, message);
+        synchronized (mLog) {
+            mLog.i(message);
+        }
+    }
+
+    /**
+     * Dump ConnectivityModuleConnector logs to the specified {@link PrintWriter}.
+     */
+    public void dump(PrintWriter pw) {
+        // dump is thread-safe on SharedLog
+        mLog.dump(null, pw, null);
+    }
+
+    /**
+     * Stub class to replicate DeviceConfig behavior with minimal merge conflicts.
+     */
+    private class DeviceConfigStub {
+        private final Context mContext;
+
+        // Namespace is actually unused, but is here to replicate the final API.
+        private static final String NAMESPACE_CONNECTIVITY = "connectivity";
+
+        private DeviceConfigStub(Context context) {
+            mContext = context;
+        }
+
+        private long getLong(
+                @NonNull String namespace, @NonNull String key, long defaultVal) {
+            // Temporary solution until DeviceConfig is available
+            try {
+                return Settings.Global.getLong(
+                        mContext.getContentResolver(), TAG + "_" + key, defaultVal);
+            } catch (Throwable e) {
+                logWtf("Could not obtain setting " + key, e);
+                return defaultVal;
+            }
+        }
+
+        private boolean getBoolean(
+                @NonNull String namespace, @NonNull String key, boolean defaultVal) {
+            // Temporary solution until DeviceConfig is available
+            return getLong(namespace, key, defaultVal ? 1 : 0) != 0;
+        }
+    }
+}
diff --git a/services/net/java/android/net/IpMemoryStoreClient.java b/services/net/java/android/net/IpMemoryStoreClient.java
index 3d56202..014b528 100644
--- a/services/net/java/android/net/IpMemoryStoreClient.java
+++ b/services/net/java/android/net/IpMemoryStoreClient.java
@@ -212,4 +212,16 @@
                             null, null, null));
         }
     }
+
+    /**
+     * Wipe the data in the database upon network factory reset.
+     */
+    public void factoryReset() {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.factoryReset()));
+        } catch (ExecutionException m) {
+            Log.e(TAG, "Error executing factory reset", m);
+        }
+    }
 }
diff --git a/services/net/java/android/net/NetworkMonitorManager.java b/services/net/java/android/net/NetworkMonitorManager.java
new file mode 100644
index 0000000..0f41302
--- /dev/null
+++ b/services/net/java/android/net/NetworkMonitorManager.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A convenience wrapper for INetworkMonitor.
+ *
+ * Wraps INetworkMonitor calls, making them a bit more friendly to use. Currently handles:
+ * - Clearing calling identity
+ * - Ignoring RemoteExceptions
+ * - Converting to stable parcelables
+ *
+ * By design, all methods on INetworkMonitor are asynchronous oneway IPCs and are thus void. All the
+ * wrapper methods in this class return a boolean that callers can use to determine whether
+ * RemoteException was thrown.
+ */
+public class NetworkMonitorManager {
+
+    @NonNull private final INetworkMonitor mNetworkMonitor;
+    @NonNull private final String mTag;
+
+    public NetworkMonitorManager(@NonNull INetworkMonitor networkMonitorManager,
+            @NonNull String tag) {
+        mNetworkMonitor = networkMonitorManager;
+        mTag = tag;
+    }
+
+    public NetworkMonitorManager(@NonNull INetworkMonitor networkMonitorManager) {
+        this(networkMonitorManager, NetworkMonitorManager.class.getSimpleName());
+    }
+
+    private void log(String s, Throwable e) {
+        Log.e(mTag, s, e);
+    }
+
+    // CHECKSTYLE:OFF Generated code
+
+    public boolean start() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.start();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in start", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean launchCaptivePortalApp() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.launchCaptivePortalApp();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in launchCaptivePortalApp", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyCaptivePortalAppFinished(int response) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyCaptivePortalAppFinished(response);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyCaptivePortalAppFinished", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean setAcceptPartialConnectivity() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.setAcceptPartialConnectivity();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in setAcceptPartialConnectivity", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean forceReevaluation(int uid) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.forceReevaluation(uid);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in forceReevaluation", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyPrivateDnsChanged(PrivateDnsConfigParcel config) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyPrivateDnsChanged(config);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyPrivateDnsChanged", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyDnsResponse(int returnCode) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyDnsResponse(returnCode);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyDnsResponse", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyNetworkConnected(lp, nc);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyNetworkConnected", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyNetworkDisconnected() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyNetworkDisconnected();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyNetworkDisconnected", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyLinkPropertiesChanged(LinkProperties lp) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyLinkPropertiesChanged(lp);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyLinkPropertiesChanged", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyNetworkCapabilitiesChanged(NetworkCapabilities nc) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyNetworkCapabilitiesChanged(nc);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyNetworkCapabilitiesChanged", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // CHECKSTYLE:ON Generated code
+}
diff --git a/services/net/java/android/net/NetworkStackClient.java b/services/net/java/android/net/NetworkStackClient.java
index 6b5842f..69e2406 100644
--- a/services/net/java/android/net/NetworkStackClient.java
+++ b/services/net/java/android/net/NetworkStackClient.java
@@ -15,24 +15,18 @@
  */
 package android.net;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServerCallbacks;
 import android.net.ip.IIpClientCallbacks;
 import android.net.util.SharedLog;
 import android.os.Binder;
-import android.os.Build;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
@@ -41,6 +35,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -53,11 +48,13 @@
     private static final String TAG = NetworkStackClient.class.getSimpleName();
 
     private static final int NETWORKSTACK_TIMEOUT_MS = 10_000;
-    private static final String IN_PROCESS_SUFFIX = ".InProcess";
 
     private static NetworkStackClient sInstance;
 
     @NonNull
+    private final Dependencies mDependencies;
+
+    @NonNull
     @GuardedBy("mPendingNetStackRequests")
     private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>();
     @Nullable
@@ -67,13 +64,56 @@
     @GuardedBy("mLog")
     private final SharedLog mLog = new SharedLog(TAG);
 
-    private volatile boolean mNetworkStackStartRequested = false;
+    private volatile boolean mWasSystemServerInitialized = false;
 
     private interface NetworkStackCallback {
         void onNetworkStackConnected(INetworkStackConnector connector);
     }
 
-    private NetworkStackClient() { }
+    @VisibleForTesting
+    protected NetworkStackClient(@NonNull Dependencies dependencies) {
+        mDependencies = dependencies;
+    }
+
+    private NetworkStackClient() {
+        this(new DependenciesImpl());
+    }
+
+    @VisibleForTesting
+    protected interface Dependencies {
+        void addToServiceManager(@NonNull IBinder service);
+        void checkCallerUid();
+        ConnectivityModuleConnector getConnectivityModuleConnector();
+    }
+
+    private static class DependenciesImpl implements Dependencies {
+        @Override
+        public void addToServiceManager(@NonNull IBinder service) {
+            ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service,
+                    false /* allowIsolated */, DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+        }
+
+        @Override
+        public void checkCallerUid() {
+            final int caller = Binder.getCallingUid();
+            // This is a client lib so "caller" is the current UID in most cases. The check is done
+            // here in the caller's process just to provide a nicer error message to clients; more
+            // generic checks are also done in NetworkStackService.
+            // See PermissionUtil in NetworkStack for the actual check on the service side - the
+            // checks here should be kept in sync with PermissionUtil.
+            if (caller != Process.SYSTEM_UID
+                    && caller != Process.NETWORK_STACK_UID
+                    && !UserHandle.isSameApp(caller, Process.BLUETOOTH_UID)) {
+                throw new SecurityException(
+                        "Only the system server should try to bind to the network stack.");
+            }
+        }
+
+        @Override
+        public ConnectivityModuleConnector getConnectivityModuleConnector() {
+            return ConnectivityModuleConnector.getInstance();
+        }
+    }
 
     /**
      * Get the NetworkStackClient singleton instance.
@@ -146,29 +186,18 @@
         });
     }
 
-    private class NetworkStackConnection implements ServiceConnection {
+    private class NetworkStackConnection implements
+            ConnectivityModuleConnector.ModuleServiceCallback {
         @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
+        public void onModuleServiceConnected(IBinder service) {
             logi("Network stack service connected");
             registerNetworkStackService(service);
         }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            // The system has lost its network stack (probably due to a crash in the
-            // network stack process): better crash rather than stay in a bad state where all
-            // networking is broken.
-            // onServiceDisconnected is not being called on device shutdown, so this method being
-            // called always indicates a bad state for the system server.
-            maybeCrashWithTerribleFailure("Lost network stack");
-        }
-    };
+    }
 
     private void registerNetworkStackService(@NonNull IBinder service) {
         final INetworkStackConnector connector = INetworkStackConnector.Stub.asInterface(service);
-
-        ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */,
-                DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+        mDependencies.addToServiceManager(service);
         log("Network stack service registered");
 
         final ArrayList<NetworkStackCallback> requests;
@@ -189,7 +218,7 @@
      */
     public void init() {
         log("Network stack init");
-        mNetworkStackStartRequested = true;
+        mWasSystemServerInitialized = true;
     }
 
     /**
@@ -200,87 +229,13 @@
      * connector will then be delivered asynchronously to clients that requested it before it was
      * started.
      */
-    public void start(Context context) {
-        log("Starting network stack");
-        final PackageManager pm = context.getPackageManager();
-
-        // Try to bind in-process if the device was shipped with an in-process version
-        Intent intent = getNetworkStackIntent(pm, true /* inSystemProcess */);
-
-        // Otherwise use the updatable module version
-        if (intent == null) {
-            intent = getNetworkStackIntent(pm, false /* inSystemProcess */);
-            log("Starting network stack process");
-        } else {
-            log("Starting network stack in-process");
-        }
-
-        if (intent == null) {
-            maybeCrashWithTerribleFailure("Could not resolve the network stack");
-            return;
-        }
-
-        // Start the network stack. The service will be added to the service manager in
-        // NetworkStackConnection.onServiceConnected().
-        if (!context.bindServiceAsUser(intent, new NetworkStackConnection(),
-                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) {
-            maybeCrashWithTerribleFailure(
-                    "Could not bind to network stack in-process, or in app with " + intent);
-            return;
-        }
-
+    public void start() {
+        mDependencies.getConnectivityModuleConnector().startModuleService(
+                INetworkStackConnector.class.getName(), PERMISSION_MAINLINE_NETWORK_STACK,
+                new NetworkStackConnection());
         log("Network stack service start requested");
     }
 
-    @Nullable
-    private Intent getNetworkStackIntent(@NonNull PackageManager pm, boolean inSystemProcess) {
-        final String baseAction = INetworkStackConnector.class.getName();
-        final Intent intent =
-                new Intent(inSystemProcess ? baseAction + IN_PROCESS_SUFFIX : baseAction);
-        final ComponentName comp = intent.resolveSystemService(pm, 0);
-
-        if (comp == null) {
-            return null;
-        }
-        intent.setComponent(comp);
-
-        int uid = -1;
-        try {
-            uid = pm.getPackageUidAsUser(comp.getPackageName(), UserHandle.USER_SYSTEM);
-        } catch (PackageManager.NameNotFoundException e) {
-            logWtf("Network stack package not found", e);
-            // Fall through
-        }
-
-        final int expectedUid = inSystemProcess ? Process.SYSTEM_UID : Process.NETWORK_STACK_UID;
-        if (uid != expectedUid) {
-            throw new SecurityException("Invalid network stack UID: " + uid);
-        }
-
-        if (!inSystemProcess) {
-            checkNetworkStackPermission(pm, comp);
-        }
-
-        return intent;
-    }
-
-    private void checkNetworkStackPermission(
-            @NonNull PackageManager pm, @NonNull ComponentName comp) {
-        final int hasPermission =
-                pm.checkPermission(PERMISSION_MAINLINE_NETWORK_STACK, comp.getPackageName());
-        if (hasPermission != PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    "Network stack does not have permission " + PERMISSION_MAINLINE_NETWORK_STACK);
-        }
-    }
-
-    private void maybeCrashWithTerribleFailure(@NonNull String message) {
-        logWtf(message, null);
-        if (Build.IS_DEBUGGABLE) {
-            throw new IllegalStateException(message);
-        }
-    }
-
     /**
      * Log a message in the local log.
      */
@@ -341,16 +296,9 @@
     }
 
     private void requestConnector(@NonNull NetworkStackCallback request) {
-        // TODO: PID check.
-        final int caller = Binder.getCallingUid();
-        if (caller != Process.SYSTEM_UID && !UserHandle.isSameApp(caller, Process.BLUETOOTH_UID)
-                && !UserHandle.isSameApp(caller, Process.PHONE_UID)) {
-            // Don't even attempt to obtain the connector and give a nice error message
-            throw new SecurityException(
-                    "Only the system server should try to bind to the network stack.");
-        }
+        mDependencies.checkCallerUid();
 
-        if (!mNetworkStackStartRequested) {
+        if (!mWasSystemServerInitialized) {
             // The network stack is not being started in this process, e.g. this process is not
             // the system server. Get a remote connector registered by the system server.
             final INetworkStackConnector connector = getRemoteConnector();
@@ -379,6 +327,8 @@
     public void dump(PrintWriter pw) {
         // dump is thread-safe on SharedLog
         mLog.dump(null, pw, null);
+        // dump connectivity module connector logs.
+        ConnectivityModuleConnector.getInstance().dump(pw);
 
         final int requestsQueueLength;
         synchronized (mPendingNetStackRequests) {
diff --git a/services/net/java/android/net/dhcp/DhcpServerCallbacks.java b/services/net/java/android/net/dhcp/DhcpServerCallbacks.java
index bb56876..7c413779 100644
--- a/services/net/java/android/net/dhcp/DhcpServerCallbacks.java
+++ b/services/net/java/android/net/dhcp/DhcpServerCallbacks.java
@@ -21,13 +21,11 @@
  * @hide
  */
 public abstract class DhcpServerCallbacks extends IDhcpServerCallbacks.Stub {
-    // TODO: add @Override here once the API is versioned
-
     /**
      * Get the version of the aidl interface implemented by the callbacks.
      */
+    @Override
     public int getInterfaceVersion() {
-        // TODO: return IDhcpServerCallbacks.VERSION;
-        return 0;
+        return IDhcpServerCallbacks.VERSION;
     }
 }
diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java
index f8d7e84..1e653cd 100644
--- a/services/net/java/android/net/ip/IpClientManager.java
+++ b/services/net/java/android/net/ip/IpClientManager.java
@@ -21,6 +21,7 @@
 import android.net.ProxyInfo;
 import android.net.TcpKeepalivePacketData;
 import android.net.shared.ProvisioningConfiguration;
+import android.net.util.KeepalivePacketDataUtil;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -229,7 +230,8 @@
     public boolean addKeepalivePacketFilter(int slot, NattKeepalivePacketData pkt) {
         final long token = Binder.clearCallingIdentity();
         try {
-            mIpClient.addNattKeepalivePacketFilter(slot, pkt.toStableParcelable());
+            mIpClient.addNattKeepalivePacketFilter(
+                    slot, KeepalivePacketDataUtil.toStableParcelable(pkt));
             return true;
         } catch (RemoteException e) {
             log("Error adding Keepalive Packet Filter ", e);
diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java
index 66884c6..6a6a130 100644
--- a/services/net/java/android/net/ip/IpServer.java
+++ b/services/net/java/android/net/ip/IpServer.java
@@ -433,6 +433,9 @@
                 }
             }
             ifcg.clearFlag("running");
+
+            // TODO: this may throw if the interface is already gone. Do proper handling and
+            // simplify the DHCP server start/stop.
             mNMService.setInterfaceConfig(mIfaceName, ifcg);
 
             if (!configureDhcp(enabled, (Inet4Address) addr, prefixLen)) {
@@ -440,6 +443,14 @@
             }
         } catch (Exception e) {
             mLog.e("Error configuring interface " + e);
+            if (!enabled) {
+                try {
+                    // Calling stopDhcp several times is fine
+                    stopDhcp();
+                } catch (Exception dhcpError) {
+                    mLog.e("Error stopping DHCP", dhcpError);
+                }
+            }
             return false;
         }
 
diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
index e769769..818515a 100644
--- a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -127,6 +127,7 @@
 
     @Nullable
     private static InetAddress getByAddressOrNull(@Nullable final byte[] address) {
+        if (null == address) return null;
         try {
             return InetAddress.getByAddress(address);
         } catch (UnknownHostException e) {
@@ -227,7 +228,9 @@
         }
 
         /**
-         * Set the lease expiry timestamp of assigned v4 address.
+         * Set the lease expiry timestamp of assigned v4 address. Long.MAX_VALUE is used
+         * to represent "infinite lease".
+         *
          * @param assignedV4AddressExpiry The lease expiry timestamp of assigned v4 address.
          * @return This builder.
          */
diff --git a/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java b/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
index ca6f302..395ad98 100644
--- a/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
+++ b/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
@@ -40,8 +40,8 @@
                 // NonNull, but still don't crash the system server if null
                 if (null != listener) {
                     listener.onNetworkAttributesRetrieved(
-                            new Status(statusParcelable), l2Key,
-                            new NetworkAttributes(networkAttributesParcelable));
+                            new Status(statusParcelable), l2Key, null == networkAttributesParcelable
+                                ? null : new NetworkAttributes(networkAttributesParcelable));
                 }
             }
 
diff --git a/services/net/java/android/net/util/KeepalivePacketDataUtil.java b/services/net/java/android/net/util/KeepalivePacketDataUtil.java
new file mode 100644
index 0000000..9a51729
--- /dev/null
+++ b/services/net/java/android/net/util/KeepalivePacketDataUtil.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.annotation.NonNull;
+import android.net.NattKeepalivePacketData;
+import android.net.NattKeepalivePacketDataParcelable;
+
+/** @hide */
+public final class KeepalivePacketDataUtil {
+     /**
+     * Convert this NattKeepalivePacketData to a NattKeepalivePacketDataParcelable.
+     */
+    @NonNull
+    public static NattKeepalivePacketDataParcelable toStableParcelable(
+            NattKeepalivePacketData pkt) {
+        final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable();
+
+        parcel.srcAddress = pkt.srcAddress.getAddress();
+        parcel.srcPort = pkt.srcPort;
+        parcel.dstAddress = pkt.dstAddress.getAddress();
+        parcel.dstPort = pkt.dstPort;
+        return parcel;
+    }
+}
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index c01c124..753d8d4 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -64,12 +64,14 @@
     $(call all-Iaidl-files-under, ../../core/java/android/app/backup) \
     ../../core/java/android/content/pm/PackageInfo.java \
     ../../core/java/android/app/IBackupAgent.aidl \
-    ../../core/java/android/util/KeyValueSettingObserver.java
+    ../../core/java/android/util/KeyValueSettingObserver.java \
+    ../../../../system/apex/apexd/aidl/android/apex/ApexInfo.aidl
 
 LOCAL_AIDL_INCLUDES := \
     $(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
     $(call all-Iaidl-files-under, ../../core/java/android/app/backup) \
-    ../../core/java/android/app/IBackupAgent.aidl
+    ../../core/java/android/app/IBackupAgent.aidl \
+    ../../../../system/apex/apexd/aidl/android/apex/ApexInfo.aidl
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     platform-robolectric-android-all-stubs \
diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
index b1dad5a..c163d11 100644
--- a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
+++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
@@ -19,7 +19,6 @@
 import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createBackupWakeLock;
 import static com.android.server.backup.testing.BackupManagerServiceTestUtils.setUpBackupManagerServiceBasics;
 import static com.android.server.backup.testing.BackupManagerServiceTestUtils.startBackupThreadAndGetLooper;
-
 import static com.android.server.backup.testing.TransportData.backupTransport;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -80,7 +79,6 @@
 import com.android.server.testing.shadows.ShadowBackupDataOutput;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatcher;
@@ -106,7 +104,7 @@
     sdk = 26,
     shadows = {ShadowBackupDataInput.class, ShadowBackupDataOutput.class, ShadowQueuedWork.class}
 )
-@SystemLoaderPackages({"com.android.server.backup", "android.app.backup"})
+@SystemLoaderPackages({"com.android.server.backup", "android.app.backup", "android.apex"})
 @SystemLoaderClasses({IBackupTransport.class, IBackupAgent.class, PackageInfo.class})
 @Presubmit
 public class PerformBackupTaskTest {
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 9097430..41f46f5 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -38,6 +38,7 @@
         "ub-uiautomator",
         "platformprotosnano",
         "servicestests-utils",
+        "xml-writer-device-lib",
     ],
 
     aidl: {
@@ -70,7 +71,7 @@
         "libui",
         "libunwindstack",
         "libutils",
-        "netd_aidl_interface-cpp",
+        "netd_aidl_interface-V2-cpp",
     ],
 
     dxflags: ["--multi-dex"],
@@ -78,6 +79,8 @@
     optimize: {
         enabled: false,
     },
+
+    data: [":JobTestApp"],
 }
 
 java_library {
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 85d0c4c..36103e3 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -49,7 +49,6 @@
 import static android.telephony.CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT;
 import static android.telephony.SubscriptionPlan.BYTES_UNLIMITED;
 import static android.telephony.SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED;
-import static android.text.format.Time.TIMEZONE_UTC;
 
 import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_JOBS;
 import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
@@ -128,7 +127,6 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.text.TextUtils;
-import android.text.format.Time;
 import android.util.DataUnit;
 import android.util.Log;
 import android.util.Pair;
@@ -185,6 +183,7 @@
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.TimeZone;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
@@ -223,6 +222,7 @@
      * Path on assets where files used by {@link NetPolicyXml} are located.
      */
     private static final String NETPOLICY_DIR = "NetworkPolicyManagerServiceTest/netpolicy";
+    private static final String TIMEZONE_UTC = "UTC";
 
     private BroadcastInterceptingContext mServiceContext;
     private File mPolicyDir;
@@ -1771,7 +1771,7 @@
     private static NetworkPolicy buildFakeMobilePolicy(int cycleDay, long warningBytes,
             long limitBytes, boolean inferred){
         final NetworkTemplate template = buildTemplateMobileAll(FAKE_SUBSCRIBER_ID);
-        return new NetworkPolicy(template, cycleDay, new Time().timezone, warningBytes,
+        return new NetworkPolicy(template, cycleDay, TimeZone.getDefault().getID(), warningBytes,
                 limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
index 863a0d8..5fbf241 100644
--- a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
@@ -390,7 +390,7 @@
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
         mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
 
-        // WHEN calling stopLockTaskMode on the root task
+        // WHEN calling clearLockedTasks on the root task
         mLockTaskController.clearLockedTasks("testClearLockedTasks");
 
         // THEN the lock task mode should be inactive
@@ -404,7 +404,81 @@
     }
 
     @Test
-    public void testUpdateLockTaskPackages() throws Exception {
+    public void testClearLockedTasks_noLockSetting_noPassword_deviceIsUnlocked() throws Exception {
+        // GIVEN There is no setting set for LOCK_TO_APP_EXIT_LOCKED
+        Settings.Secure.clearProviderForTest();
+
+        // AND no password is set
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(anyInt()))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+
+        // AND there is a task record
+        TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
+
+        // WHEN calling clearLockedTasks on the root task
+        mLockTaskController.clearLockedTasks("testClearLockedTasks");
+
+        // THEN the device should not be locked
+        verify(mWindowManager, never()).lockNow(any());
+    }
+
+    @Test
+    public void testClearLockedTasks_noLockSetting_password_deviceIsLocked() throws Exception {
+        // GIVEN There is no setting set for LOCK_TO_APP_EXIT_LOCKED
+        Settings.Secure.clearProviderForTest();
+
+        // AND a password is set
+        when(mLockPatternUtils.isSecure(anyInt()))
+                .thenReturn(true);
+
+        // AND there is a task record
+        TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
+
+        // WHEN calling clearLockedTasks on the root task
+        mLockTaskController.clearLockedTasks("testClearLockedTasks");
+
+        // THEN the device should be locked
+        verify(mWindowManager, times(1)).lockNow(any());
+    }
+
+    @Test
+    public void testClearLockedTasks_lockSettingTrue_deviceIsLocked() throws Exception {
+        // GIVEN LOCK_TO_APP_EXIT_LOCKED is set to 1
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 1, mContext.getUserId());
+
+        // AND there is a task record
+        TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
+
+        // WHEN calling clearLockedTasks on the root task
+        mLockTaskController.clearLockedTasks("testClearLockedTasks");
+
+        // THEN the device should be locked
+        verify(mWindowManager, times(1)).lockNow(any());
+    }
+
+    @Test
+    public void testClearLockedTasks_lockSettingFalse_doesNotRequirePassword() throws Exception {
+        // GIVEN LOCK_TO_APP_EXIT_LOCKED is set to 1
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 0, mContext.getUserId());
+
+        // AND there is a task record
+        TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
+
+        // WHEN calling clearLockedTasks on the root task
+        mLockTaskController.clearLockedTasks("testClearLockedTasks");
+
+        // THEN the device should be unlocked
+        verify(mWindowManager, never()).lockNow(any());
+    }
+
+    @Test
+    public void testUpdateLockTaskPackages() {
         String[] whitelist1 = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2};
         String[] whitelist2 = {TEST_PACKAGE_NAME};
 
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
new file mode 100644
index 0000000..f8c87fc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.compat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.ApplicationInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compat.annotation.Change;
+import com.android.compat.annotation.XmlWriter;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.UUID;
+
+@RunWith(AndroidJUnit4.class)
+public class CompatConfigTest {
+
+    private ApplicationInfo makeAppInfo(String pName, int targetSdkVersion) {
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = pName;
+        ai.targetSdkVersion = targetSdkVersion;
+        return ai;
+    }
+
+    private File createTempDir() {
+        String base = System.getProperty("java.io.tmpdir");
+        File dir = new File(base, UUID.randomUUID().toString());
+        assertThat(dir.mkdirs()).isTrue();
+        return dir;
+    }
+
+    private void writeChangesToFile(Change[] changes, File f) {
+        XmlWriter writer = new XmlWriter();
+        for (Change change: changes) {
+            writer.addChange(change);
+        }
+        try {
+            f.createNewFile();
+            writer.write(new FileOutputStream(f));
+        } catch (IOException e) {
+            throw new RuntimeException(
+                    "Encountered an error while writing compat config file", e);
+        }
+    }
+
+    @Test
+    public void testUnknownChangeEnabled() {
+        CompatConfig pc = new CompatConfig();
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue();
+    }
+
+    @Test
+    public void testDisabledChangeDisabled() {
+        CompatConfig pc = new CompatConfig();
+        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true));
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
+    }
+
+    @Test
+    public void testTargetSdkChangeDisabled() {
+        CompatConfig pc = new CompatConfig();
+        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false));
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
+    }
+
+    @Test
+    public void testTargetSdkChangeEnabled() {
+        CompatConfig pc = new CompatConfig();
+        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false));
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
+    }
+
+    @Test
+    public void testDisabledOverrideTargetSdkChange() {
+        CompatConfig pc = new CompatConfig();
+        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true));
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isFalse();
+    }
+
+    @Test
+    public void testGetDisabledChanges() {
+        CompatConfig pc = new CompatConfig();
+        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true));
+        pc.addChange(new CompatChange(2345L, "OTHER_CHANGE", -1, false));
+        assertThat(pc.getDisabledChanges(
+                makeAppInfo("com.some.package", 2))).asList().containsExactly(1234L);
+    }
+
+    @Test
+    public void testGetDisabledChangesSorted() {
+        CompatConfig pc = new CompatConfig();
+        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true));
+        pc.addChange(new CompatChange(123L, "OTHER_CHANGE", 2, true));
+        pc.addChange(new CompatChange(12L, "THIRD_CHANGE", 2, true));
+        assertThat(pc.getDisabledChanges(
+                makeAppInfo("com.some.package", 2))).asList().containsExactly(12L, 123L, 1234L);
+    }
+
+    @Test
+    public void testPackageOverrideEnabled() {
+        CompatConfig pc = new CompatConfig();
+        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); // disabled
+        pc.addOverride(1234L, "com.some.package", true);
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue();
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isFalse();
+    }
+
+    @Test
+    public void testPackageOverrideDisabled() {
+        CompatConfig pc = new CompatConfig();
+        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false));
+        pc.addOverride(1234L, "com.some.package", false);
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue();
+    }
+
+    @Test
+    public void testPackageOverrideUnknownPackage() {
+        CompatConfig pc = new CompatConfig();
+        pc.addOverride(1234L, "com.some.package", false);
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue();
+    }
+
+    @Test
+    public void testPackageOverrideUnknownChange() {
+        CompatConfig pc = new CompatConfig();
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue();
+    }
+
+    @Test
+    public void testRemovePackageOverride() {
+        CompatConfig pc = new CompatConfig();
+        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false));
+        pc.addOverride(1234L, "com.some.package", false);
+        pc.removeOverride(1234L, "com.some.package");
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue();
+    }
+
+    @Test
+    public void testLookupChangeId() {
+        CompatConfig pc = new CompatConfig();
+        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false));
+        pc.addChange(new CompatChange(2345L, "ANOTHER_CHANGE", -1, false));
+        assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(1234L);
+    }
+
+    @Test
+    public void testLookupChangeIdNotPresent() {
+        CompatConfig pc = new CompatConfig();
+        assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(-1L);
+    }
+
+    @Test
+    public void testReadConfig() {
+        Change[] changes = {new Change(1234L, "MY_CHANGE1", false, 2), new Change(1235L,
+                "MY_CHANGE2", true, null), new Change(1236L, "MY_CHANGE3", false, null)};
+
+        File dir = createTempDir();
+        writeChangesToFile(changes, new File(dir.getPath() + "/platform_compat_config.xml"));
+
+        CompatConfig pc = new CompatConfig();
+        pc.initConfigFromLib(dir);
+
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
+        assertThat(pc.isChangeEnabled(1235L, makeAppInfo("com.some.package", 5))).isFalse();
+        assertThat(pc.isChangeEnabled(1236L, makeAppInfo("com.some.package", 1))).isTrue();
+    }
+
+    @Test
+    public void testReadConfigMultipleFiles() {
+        Change[] changes1 = {new Change(1234L, "MY_CHANGE1", false, 2)};
+        Change[] changes2 = {new Change(1235L, "MY_CHANGE2", true, null), new Change(1236L,
+                "MY_CHANGE3", false, null)};
+
+        File dir = createTempDir();
+        writeChangesToFile(changes1,
+                new File(dir.getPath() + "/libcore_platform_compat_config.xml"));
+        writeChangesToFile(changes2,
+                new File(dir.getPath() + "/frameworks_platform_compat_config.xml"));
+
+
+        CompatConfig pc = new CompatConfig();
+        pc.initConfigFromLib(dir);
+
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
+        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
+        assertThat(pc.isChangeEnabled(1235L, makeAppInfo("com.some.package", 5))).isFalse();
+        assertThat(pc.isChangeEnabled(1236L, makeAppInfo("com.some.package", 1))).isTrue();
+    }
+}
+
+
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 543f51cba..6fa5cd29 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -382,6 +382,82 @@
                 .build());
     }
 
+    @Test
+    public void testPersistedIdleConstraint() throws Exception {
+        JobInfo.Builder b = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPersisted(true);
+        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+
+        mTaskStoreUnderTest.add(taskStatus);
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertEquals("Idle constraint not persisted correctly.",
+                loaded.getJob().isRequireDeviceIdle(),
+                taskStatus.getJob().isRequireDeviceIdle());
+    }
+
+    @Test
+    public void testPersistedChargingConstraint() throws Exception {
+        JobInfo.Builder b = new Builder(8, mComponent)
+                .setRequiresCharging(true)
+                .setPersisted(true);
+        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+
+        mTaskStoreUnderTest.add(taskStatus);
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertEquals("Charging constraint not persisted correctly.",
+                loaded.getJob().isRequireCharging(),
+                taskStatus.getJob().isRequireCharging());
+    }
+
+    @Test
+    public void testPersistedStorageNotLowConstraint() throws Exception {
+        JobInfo.Builder b = new Builder(8, mComponent)
+                .setRequiresStorageNotLow(true)
+                .setPersisted(true);
+        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+
+        mTaskStoreUnderTest.add(taskStatus);
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertEquals("Storage-not-low constraint not persisted correctly.",
+                loaded.getJob().isRequireStorageNotLow(),
+                taskStatus.getJob().isRequireStorageNotLow());
+    }
+
+    @Test
+    public void testPersistedBatteryNotLowConstraint() throws Exception {
+        JobInfo.Builder b = new Builder(8, mComponent)
+                .setRequiresBatteryNotLow(true)
+                .setPersisted(true);
+        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+
+        mTaskStoreUnderTest.add(taskStatus);
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertEquals("Battery-not-low constraint not persisted correctly.",
+                loaded.getJob().isRequireBatteryNotLow(),
+                taskStatus.getJob().isRequireBatteryNotLow());
+    }
+
     /**
      * Helper function to kick a {@link JobInfo} through a persistence cycle and
      * assert that it's unchanged.
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 68728af..6a03aed 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -60,6 +60,11 @@
             }
 
             @Override
+            public void notifyPackageChanged(String packageName, int uid) {
+
+            }
+
+            @Override
             public void notifyPackageRemoved(String packageName, int uid) {
             }
         }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 9736e68..c56a393 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -38,13 +38,14 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import libcore.io.IoUtils;
-
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
 import java.nio.charset.StandardCharsets;
@@ -57,13 +58,16 @@
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class PackageParserTest {
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
     private File mTmpDir;
     private static final File FRAMEWORK = new File("/system/framework/framework-res.apk");
 
     @Before
-    public void setUp() {
+    public void setUp() throws IOException {
         // Create a new temporary directory for each of our tests.
-        mTmpDir = IoUtils.createTemporaryDirectory("PackageParserTest");
+        mTmpDir = mTemporaryFolder.newFolder("PackageParserTest");
     }
 
     @Test
diff --git a/services/tests/servicestests/test-apps/JobTestApp/Android.bp b/services/tests/servicestests/test-apps/JobTestApp/Android.bp
index ae1eca7..b29e187 100644
--- a/services/tests/servicestests/test-apps/JobTestApp/Android.bp
+++ b/services/tests/servicestests/test-apps/JobTestApp/Android.bp
@@ -17,8 +17,6 @@
 
     sdk_version: "current",
 
-    test_suites: ["device-tests"],
-
     srcs: ["**/*.java"],
 
     dex_preopt: {
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index c22ca90..8b25b96 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -53,6 +53,6 @@
         "libui",
         "libunwindstack",
         "libutils",
-        "netd_aidl_interface-cpp",
+        "netd_aidl_interface-V2-cpp",
     ],
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f02c3f0..5622622 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -34,6 +34,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Build.VERSION_CODES.P;
 
@@ -106,6 +107,7 @@
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
+import android.testing.TestablePermissions;
 import android.text.Html;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
@@ -3145,4 +3147,21 @@
 
         assertEquals(0, captor.getValue().getNotification().flags);
     }
+
+    @Test
+    public void testAreNotificationsEnabledForPackage_crossUser() throws Exception {
+        try {
+            mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
+                    mUid + UserHandle.PER_USER_RANGE);
+            fail("Cannot call cross user without permission");
+        } catch (SecurityException e) {
+            // pass
+        }
+
+        // cross user, with permission, no problem
+        TestablePermissions perms = mContext.getTestablePermissions();
+        perms.setPermission(android.Manifest.permission.INTERACT_ACROSS_USERS, PERMISSION_GRANTED);
+        mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
+                mUid + UserHandle.PER_USER_RANGE);
+    }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index e949e74..655363e 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -922,6 +922,8 @@
                     if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                         // If the screen is unlocked, also set current functions.
                         setScreenUnlockedFunctions();
+                    } else {
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                     }
                     break;
                 case MSG_UPDATE_SCREEN_LOCK:
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 6a3469c5..e615428 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -177,15 +177,11 @@
              * Audio Class Specific
              */
             case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE:
-                if (mDeviceDescriptor.getDevClass() == UsbDescriptor.CLASSID_AUDIO) {
-                    descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
-                }
+                descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT:
-                if (mDeviceDescriptor.getDevClass() == UsbDescriptor.CLASSID_AUDIO) {
-                    descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
-                }
+                descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
                 break;
 
             default:
diff --git a/startop/apps/ColorChanging/.gitignore b/startop/apps/ColorChanging/.gitignore
new file mode 100644
index 0000000..2b75303
--- /dev/null
+++ b/startop/apps/ColorChanging/.gitignore
@@ -0,0 +1,13 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/startop/apps/ColorChanging/.idea/encodings.xml b/startop/apps/ColorChanging/.idea/encodings.xml
new file mode 100644
index 0000000..15a15b2
--- /dev/null
+++ b/startop/apps/ColorChanging/.idea/encodings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" addBOMForNewFiles="with NO BOM" />
+</project>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/.idea/gradle.xml b/startop/apps/ColorChanging/.idea/gradle.xml
new file mode 100644
index 0000000..2996d53
--- /dev/null
+++ b/startop/apps/ColorChanging/.idea/gradle.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <compositeConfiguration>
+          <compositeBuild compositeDefinitionSource="SCRIPT" />
+        </compositeConfiguration>
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="resolveModulePerSourceSet" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/.idea/misc.xml b/startop/apps/ColorChanging/.idea/misc.xml
new file mode 100644
index 0000000..37a7509
--- /dev/null
+++ b/startop/apps/ColorChanging/.idea/misc.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/.idea/runConfigurations.xml b/startop/apps/ColorChanging/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/startop/apps/ColorChanging/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RunConfigurationProducerService">
+    <option name="ignoredProducers">
+      <set>
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
+      </set>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/README.md b/startop/apps/ColorChanging/README.md
new file mode 100644
index 0000000..eb8b9cc
--- /dev/null
+++ b/startop/apps/ColorChanging/README.md
@@ -0,0 +1,5 @@
+This directory contains a simple Android app that is meant to help in 
+syncing a trace along with a video in Perfetto.
+
+This app changes the colors of the screen that has traces to go along
+with the colors.
diff --git a/startop/apps/ColorChanging/app/.gitignore b/startop/apps/ColorChanging/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/startop/apps/ColorChanging/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/startop/apps/ColorChanging/app/build.gradle b/startop/apps/ColorChanging/app/build.gradle
new file mode 100644
index 0000000..ab955aa
--- /dev/null
+++ b/startop/apps/ColorChanging/app/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 29
+    buildToolsVersion "29.0.0"
+    defaultConfig {
+        applicationId "com.android.startop.colorchanging"
+        minSdkVersion 15
+        targetSdkVersion 29
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation 'androidx.appcompat:appcompat:1.0.2'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test:runner:1.2.0'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+}
diff --git a/startop/apps/ColorChanging/app/proguard-rules.pro b/startop/apps/ColorChanging/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/startop/apps/ColorChanging/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/startop/apps/ColorChanging/app/src/androidTest/java/com/android/startop/colorchanging/ExampleInstrumentedTest.java b/startop/apps/ColorChanging/app/src/androidTest/java/com/android/startop/colorchanging/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..31736f3
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/androidTest/java/com/android/startop/colorchanging/ExampleInstrumentedTest.java
@@ -0,0 +1,27 @@
+package com.android.startop.colorchanging;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("com.android.startop.colorchanging", appContext.getPackageName());
+    }
+}
diff --git a/startop/apps/ColorChanging/app/src/main/AndroidManifest.xml b/startop/apps/ColorChanging/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..37193b5
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.startop.colorchanging">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity android:name="com.android.startop.colorchanging.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/app/src/main/java/com/android/startop/colorchanging/MainActivity.java b/startop/apps/ColorChanging/app/src/main/java/com/android/startop/colorchanging/MainActivity.java
new file mode 100644
index 0000000..b8f4faf
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/java/com/android/startop/colorchanging/MainActivity.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.startop.colorchanging;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.os.Bundle;
+import android.os.Trace;
+import android.view.View;
+
+public class MainActivity extends AppCompatActivity {
+    View view;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        view = this.getWindow().getDecorView();
+        view.setBackgroundResource(R.color.gray);
+        Trace.beginSection("gray");
+    }
+
+    public void goRed(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.red);
+        Trace.beginSection("red");
+    }
+
+    public void goOrange(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.orange);
+        Trace.beginSection("orange");
+    }
+
+    public void goYellow(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.yellow);
+        Trace.beginSection("yellow");
+    }
+
+    public void goGreen(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.green);
+        Trace.beginSection("green");
+    }
+
+    public void goBlue(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.blue);
+        Trace.beginSection("blue");
+    }
+
+    public void goIndigo(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.indigo);
+        Trace.beginSection("indigo");
+    }
+
+    public void goViolet(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.violet);
+        Trace.beginSection("violet");
+    }
+
+    public void goCyan(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.cyan);
+        Trace.beginSection("cyan");
+    }
+
+    public void goBlack(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.black);
+        Trace.beginSection("black");
+    }
+
+}
diff --git a/startop/apps/ColorChanging/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/startop/apps/ColorChanging/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..1f6bb29
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
diff --git a/startop/apps/ColorChanging/app/src/main/res/drawable/ic_launcher_background.xml b/startop/apps/ColorChanging/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..0d025f9
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#008577"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/startop/apps/ColorChanging/app/src/main/res/layout/activity_main.xml b/startop/apps/ColorChanging/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..fb18df7
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 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"
+    tools:context=".MainActivity">
+
+    <Button
+        android:id="@+id/button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginLeft="16dp"
+        android:layout_marginTop="16dp"
+        android:onClick="goRed"
+        android:text="red"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Button
+        android:id="@+id/button4"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginRight="16dp"
+        android:onClick="goYellow"
+        android:text="YELLOW"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Button
+        android:id="@+id/button6"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginLeft="16dp"
+        android:layout_marginTop="32dp"
+        android:onClick="goGreen"
+        android:text="GREEN"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button" />
+
+    <Button
+        android:id="@+id/button7"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="165dp"
+        android:layout_marginLeft="165dp"
+        android:layout_marginTop="115dp"
+        android:layout_marginEnd="165dp"
+        android:layout_marginRight="165dp"
+        android:onClick="goViolet"
+        android:text="VIOLET"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.428"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button8" />
+
+    <Button
+        android:id="@+id/button10"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="165dp"
+        android:layout_marginLeft="165dp"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="165dp"
+        android:layout_marginRight="165dp"
+        android:onClick="goBlue"
+        android:text="BLUE"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button8" />
+
+    <Button
+        android:id="@+id/button8"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="165dp"
+        android:layout_marginLeft="165dp"
+        android:layout_marginTop="16dp"
+        android:layout_marginEnd="165dp"
+        android:layout_marginRight="165dp"
+        android:onClick="goOrange"
+        android:text="ORANGE"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Button
+        android:id="@+id/button11"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginRight="16dp"
+        android:onClick="goIndigo"
+        android:text="INDIGO"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button4" />
+
+    <Button
+        android:id="@+id/button12"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="162dp"
+        android:layout_marginLeft="162dp"
+        android:layout_marginTop="25dp"
+        android:layout_marginEnd="161dp"
+        android:layout_marginRight="161dp"
+        android:onClick="goCyan"
+        android:text="CYAN"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button7" />
+
+    <Button
+        android:id="@+id/button13"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="162dp"
+        android:layout_marginLeft="162dp"
+        android:layout_marginTop="25dp"
+        android:layout_marginEnd="161dp"
+        android:layout_marginRight="161dp"
+        android:onClick="goBlack"
+        android:text="BLACK"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button12" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/values/colors.xml b/startop/apps/ColorChanging/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..209790f
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/values/colors.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#008577</color>
+    <color name="colorPrimaryDark">#00574B</color>
+    <color name="colorAccent">#D81B60</color>
+    <color name="black">#000000</color>
+    <color name="red">#F44336</color>
+    <color name="green">#2CF035</color>
+    <color name="blue">#2C70F0</color>
+    <color name="yellow">#F0EA2C</color>
+    <color name="gray">#D3D3D3</color>
+    <color name="orange">#E57E0A</color>
+    <color name="indigo">#4B0082</color>
+    <color name="violet">#EE82EE</color>
+    <color name="cyan">#00E8FF</color>
+</resources>
diff --git a/startop/apps/ColorChanging/app/src/main/res/values/strings.xml b/startop/apps/ColorChanging/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ff062fb
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">ColorChanging</string>
+</resources>
diff --git a/startop/apps/ColorChanging/app/src/main/res/values/styles.xml b/startop/apps/ColorChanging/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+<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>
+
+</resources>
diff --git a/startop/apps/ColorChanging/app/src/test/java/com/android/startop/colorchanging/ExampleUnitTest.java b/startop/apps/ColorChanging/app/src/test/java/com/android/startop/colorchanging/ExampleUnitTest.java
new file mode 100644
index 0000000..8423674
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/test/java/com/android/startop/colorchanging/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.android.startop.colorchanging;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/build.gradle b/startop/apps/ColorChanging/build.gradle
new file mode 100644
index 0000000..a960ab3
--- /dev/null
+++ b/startop/apps/ColorChanging/build.gradle
@@ -0,0 +1,24 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.4.1'
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/startop/apps/ColorChanging/gradle.properties b/startop/apps/ColorChanging/gradle.properties
new file mode 100644
index 0000000..199d16e
--- /dev/null
+++ b/startop/apps/ColorChanging/gradle.properties
@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
diff --git a/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.jar b/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
--- /dev/null
+++ b/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.properties b/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..09f2718
--- /dev/null
+++ b/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Jun 17 13:40:58 PDT 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/startop/apps/ColorChanging/gradlew b/startop/apps/ColorChanging/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/startop/apps/ColorChanging/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/startop/apps/ColorChanging/gradlew.bat b/startop/apps/ColorChanging/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/startop/apps/ColorChanging/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/startop/apps/ColorChanging/settings.gradle b/startop/apps/ColorChanging/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/startop/apps/ColorChanging/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/tests/net/util/Android.bp b/startop/apps/test/Android.bp
similarity index 64%
copy from tests/net/util/Android.bp
copy to startop/apps/test/Android.bp
index d8c502d..a4906d7 100644
--- a/tests/net/util/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -14,17 +14,14 @@
 // limitations under the License.
 //
 
-// Common utilities for network tests.
-java_library {
-    name: "frameworks-net-testutils",
-    srcs: ["java/**/*.java"],
-    // test_current to be also appropriate for CTS tests
-    sdk_version: "test_current",
-    static_libs: [
-        "androidx.annotation_annotation",
-        "junit",
+android_app {
+    name: "startop_test_app",
+    srcs: [
+        "src/EmptyActivity.java",
+        "src/LayoutInflationActivity.java",
+        "src/ComplexLayoutInflationActivity.java",
+        "src/FrameLayoutInflationActivity.java",
+        "src/TextViewInflationActivity.java",
     ],
-    libs: [
-        "android.test.base.stubs",
-    ],
-}
\ No newline at end of file
+    platform_apis: true,
+}
diff --git a/startop/apps/test/AndroidManifest.xml b/startop/apps/test/AndroidManifest.xml
new file mode 100644
index 0000000..467d8f7
--- /dev/null
+++ b/startop/apps/test/AndroidManifest.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.startop.test">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true">
+
+        <activity
+            android:label="Complex Layout Test"
+            android:name=".ComplexLayoutInflationActivity"
+            android:exported="true" >
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:label="Empty Activity Layout Test"
+            android:name=".EmptyActivity"
+            android:exported="true" >
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:label="FrameLayout Layout Test"
+            android:name=".FrameLayoutInflationActivity"
+            android:exported="true" >
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:label="TextView Layout Test"
+            android:name=".TextViewInflationActivity"
+            android:exported="true" >
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/startop/apps/test/README.md b/startop/apps/test/README.md
new file mode 100644
index 0000000..dadc66a
--- /dev/null
+++ b/startop/apps/test/README.md
@@ -0,0 +1,26 @@
+This directory contains a simple Android app that is meant to help in doing
+controlled startup performance experiments.
+
+This app is structured as a number of activities that each are useful for a
+different aspect of startup testing.
+
+# Activities
+
+## EmptyActivity
+
+This is the simplest possible Android activity. Starting this exercises only the
+system parts of startup without any app-specific behavior.
+
+    adb shell am start -n com.android.startop.test/.EmptyActivity
+
+## LayoutInflation
+
+This activity inflates a reasonably complex layout to see the impact of layout
+inflation. The layout is supported by the viewcompiler, so this can be used for
+testing precompiled layout performance.
+
+The activity adds an `inflate#activity_main` slice to atrace around the time
+spent in view inflation to make it easier to focus on the time spent in view
+inflation.
+
+    adb shell am start -n com.android.startop.test/.ComplexLayoutInflationActivity
diff --git a/startop/apps/test/res/drawable-v24/ic_launcher_foreground.xml b/startop/apps/test/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..c7bd21d
--- /dev/null
+++ b/startop/apps/test/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1" />
+</vector>
diff --git a/startop/apps/test/res/drawable/ic_launcher_background.xml b/startop/apps/test/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..d5fccc5
--- /dev/null
+++ b/startop/apps/test/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillColor="#26A69A"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+</vector>
diff --git a/startop/apps/test/res/layout/activity_main.xml b/startop/apps/test/res/layout/activity_main.xml
new file mode 100644
index 0000000..16f5641f2
--- /dev/null
+++ b/startop/apps/test/res/layout/activity_main.xml
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout 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"
+    tools:context=".MainActivity" >
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_weight="0.5"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <CheckBox
+            android:id="@+id/checkBox5"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="CheckBox" />
+
+        <EditText
+            android:id="@+id/myEditText"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="EditText" />
+
+        <EditText
+            android:id="@+id/myEditText2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="EditText" />
+
+        <Button
+            android:id="@+id/myButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Button" />
+
+        <Button
+            android:id="@+id/myButton2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Button" />
+
+        <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <CheckBox
+            android:id="@+id/checkBox2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="CheckBox" />
+
+        <RadioButton
+            android:id="@+id/radioButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="RadioButton" />
+
+        <CheckedTextView
+            android:id="@+id/checkedTextView2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="CheckedTextView" />
+
+        <TextView
+            android:id="@+id/textView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <ToggleButton
+            android:id="@+id/toggleButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="ToggleButton" />
+
+        <Switch
+            android:id="@+id/switch1"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Switch" />
+
+        <CheckBox
+            android:id="@+id/checkBox3"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="CheckBox" />
+
+        <CheckBox
+            android:id="@+id/checkBox4"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="CheckBox" />
+
+        <EditText
+            android:id="@+id/editText2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:inputType="textPassword" />
+
+        <RadioButton
+            android:id="@+id/radioButton2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="RadioButton" />
+
+        <EditText
+            android:id="@+id/editText3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:ems="10"
+            android:inputType="numberDecimal" />
+
+        <SearchView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" >
+
+        </SearchView>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_weight="0.5"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <Button
+            android:id="@+id/myButton3"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Button" />
+
+        <Button
+            android:id="@+id/myButton4"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Button" />
+
+        <Button
+            android:id="@+id/button14"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Button" />
+
+        <EditText
+            android:id="@+id/myEditText3"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="EditText" />
+
+        <Button
+            android:id="@+id/button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Button" />
+
+        <EditText
+            android:id="@+id/myEditText4"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="EditText" />
+
+        <ToggleButton
+            android:id="@+id/toggleButton2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="ToggleButton" />
+
+        <ToggleButton
+            android:id="@+id/toggleButton5"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="ToggleButton" />
+
+        <ToggleButton
+            android:id="@+id/toggleButton4"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="ToggleButton" />
+
+        <ToggleButton
+            android:id="@+id/toggleButton3"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="ToggleButton" />
+
+        <ProgressBar
+            android:id="@+id/progressBar2"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <EditText
+            android:id="@+id/editText"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:inputType="date" />
+
+        <CheckedTextView
+            android:id="@+id/checkedTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="CheckedTextView" />
+
+        <SeekBar
+            android:id="@+id/seekBar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <ToggleButton
+            android:id="@+id/toggleButton6"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="ToggleButton" />
+
+        <ToggleButton
+            android:id="@+id/toggleButton7"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="ToggleButton" />
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/startop/apps/test/res/layout/framelayout_list.xml b/startop/apps/test/res/layout/framelayout_list.xml
new file mode 100644
index 0000000..2dd8219
--- /dev/null
+++ b/startop/apps/test/res/layout/framelayout_list.xml
@@ -0,0 +1,5013 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView 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" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ffaaaaaa" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:background="#ff000000" />
+    </LinearLayout>
+</ScrollView>
diff --git a/startop/apps/test/res/layout/textview_list.xml b/startop/apps/test/res/layout/textview_list.xml
new file mode 100644
index 0000000..1cff5b2
--- /dev/null
+++ b/startop/apps/test/res/layout/textview_list.xml
@@ -0,0 +1,5014 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView 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" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+    </LinearLayout>
+</ScrollView>
diff --git a/startop/apps/test/res/mipmap-anydpi-v26/ic_launcher.xml b/startop/apps/test/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/startop/apps/test/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/startop/apps/test/res/mipmap-anydpi-v26/ic_launcher_round.xml b/startop/apps/test/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/startop/apps/test/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/startop/apps/test/res/mipmap-hdpi/ic_launcher.png b/startop/apps/test/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a2f5908
--- /dev/null
+++ b/startop/apps/test/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/test/res/mipmap-hdpi/ic_launcher_round.png b/startop/apps/test/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..1b52399
--- /dev/null
+++ b/startop/apps/test/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/test/res/mipmap-mdpi/ic_launcher.png b/startop/apps/test/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..ff10afd
--- /dev/null
+++ b/startop/apps/test/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/test/res/mipmap-mdpi/ic_launcher_round.png b/startop/apps/test/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..115a4c7
--- /dev/null
+++ b/startop/apps/test/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/test/res/mipmap-xhdpi/ic_launcher.png b/startop/apps/test/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..dcd3cd8
--- /dev/null
+++ b/startop/apps/test/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/test/res/mipmap-xhdpi/ic_launcher_round.png b/startop/apps/test/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..459ca60
--- /dev/null
+++ b/startop/apps/test/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/test/res/mipmap-xxhdpi/ic_launcher.png b/startop/apps/test/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..8ca12fe
--- /dev/null
+++ b/startop/apps/test/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/test/res/mipmap-xxhdpi/ic_launcher_round.png b/startop/apps/test/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..8e19b41
--- /dev/null
+++ b/startop/apps/test/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/test/res/mipmap-xxxhdpi/ic_launcher.png b/startop/apps/test/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b824ebd
--- /dev/null
+++ b/startop/apps/test/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/test/res/mipmap-xxxhdpi/ic_launcher_round.png b/startop/apps/test/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4c19a13
--- /dev/null
+++ b/startop/apps/test/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/test/res/values/colors.xml b/startop/apps/test/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/startop/apps/test/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/startop/apps/test/res/values/strings.xml b/startop/apps/test/res/values/strings.xml
new file mode 100644
index 0000000..18419b5
--- /dev/null
+++ b/startop/apps/test/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">Startup Testing Swiss Army Knife</string>
+</resources>
diff --git a/startop/apps/test/src/ComplexLayoutInflationActivity.java b/startop/apps/test/src/ComplexLayoutInflationActivity.java
new file mode 100644
index 0000000..a357073
--- /dev/null
+++ b/startop/apps/test/src/ComplexLayoutInflationActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.startop.test;
+
+import android.os.Bundle;
+
+/**
+ * This activity inflates a reasonably complex layout to see the impact of
+ * layout inflation. The layout is supported by the viewcompiler, so this can be
+ * used for testing precompiled layout performance.
+ */
+public class ComplexLayoutInflationActivity extends LayoutInflationActivity {
+    protected void onCreate(Bundle savedInstanceState) {
+        Bundle newState = savedInstanceState == null
+                ? new Bundle() : new Bundle(savedInstanceState);
+        newState.putInt(LAYOUT_ID, R.layout.activity_main);
+
+        super.onCreate(newState);
+    }
+}
diff --git a/packages/NetworkPermissionConfig/src/com/android/server/NetworkPermissionConfig.java b/startop/apps/test/src/EmptyActivity.java
similarity index 71%
rename from packages/NetworkPermissionConfig/src/com/android/server/NetworkPermissionConfig.java
rename to startop/apps/test/src/EmptyActivity.java
index c904e23..bcb2e70 100644
--- a/packages/NetworkPermissionConfig/src/com/android/server/NetworkPermissionConfig.java
+++ b/startop/apps/test/src/EmptyActivity.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.startop.test;
 
-import android.app.Application;
+import android.app.Activity;
 
 /**
- * Empty application for NetworkPermissionConfig that only exists because
- * soong builds complain if APKs have no source file.
+ * The simplest possible Android activity, for testing startup with no
+ * app-specific behavior.
  */
-public class NetworkPermissionConfig extends Application {
+public class EmptyActivity extends Activity {
 }
diff --git a/startop/apps/test/src/FrameLayoutInflationActivity.java b/startop/apps/test/src/FrameLayoutInflationActivity.java
new file mode 100644
index 0000000..b995e79
--- /dev/null
+++ b/startop/apps/test/src/FrameLayoutInflationActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.startop.test;
+
+import android.os.Bundle;
+
+public class FrameLayoutInflationActivity extends LayoutInflationActivity {
+    protected void onCreate(Bundle savedInstanceState) {
+        Bundle newState = savedInstanceState == null
+                ? new Bundle() : new Bundle(savedInstanceState);
+        newState.putInt(LAYOUT_ID, R.layout.framelayout_list);
+
+        super.onCreate(newState);
+    }
+}
diff --git a/startop/apps/test/src/LayoutInflationActivity.java b/startop/apps/test/src/LayoutInflationActivity.java
new file mode 100644
index 0000000..06a0570
--- /dev/null
+++ b/startop/apps/test/src/LayoutInflationActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.startop.test;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Trace;
+import android.view.LayoutInflater;
+import android.view.View;
+
+public class LayoutInflationActivity extends Activity {
+    public static String LAYOUT_ID = "layout-id";
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        int layoutId = savedInstanceState.getInt(LAYOUT_ID);
+        String layoutName = getResources().getResourceEntryName(layoutId);
+
+        LayoutInflater inflater = LayoutInflater.from(this);
+        Trace.beginSection("inflate layout: " + layoutName);
+        View view = inflater.inflate(layoutId, /*root=*/null);
+        Trace.endSection();
+        setContentView(view);
+    }
+}
diff --git a/startop/apps/test/src/TextViewInflationActivity.java b/startop/apps/test/src/TextViewInflationActivity.java
new file mode 100644
index 0000000..30e308e
--- /dev/null
+++ b/startop/apps/test/src/TextViewInflationActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.startop.test;
+
+import android.os.Bundle;
+
+public class TextViewInflationActivity extends LayoutInflationActivity {
+    protected void onCreate(Bundle savedInstanceState) {
+        Bundle newState = savedInstanceState == null
+                ? new Bundle() : new Bundle(savedInstanceState);
+        newState.putInt(LAYOUT_ID, R.layout.textview_list);
+
+        super.onCreate(newState);
+    }
+}
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 92ea872..4f6524e 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -77,7 +77,6 @@
     name: "view-compiler-tests",
     defaults: ["viewcompiler_defaults"],
     srcs: [
-        "dex_builder_test.cc",
         "layout_validation_test.cc",
         "util_test.cc",
     ],
diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc
index 6047e8c..499c42e 100644
--- a/startop/view_compiler/dex_builder.cc
+++ b/startop/view_compiler/dex_builder.cc
@@ -102,6 +102,18 @@
     case Instruction::Op::kCheckCast:
       out << "kCheckCast";
       return out;
+    case Instruction::Op::kGetStaticField:
+      out << "kGetStaticField";
+      return out;
+    case Instruction::Op::kSetStaticField:
+      out << "kSetStaticField";
+      return out;
+    case Instruction::Op::kGetInstanceField:
+      out << "kGetInstanceField";
+      return out;
+    case Instruction::Op::kSetInstanceField:
+      out << "kSetInstanceField";
+      return out;
   }
 }
 
@@ -229,6 +241,23 @@
   return type;
 }
 
+ir::FieldDecl* DexBuilder::GetOrAddField(TypeDescriptor parent, const std::string& name,
+                                         TypeDescriptor type) {
+  const auto key = std::make_tuple(parent, name);
+  if (field_decls_by_key_.find(key) != field_decls_by_key_.end()) {
+    return field_decls_by_key_[key];
+  }
+
+  ir::FieldDecl* field = Alloc<ir::FieldDecl>();
+  field->parent = GetOrAddType(parent);
+  field->name = GetOrAddString(name);
+  field->type = GetOrAddType(type);
+  field->orig_index = dex_file_->fields_indexes.AllocateIndex();
+  dex_file_->fields_map[field->orig_index] = field;
+  field_decls_by_key_[key] = field;
+  return field;
+}
+
 ir::Proto* Prototype::Encode(DexBuilder* dex) const {
   auto* proto = dex->Alloc<ir::Proto>();
   proto->shorty = dex->GetOrAddString(Shorty());
@@ -360,6 +389,11 @@
       return EncodeNew(instruction);
     case Instruction::Op::kCheckCast:
       return EncodeCast(instruction);
+    case Instruction::Op::kGetStaticField:
+    case Instruction::Op::kSetStaticField:
+    case Instruction::Op::kGetInstanceField:
+    case Instruction::Op::kSetInstanceField:
+      return EncodeFieldOp(instruction);
   }
 }
 
@@ -428,7 +462,7 @@
     // first move all the arguments into contiguous temporary registers.
     std::array<Value, kMaxArgs> scratch = GetScratchRegisters<kMaxArgs>();
 
-    const auto& prototype = dex_->GetPrototypeByMethodId(instruction.method_id());
+    const auto& prototype = dex_->GetPrototypeByMethodId(instruction.index_argument());
     CHECK(prototype.has_value());
 
     for (size_t i = 0; i < instruction.args().size(); ++i) {
@@ -452,12 +486,12 @@
 
     Encode3rc(InvokeToInvokeRange(opcode),
               instruction.args().size(),
-              instruction.method_id(),
+              instruction.index_argument(),
               RegisterValue(scratch[0]));
   } else {
     Encode35c(opcode,
               instruction.args().size(),
-              instruction.method_id(),
+              instruction.index_argument(),
               arguments[0],
               arguments[1],
               arguments[2],
@@ -514,6 +548,54 @@
   Encode21c(::art::Instruction::CHECK_CAST, RegisterValue(*instruction.dest()), type.value());
 }
 
+void MethodBuilder::EncodeFieldOp(const Instruction& instruction) {
+  const auto& args = instruction.args();
+  switch (instruction.opcode()) {
+    case Instruction::Op::kGetStaticField: {
+      CHECK(instruction.dest().has_value());
+      CHECK(instruction.dest()->is_variable());
+      CHECK_EQ(0, instruction.args().size());
+
+      Encode21c(::art::Instruction::SGET,
+                RegisterValue(*instruction.dest()),
+                instruction.index_argument());
+      break;
+    }
+    case Instruction::Op::kSetStaticField: {
+      CHECK(!instruction.dest().has_value());
+      CHECK_EQ(1, args.size());
+      CHECK(args[0].is_variable());
+
+      Encode21c(::art::Instruction::SPUT, RegisterValue(args[0]), instruction.index_argument());
+      break;
+    }
+    case Instruction::Op::kGetInstanceField: {
+      CHECK(instruction.dest().has_value());
+      CHECK(instruction.dest()->is_variable());
+      CHECK_EQ(1, instruction.args().size());
+
+      Encode22c(::art::Instruction::IGET,
+                RegisterValue(*instruction.dest()),
+                RegisterValue(args[0]),
+                instruction.index_argument());
+      break;
+    }
+    case Instruction::Op::kSetInstanceField: {
+      CHECK(!instruction.dest().has_value());
+      CHECK_EQ(2, args.size());
+      CHECK(args[0].is_variable());
+      CHECK(args[1].is_variable());
+
+      Encode22c(::art::Instruction::IPUT,
+                RegisterValue(args[1]),
+                RegisterValue(args[0]),
+                instruction.index_argument());
+      break;
+    }
+    default: { LOG(FATAL) << "Unsupported field operation"; }
+  }
+}
+
 size_t MethodBuilder::RegisterValue(const Value& value) const {
   if (value.is_register()) {
     return value.value();
diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h
index 541d800..292d659 100644
--- a/startop/view_compiler/dex_builder.h
+++ b/startop/view_compiler/dex_builder.h
@@ -153,6 +153,8 @@
     kBranchEqz,
     kBranchNEqz,
     kCheckCast,
+    kGetInstanceField,
+    kGetStaticField,
     kInvokeDirect,
     kInvokeInterface,
     kInvokeStatic,
@@ -162,6 +164,8 @@
     kNew,
     kReturn,
     kReturnObject,
+    kSetInstanceField,
+    kSetStaticField
   };
 
   ////////////////////////
@@ -170,12 +174,12 @@
 
   // For instructions with no return value and no arguments.
   static inline Instruction OpNoArgs(Op opcode) {
-    return Instruction{opcode, /*method_id*/ 0, /*dest*/ {}};
+    return Instruction{opcode, /*index_argument*/ 0, /*dest*/ {}};
   }
   // For most instructions, which take some number of arguments and have an optional return value.
   template <typename... T>
   static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest, T... args) {
-    return Instruction{opcode, /*method_id=*/0, /*result_is_object=*/false, dest, args...};
+    return Instruction{opcode, /*index_argument=*/0, /*result_is_object=*/false, dest, args...};
   }
 
   // A cast instruction. Basically, `(type)val`
@@ -186,49 +190,71 @@
 
   // For method calls.
   template <typename... T>
-  static inline Instruction InvokeVirtual(size_t method_id, std::optional<const Value> dest,
+  static inline Instruction InvokeVirtual(size_t index_argument, std::optional<const Value> dest,
                                           Value this_arg, T... args) {
     return Instruction{
-        Op::kInvokeVirtual, method_id, /*result_is_object=*/false, dest, this_arg, args...};
+        Op::kInvokeVirtual, index_argument, /*result_is_object=*/false, dest, this_arg, args...};
   }
   // Returns an object
   template <typename... T>
-  static inline Instruction InvokeVirtualObject(size_t method_id, std::optional<const Value> dest,
-                                                Value this_arg, T... args) {
+  static inline Instruction InvokeVirtualObject(size_t index_argument,
+                                                std::optional<const Value> dest, Value this_arg,
+                                                T... args) {
     return Instruction{
-        Op::kInvokeVirtual, method_id, /*result_is_object=*/true, dest, this_arg, args...};
+        Op::kInvokeVirtual, index_argument, /*result_is_object=*/true, dest, this_arg, args...};
   }
   // For direct calls (basically, constructors).
   template <typename... T>
-  static inline Instruction InvokeDirect(size_t method_id, std::optional<const Value> dest,
+  static inline Instruction InvokeDirect(size_t index_argument, std::optional<const Value> dest,
                                          Value this_arg, T... args) {
     return Instruction{
-        Op::kInvokeDirect, method_id, /*result_is_object=*/false, dest, this_arg, args...};
+        Op::kInvokeDirect, index_argument, /*result_is_object=*/false, dest, this_arg, args...};
   }
   // Returns an object
   template <typename... T>
-  static inline Instruction InvokeDirectObject(size_t method_id, std::optional<const Value> dest,
-                                               Value this_arg, T... args) {
-    return Instruction{
-        Op::kInvokeDirect, method_id, /*result_is_object=*/true, dest, this_arg, args...};
-  }
-  // For static calls.
-  template <typename... T>
-  static inline Instruction InvokeStatic(size_t method_id, std::optional<const Value> dest,
-                                         T... args) {
-    return Instruction{Op::kInvokeStatic, method_id, /*result_is_object=*/false, dest, args...};
-  }
-  // Returns an object
-  template <typename... T>
-  static inline Instruction InvokeStaticObject(size_t method_id, std::optional<const Value> dest,
+  static inline Instruction InvokeDirectObject(size_t index_argument,
+                                               std::optional<const Value> dest, Value this_arg,
                                                T... args) {
-    return Instruction{Op::kInvokeStatic, method_id, /*result_is_object=*/true, dest, args...};
+    return Instruction{
+        Op::kInvokeDirect, index_argument, /*result_is_object=*/true, dest, this_arg, args...};
   }
   // For static calls.
   template <typename... T>
-  static inline Instruction InvokeInterface(size_t method_id, std::optional<const Value> dest,
+  static inline Instruction InvokeStatic(size_t index_argument, std::optional<const Value> dest,
+                                         T... args) {
+    return Instruction{
+        Op::kInvokeStatic, index_argument, /*result_is_object=*/false, dest, args...};
+  }
+  // Returns an object
+  template <typename... T>
+  static inline Instruction InvokeStaticObject(size_t index_argument,
+                                               std::optional<const Value> dest, T... args) {
+    return Instruction{Op::kInvokeStatic, index_argument, /*result_is_object=*/true, dest, args...};
+  }
+  // For static calls.
+  template <typename... T>
+  static inline Instruction InvokeInterface(size_t index_argument, std::optional<const Value> dest,
                                             T... args) {
-    return Instruction{Op::kInvokeInterface, method_id, /*result_is_object=*/false, dest, args...};
+    return Instruction{
+        Op::kInvokeInterface, index_argument, /*result_is_object=*/false, dest, args...};
+  }
+
+  static inline Instruction GetStaticField(size_t field_id, Value dest) {
+    return Instruction{Op::kGetStaticField, field_id, dest};
+  }
+
+  static inline Instruction SetStaticField(size_t field_id, Value value) {
+    return Instruction{
+        Op::kSetStaticField, field_id, /*result_is_object=*/false, /*dest=*/{}, value};
+  }
+
+  static inline Instruction GetField(size_t field_id, Value dest, Value object) {
+    return Instruction{Op::kGetInstanceField, field_id, /*result_is_object=*/false, dest, object};
+  }
+
+  static inline Instruction SetField(size_t field_id, Value object, Value value) {
+    return Instruction{
+        Op::kSetInstanceField, field_id, /*result_is_object=*/false, /*dest=*/{}, object, value};
   }
 
   ///////////////
@@ -236,27 +262,31 @@
   ///////////////
 
   Op opcode() const { return opcode_; }
-  size_t method_id() const { return method_id_; }
+  size_t index_argument() const { return index_argument_; }
   bool result_is_object() const { return result_is_object_; }
   const std::optional<const Value>& dest() const { return dest_; }
   const std::vector<const Value>& args() const { return args_; }
 
  private:
-  inline Instruction(Op opcode, size_t method_id, std::optional<const Value> dest)
-      : opcode_{opcode}, method_id_{method_id}, result_is_object_{false}, dest_{dest}, args_{} {}
+  inline Instruction(Op opcode, size_t index_argument, std::optional<const Value> dest)
+      : opcode_{opcode},
+        index_argument_{index_argument},
+        result_is_object_{false},
+        dest_{dest},
+        args_{} {}
 
   template <typename... T>
-  inline constexpr Instruction(Op opcode, size_t method_id, bool result_is_object,
+  inline Instruction(Op opcode, size_t index_argument, bool result_is_object,
                                std::optional<const Value> dest, T... args)
       : opcode_{opcode},
-        method_id_{method_id},
+        index_argument_{index_argument},
         result_is_object_{result_is_object},
         dest_{dest},
         args_{args...} {}
 
   const Op opcode_;
   // The index of the method to invoke, for kInvokeVirtual and similar opcodes.
-  const size_t method_id_{0};
+  const size_t index_argument_{0};
   const bool result_is_object_;
   const std::optional<const Value> dest_;
   const std::vector<const Value> args_;
@@ -319,6 +349,7 @@
   void EncodeBranch(art::Instruction::Code op, const Instruction& instruction);
   void EncodeNew(const Instruction& instruction);
   void EncodeCast(const Instruction& instruction);
+  void EncodeFieldOp(const Instruction& instruction);
 
   // Low-level instruction format encoding. See
   // https://source.android.com/devices/tech/dalvik/instruction-formats for documentation of
@@ -351,6 +382,14 @@
     buffer_.push_back(b);
   }
 
+  inline void Encode22c(art::Instruction::Code opcode, uint8_t a, uint8_t b, uint16_t c) {
+    // b|a|op|bbbb
+    CHECK(IsShortRegister(a));
+    CHECK(IsShortRegister(b));
+    buffer_.push_back((b << 12) | (a << 8) | opcode);
+    buffer_.push_back(c);
+  }
+
   inline void Encode32x(art::Instruction::Code opcode, uint16_t a, uint16_t b) {
     buffer_.push_back(opcode);
     buffer_.push_back(a);
@@ -481,6 +520,11 @@
   // See the TypeDescriptor class for help generating these. GetOrAddType can be used to declare
   // imported classes.
   ir::Type* GetOrAddType(const std::string& descriptor);
+  inline ir::Type* GetOrAddType(TypeDescriptor descriptor) {
+    return GetOrAddType(descriptor.descriptor());
+  }
+
+  ir::FieldDecl* GetOrAddField(TypeDescriptor parent, const std::string& name, TypeDescriptor type);
 
   // Returns the method id for the method, creating it if it has not been created yet.
   const MethodDeclData& GetOrDeclareMethod(TypeDescriptor type, const std::string& name,
@@ -526,6 +570,9 @@
 
   // Keep track of already-encoded protos.
   std::map<Prototype, ir::Proto*> proto_map_;
+
+  // Keep track of fields that have been declared
+  std::map<std::tuple<TypeDescriptor, std::string>, ir::FieldDecl*> field_decls_by_key_;
 };
 
 template <typename... T>
diff --git a/startop/view_compiler/dex_builder_test.cc b/startop/view_compiler/dex_builder_test.cc
deleted file mode 100644
index 90c256f..0000000
--- a/startop/view_compiler/dex_builder_test.cc
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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.
- */
-
-#include "dex_builder.h"
-
-#include "dex/art_dex_file_loader.h"
-#include "dex/dex_file.h"
-#include "gtest/gtest.h"
-
-using namespace startop::dex;
-
-// Takes a DexBuilder, encodes it into an in-memory DEX file, verifies the resulting DEX file and
-// returns whether the verification was successful.
-bool EncodeAndVerify(DexBuilder* dex_file) {
-  slicer::MemView image{dex_file->CreateImage()};
-
-  art::ArtDexFileLoader loader;
-  std::string error_msg;
-  std::unique_ptr<const art::DexFile> loaded_dex_file{loader.Open(image.ptr<const uint8_t>(),
-                                                                  image.size(),
-                                                                  /*location=*/"",
-                                                                  /*location_checksum=*/0,
-                                                                  /*oat_dex_file=*/nullptr,
-                                                                  /*verify=*/true,
-                                                                  /*verify_checksum=*/false,
-                                                                  &error_msg)};
-  return loaded_dex_file != nullptr;
-}
-
-// Write out and verify a DEX file that corresponds to:
-//
-// package dextest;
-// public class DexTest {
-//     public static void foo() {}
-// }
-TEST(DexBuilderTest, VerifyDexWithClassMethod) {
-  DexBuilder dex_file;
-
-  auto cbuilder{dex_file.MakeClass("dextest.DexTest")};
-
-  auto method{cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Void()})};
-  method.BuildReturn();
-  method.Encode();
-
-  EXPECT_TRUE(EncodeAndVerify(&dex_file));
-}
-
-// Makes sure a bad DEX class fails to verify.
-TEST(DexBuilderTest, VerifyBadDexWithClassMethod) {
-  DexBuilder dex_file;
-
-  auto cbuilder{dex_file.MakeClass("dextest.DexTest")};
-
-  // This method has the error, because methods cannot take Void() as a parameter.
-  auto method{
-      cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Void(), TypeDescriptor::Void()})};
-  method.BuildReturn();
-  method.Encode();
-
-  EXPECT_FALSE(EncodeAndVerify(&dex_file));
-}
-
-// Write out and verify a DEX file that corresponds to:
-//
-// package dextest;
-// public class DexTest {
-//     public static int foo() { return 5; }
-// }
-TEST(DexBuilderTest, VerifyDexReturn5) {
-  DexBuilder dex_file;
-
-  auto cbuilder{dex_file.MakeClass("dextest.DexTest")};
-
-  auto method{cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Int()})};
-  auto r = method.MakeRegister();
-  method.BuildConst4(r, 5);
-  method.BuildReturn(r);
-  method.Encode();
-
-  EXPECT_TRUE(EncodeAndVerify(&dex_file));
-}
-
-// Write out and verify a DEX file that corresponds to:
-//
-// package dextest;
-// public class DexTest {
-//     public static int foo(int x) { return x; }
-// }
-TEST(DexBuilderTest, VerifyDexReturnIntParam) {
-  DexBuilder dex_file;
-
-  auto cbuilder{dex_file.MakeClass("dextest.DexTest")};
-
-  auto method{
-      cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
-  method.BuildReturn(Value::Parameter(0));
-  method.Encode();
-
-  EXPECT_TRUE(EncodeAndVerify(&dex_file));
-}
-
-// Write out and verify a DEX file that corresponds to:
-//
-// package dextest;
-// public class DexTest {
-//     public static int foo(String s) { return s.length(); }
-// }
-TEST(DexBuilderTest, VerifyDexCallStringLength) {
-  DexBuilder dex_file;
-
-  auto cbuilder{dex_file.MakeClass("dextest.DexTest")};
-
-  MethodBuilder method{cbuilder.CreateMethod(
-      "foo", Prototype{TypeDescriptor::Int(), TypeDescriptor::FromClassname("java.lang.String")})};
-
-  Value result = method.MakeRegister();
-
-  MethodDeclData string_length =
-      dex_file.GetOrDeclareMethod(TypeDescriptor::FromClassname("java.lang.String"),
-                                  "length",
-                                  Prototype{TypeDescriptor::Int()});
-
-  method.AddInstruction(Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0)));
-  method.BuildReturn(result);
-
-  method.Encode();
-
-  EXPECT_TRUE(EncodeAndVerify(&dex_file));
-}
-
-// Write out and verify a DEX file that corresponds to:
-//
-// package dextest;
-// public class DexTest {
-//     public static int foo(String s) { return s.length(); }
-// }
-TEST(DexBuilderTest, VerifyDexCallManyRegisters) {
-  DexBuilder dex_file;
-
-  auto cbuilder{dex_file.MakeClass("dextest.DexTest")};
-
-  MethodBuilder method{cbuilder.CreateMethod(
-      "foo", Prototype{TypeDescriptor::Int()})};
-
-  Value result = method.MakeRegister();
-
-  // Make a bunch of registers
-  for (size_t i = 0; i < 25; ++i) {
-    method.MakeRegister();
-  }
-
-  // Now load a string literal into a register
-  Value string_val = method.MakeRegister();
-  method.BuildConstString(string_val, "foo");
-
-  MethodDeclData string_length =
-      dex_file.GetOrDeclareMethod(TypeDescriptor::FromClassname("java.lang.String"),
-                                  "length",
-                                  Prototype{TypeDescriptor::Int()});
-
-  method.AddInstruction(Instruction::InvokeVirtual(string_length.id, result, string_val));
-  method.BuildReturn(result);
-
-  method.Encode();
-
-  EXPECT_TRUE(EncodeAndVerify(&dex_file));
-}
diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp
index ac60e96..9ad1ca1 100644
--- a/startop/view_compiler/dex_builder_test/Android.bp
+++ b/startop/view_compiler/dex_builder_test/Android.bp
@@ -39,6 +39,7 @@
     srcs: [
         "src/android/startop/test/DexBuilderTest.java",
         "src/android/startop/test/LayoutCompilerTest.java",
+        "src/android/startop/test/TestClass.java",
     ],
     sdk_version: "current",
     data: [":generate_dex_testcases", ":generate_compiled_layout1", ":generate_compiled_layout2"],
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
index 42d4161..93496d0 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
@@ -28,10 +28,10 @@
 
 // Adding tests here requires changes in several other places. See README.md in
 // the view_compiler directory for more information.
-public class DexBuilderTest {
+public final class DexBuilderTest {
   static ClassLoader loadDexFile(String filename) throws Exception {
     return new PathClassLoader("/data/local/tmp/dex-builder-test/" + filename,
-        ClassLoader.getSystemClassLoader());
+        DexBuilderTest.class.getClassLoader());
   }
 
   public void hello() {}
@@ -171,4 +171,44 @@
     }
     Assert.assertTrue(castFailed);
   }
+
+  @Test
+  public void readStaticField() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("readStaticField");
+    TestClass.staticInteger = 5;
+    Assert.assertEquals(5, method.invoke(null));
+  }
+
+  @Test
+  public void setStaticField() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("setStaticField");
+    TestClass.staticInteger = 5;
+    method.invoke(null);
+    Assert.assertEquals(7, TestClass.staticInteger);
+  }
+
+  @Test
+  public void readInstanceField() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("readInstanceField", TestClass.class);
+    TestClass obj = new TestClass();
+    obj.instanceField = 5;
+    Assert.assertEquals(5, method.invoke(null, obj));
+  }
+
+  @Test
+  public void setInstanceField() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("setInstanceField", TestClass.class);
+    TestClass obj = new TestClass();
+    obj.instanceField = 5;
+    method.invoke(null, obj);
+    Assert.assertEquals(7, obj.instanceField);
+  }
 }
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java
new file mode 100644
index 0000000..dd77923
--- /dev/null
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java
@@ -0,0 +1,23 @@
+/*
+ * 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.startop.test;
+
+ /**
+ * A simple class to help test DexBuilder.
+ */
+public final class TestClass {
+    public static int staticInteger;
+
+    public int instanceField;
+}
diff --git a/startop/view_compiler/dex_layout_compiler.cc b/startop/view_compiler/dex_layout_compiler.cc
index c68793d..8febfb7 100644
--- a/startop/view_compiler/dex_layout_compiler.cc
+++ b/startop/view_compiler/dex_layout_compiler.cc
@@ -23,25 +23,6 @@
 
 using android::base::StringPrintf;
 
-void LayoutValidationVisitor::VisitStartTag(const std::u16string& name) {
-  if (0 == name.compare(u"merge")) {
-    message_ = "Merge tags are not supported";
-    can_compile_ = false;
-  }
-  if (0 == name.compare(u"include")) {
-    message_ = "Include tags are not supported";
-    can_compile_ = false;
-  }
-  if (0 == name.compare(u"view")) {
-    message_ = "View tags are not supported";
-    can_compile_ = false;
-  }
-  if (0 == name.compare(u"fragment")) {
-    message_ = "Fragment tags are not supported";
-    can_compile_ = false;
-  }
-}
-
 DexViewBuilder::DexViewBuilder(dex::MethodBuilder* method)
     : method_{method},
       context_{dex::Value::Parameter(0)},
diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc
index f62ec5dd..6dedf24 100644
--- a/startop/view_compiler/dex_testcase_generator.cc
+++ b/startop/view_compiler/dex_testcase_generator.cc
@@ -282,6 +282,62 @@
     method.Encode();
   }(castObjectToString);
 
+  TypeDescriptor test_class = TypeDescriptor::FromClassname("android.startop.test.TestClass");
+
+  // Read a static field
+  // int readStaticField() { return TestClass.staticInteger; }
+  MethodBuilder readStaticField{
+      cbuilder.CreateMethod("readStaticField", Prototype{TypeDescriptor::Int()})};
+  [&](MethodBuilder& method) {
+    const ir::FieldDecl* field =
+        dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int());
+    Value result{method.MakeRegister()};
+    method.AddInstruction(Instruction::GetStaticField(field->orig_index, result));
+    method.BuildReturn(result, /*is_object=*/false);
+    method.Encode();
+  }(readStaticField);
+
+  // Set a static field
+  // void setStaticField() { TestClass.staticInteger = 7; }
+  MethodBuilder setStaticField{
+      cbuilder.CreateMethod("setStaticField", Prototype{TypeDescriptor::Void()})};
+  [&](MethodBuilder& method) {
+    const ir::FieldDecl* field =
+        dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int());
+    Value number{method.MakeRegister()};
+    method.BuildConst4(number, 7);
+    method.AddInstruction(Instruction::SetStaticField(field->orig_index, number));
+    method.BuildReturn();
+    method.Encode();
+  }(setStaticField);
+
+  // Read an instance field
+  // int readInstanceField(TestClass obj) { return obj.instanceField; }
+  MethodBuilder readInstanceField{
+      cbuilder.CreateMethod("readInstanceField", Prototype{TypeDescriptor::Int(), test_class})};
+  [&](MethodBuilder& method) {
+    const ir::FieldDecl* field =
+        dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int());
+    Value result{method.MakeRegister()};
+    method.AddInstruction(Instruction::GetField(field->orig_index, result, Value::Parameter(0)));
+    method.BuildReturn(result, /*is_object=*/false);
+    method.Encode();
+  }(readInstanceField);
+
+  // Set an instance field
+  // void setInstanceField(TestClass obj) { obj.instanceField = 7; }
+  MethodBuilder setInstanceField{
+      cbuilder.CreateMethod("setInstanceField", Prototype{TypeDescriptor::Void(), test_class})};
+  [&](MethodBuilder& method) {
+    const ir::FieldDecl* field =
+        dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int());
+    Value number{method.MakeRegister()};
+    method.BuildConst4(number, 7);
+    method.AddInstruction(Instruction::SetField(field->orig_index, Value::Parameter(0), number));
+    method.BuildReturn();
+    method.Encode();
+  }(setInstanceField);
+
   slicer::MemView image{dex_file.CreateImage()};
   std::ofstream out_file(outdir + "/simple.dex");
   out_file.write(image.ptr<const char>(), image.size());
diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java
index a9b75a3..4acee7d 100644
--- a/telecomm/java/android/telecom/AudioState.java
+++ b/telecomm/java/android/telecom/AudioState.java
@@ -16,6 +16,8 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -81,7 +83,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null) {
             return false;
         }
@@ -93,6 +95,7 @@
                 getSupportedRouteMask() == state.getSupportedRouteMask();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return String.format(Locale.US,
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 9adeea0..1822cee 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -144,6 +144,16 @@
     public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS =
             "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
 
+
+    /**
+     * Extra key used to indicate whether a {@link CallScreeningService} has requested to silence
+     * the ringtone for a call.  If the {@link InCallService} declares
+     * {@link TelecomManager#METADATA_IN_CALL_SERVICE_RINGING} in its manifest, it should not
+     * play a ringtone for an incoming call with this extra key set.
+     */
+    public static final String EXTRA_SILENT_RINGING_REQUESTED =
+            "android.telecom.extra.SILENT_RINGING_REQUESTED";
+
     /**
      * Call event sent from a {@link Call} via {@link #sendCallEvent(String, Bundle)} to inform
      * Telecom that the user has requested that the current {@link Call} should be handed over
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 27a8638..8b04b01 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -137,12 +137,14 @@
     public static class CallResponse {
         private final boolean mShouldDisallowCall;
         private final boolean mShouldRejectCall;
+        private final boolean mShouldSilenceCall;
         private final boolean mShouldSkipCallLog;
         private final boolean mShouldSkipNotification;
 
         private CallResponse(
                 boolean shouldDisallowCall,
                 boolean shouldRejectCall,
+                boolean shouldSilenceCall,
                 boolean shouldSkipCallLog,
                 boolean shouldSkipNotification) {
             if (!shouldDisallowCall
@@ -154,6 +156,7 @@
             mShouldRejectCall = shouldRejectCall;
             mShouldSkipCallLog = shouldSkipCallLog;
             mShouldSkipNotification = shouldSkipNotification;
+            mShouldSilenceCall = shouldSilenceCall;
         }
 
         /*
@@ -172,6 +175,13 @@
         }
 
         /*
+         * @return Whether the ringtone should be silenced for the incoming call.
+         */
+        public boolean getSilenceCall() {
+            return mShouldSilenceCall;
+        }
+
+        /*
          * @return Whether the incoming call should not be displayed in the call log.
          */
         public boolean getSkipCallLog() {
@@ -188,6 +198,7 @@
         public static class Builder {
             private boolean mShouldDisallowCall;
             private boolean mShouldRejectCall;
+            private boolean mShouldSilenceCall;
             private boolean mShouldSkipCallLog;
             private boolean mShouldSkipNotification;
 
@@ -209,6 +220,21 @@
             }
 
             /**
+             * Sets whether ringing should be silenced for the incoming call.  When set
+             * to {@code true}, the Telecom framework will not play a ringtone for the call.
+             * The call will, however, still be sent to the default dialer app if it is not blocked.
+             * A {@link CallScreeningService} can use this to ensure a potential nuisance call is
+             * still surfaced to the user, but in a less intrusive manner.
+             *
+             * Setting this to true only makes sense when the call has not been disallowed
+             * using {@link #setDisallowCall(boolean)}.
+             */
+            public @NonNull Builder setSilenceCall(boolean shouldSilenceCall) {
+                mShouldSilenceCall = shouldSilenceCall;
+                return this;
+            }
+
+            /**
              * Sets whether the incoming call should not be displayed in the call log. This property
              * should only be set to true if the call is disallowed.
              * <p>
@@ -234,6 +260,7 @@
                 return new CallResponse(
                         mShouldDisallowCall,
                         mShouldRejectCall,
+                        mShouldSilenceCall,
                         mShouldSkipCallLog,
                         mShouldSkipNotification);
             }
@@ -285,10 +312,11 @@
     public abstract void onScreenCall(@NonNull Call.Details callDetails);
 
     /**
-     * Responds to the given incoming call, either allowing it or disallowing it.
+     * Responds to the given incoming call, either allowing it, silencing it or disallowing it.
      * <p>
      * The {@link CallScreeningService} calls this method to inform the system whether the call
-     * should be silently blocked or not.
+     * should be silently blocked or not. In the event that it should not be blocked, it may
+     * also be requested to ring silently.
      * <p>
      * Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is
      * {@link Call.Details#DIRECTION_INCOMING}.
@@ -310,6 +338,8 @@
                         !response.getSkipCallLog(),
                         !response.getSkipNotification(),
                         new ComponentName(getPackageName(), getClass().getName()));
+            } else if (response.getSilenceCall()) {
+                mCallScreeningAdapter.silenceCall(callDetails.getTelecomCallId());
             } else {
                 mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
             }
diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java
index c45bd6b..4a8bae7 100644
--- a/telecomm/java/android/telecom/Logging/Session.java
+++ b/telecomm/java/android/telecom/Logging/Session.java
@@ -33,6 +33,8 @@
  */
 public class Session {
 
+    public static final String LOG_TAG = "Session";
+
     public static final String START_SESSION = "START_SESSION";
     public static final String START_EXTERNAL_SESSION = "START_EXTERNAL_SESSION";
     public static final String CREATE_SUBSESSION = "CREATE_SUBSESSION";
@@ -45,6 +47,9 @@
     public static final String EXTERNAL_INDICATOR = "E-";
     public static final String TRUNCATE_STRING = "...";
 
+    // Prevent infinite recursion by setting a reasonable limit.
+    private static final int SESSION_RECURSION_LIMIT = 25;
+
     /**
      * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()}
      * if the Session is canceled.
@@ -226,6 +231,15 @@
 
     // Builds full session id recursively
     private String getFullSessionId() {
+        return getFullSessionId(0);
+    }
+
+    // keep track of calls and bail if we hit the recursion limit
+    private String getFullSessionId(int parentCount) {
+        if (parentCount >= SESSION_RECURSION_LIMIT) {
+            Log.w(LOG_TAG, "getFullSessionId: Hit recursion limit!");
+            return TRUNCATE_STRING + mSessionId;
+        }
         // Cache mParentSession locally to prevent a concurrency problem where
         // Log.endParentSessions() is called while a logging statement is running (Log.i, for
         // example) and setting mParentSession to null in a different thread after the null check
@@ -235,42 +249,57 @@
             return mSessionId;
         } else {
             if (Log.VERBOSE) {
-                return parentSession.getFullSessionId() +
+                return parentSession.getFullSessionId(parentCount + 1)
                         // Append "_X" to subsession to show subsession designation.
-                        SESSION_SEPARATION_CHAR_CHILD + mSessionId;
+                        + SESSION_SEPARATION_CHAR_CHILD + mSessionId;
             } else {
                 // Only worry about the base ID at the top of the tree.
-                return parentSession.getFullSessionId();
+                return parentSession.getFullSessionId(parentCount + 1);
             }
 
         }
     }
 
-    // Print out the full Session tree from any subsession node
-    public String printFullSessionTree() {
-        // Get to the top of the tree
+    private Session getRootSession(String callingMethod) {
+        int currParentCount = 0;
         Session topNode = this;
         while (topNode.getParentSession() != null) {
+            if (currParentCount >= SESSION_RECURSION_LIMIT) {
+                Log.w(LOG_TAG, "getRootSession: Hit recursion limit from " + callingMethod);
+                break;
+            }
             topNode = topNode.getParentSession();
+            currParentCount++;
         }
-        return topNode.printSessionTree();
+        return topNode;
+    }
+
+    // Print out the full Session tree from any subsession node
+    public String printFullSessionTree() {
+        return getRootSession("printFullSessionTree").printSessionTree();
     }
 
     // Recursively move down session tree using DFS, but print out each node when it is reached.
-    public String printSessionTree() {
+    private String printSessionTree() {
         StringBuilder sb = new StringBuilder();
-        printSessionTree(0, sb);
+        printSessionTree(0, sb, 0);
         return sb.toString();
     }
 
-    private void printSessionTree(int tabI, StringBuilder sb) {
+    private void printSessionTree(int tabI, StringBuilder sb, int currChildCount) {
+        // Prevent infinite recursion.
+        if (currChildCount >= SESSION_RECURSION_LIMIT) {
+            Log.w(LOG_TAG, "printSessionTree: Hit recursion limit!");
+            sb.append(TRUNCATE_STRING);
+            return;
+        }
         sb.append(toString());
         for (Session child : mChildSessions) {
             sb.append("\n");
             for (int i = 0; i <= tabI; i++) {
                 sb.append("\t");
             }
-            child.printSessionTree(tabI + 1, sb);
+            child.printSessionTree(tabI + 1, sb, currChildCount + 1);
         }
     }
 
@@ -279,11 +308,17 @@
     // recent) will be truncated to "..."
     public String getFullMethodPath(boolean truncatePath) {
         StringBuilder sb = new StringBuilder();
-        getFullMethodPath(sb, truncatePath);
+        getFullMethodPath(sb, truncatePath, 0);
         return sb.toString();
     }
 
-    private synchronized void getFullMethodPath(StringBuilder sb, boolean truncatePath) {
+    private synchronized void getFullMethodPath(StringBuilder sb, boolean truncatePath,
+            int parentCount) {
+        if (parentCount >= SESSION_RECURSION_LIMIT) {
+            Log.w(LOG_TAG, "getFullMethodPath: Hit recursion limit!");
+            sb.append(TRUNCATE_STRING);
+            return;
+        }
         // Return cached value for method path. When returning the truncated path, recalculate the
         // full path without using the cached value.
         if (!TextUtils.isEmpty(mFullMethodPathCache) && !truncatePath) {
@@ -296,7 +331,7 @@
             // Check to see if the session has been renamed yet. If it has not, then the session
             // has not been continued.
             isSessionStarted = !mShortMethodName.equals(parentSession.mShortMethodName);
-            parentSession.getFullMethodPath(sb, truncatePath);
+            parentSession.getFullMethodPath(sb, truncatePath, parentCount + 1);
             sb.append(SUBSESSION_SEPARATION_CHAR);
         }
         // Encapsulate the external session's method name so it is obvious what part of the session
@@ -319,13 +354,10 @@
             mFullMethodPathCache = sb.toString();
         }
     }
+
     // Recursively move to the top of the tree to see if the parent session is external.
     private boolean isSessionExternal() {
-        if (getParentSession() == null) {
-            return isExternal();
-        } else {
-            return getParentSession().isSessionExternal();
-        }
+        return getRootSession("isSessionExternal").isExternal();
     }
 
     @Override
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 2ffad03..f201cc1 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -17,6 +17,7 @@
 package android.telecom;
 
 import android.annotation.SystemApi;
+import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
@@ -24,7 +25,6 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
-import java.lang.String;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -314,7 +314,22 @@
      */
     public static final int CAPABILITY_RTT = 0x1000;
 
-    /* NEXT CAPABILITY: 0x2000 */
+    /**
+     * Flag indicating that this {@link PhoneAccount} is the preferred SIM subscription for
+     * emergency calls. A {@link PhoneAccount} that sets this capabilitiy must also
+     * set the {@link #CAPABILITY_SIM_SUBSCRIPTION} and {@link #CAPABILITY_PLACE_EMERGENCY_CALLS}
+     * capabilities. There should only be one emergency preferred {@link PhoneAccount}.
+     * <p>
+     * When set, Telecom will prefer this {@link PhoneAccount} over others for emergency calling,
+     * even if the emergency call was placed with a specific {@link PhoneAccount} set using the
+     * extra{@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE} in
+     * {@link Intent#ACTION_CALL_EMERGENCY} or {@link TelecomManager#placeCall(Uri, Bundle)}.
+     *
+     * @hide
+     */
+    public static final int CAPABILITY_EMERGENCY_PREFERRED = 0x2000;
+
+    /* NEXT CAPABILITY: 0x4000 */
 
     /**
      * URI scheme for telephone number URIs.
@@ -1020,6 +1035,9 @@
         if (hasCapabilities(CAPABILITY_PLACE_EMERGENCY_CALLS)) {
             sb.append("PlaceEmerg ");
         }
+        if (hasCapabilities(CAPABILITY_EMERGENCY_PREFERRED)) {
+            sb.append("EmerPrefer ");
+        }
         if (hasCapabilities(CAPABILITY_EMERGENCY_VIDEO_CALLING)) {
             sb.append("EmergVideo ");
         }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index a8d70a6..b40cca6 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -848,15 +849,18 @@
 
     /**
      * Returns the current SIM call manager. Apps must be prepared for this method to return
-     * {@code null}, indicating that there currently exists no user-chosen default
-     * {@code PhoneAccount}.
+     * {@code null}, indicating that there currently exists no SIM call manager {@link PhoneAccount}
+     * for the default voice subscription.
      *
-     * @return The phone account handle of the current sim call manager.
+     * @return The phone account handle of the current sim call manager for the default voice
+     * subscription.
+     * @see SubscriptionManager#getDefaultVoiceSubscriptionId()
      */
     public PhoneAccountHandle getSimCallManager() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getSimCallManager();
+                return getTelecomService().getSimCallManager(
+                        SubscriptionManager.getDefaultSubscriptionId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getSimCallManager");
@@ -865,9 +869,32 @@
     }
 
     /**
-     * Returns the current SIM call manager for the specified user. Apps must be prepared for this
-     * method to return {@code null}, indicating that there currently exists no user-chosen default
-     * {@code PhoneAccount}.
+     * Returns current SIM call manager for the Telephony Subscription ID specified. Apps must be
+     * prepared for this method to return {@code null}, indicating that there currently exists no
+     * SIM call manager {@link PhoneAccount} for the subscription specified.
+     *
+     * @param subscriptionId The Telephony Subscription ID that the SIM call manager should be
+     *                       queried for.
+     * @return The phone account handle of the current sim call manager.
+     * @see SubscriptionManager#getActiveSubscriptionInfoList()
+     * @hide
+     */
+    public PhoneAccountHandle getSimCallManagerForSubscription(int subscriptionId) {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getSimCallManager(subscriptionId);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getSimCallManager");
+        }
+        return null;
+    }
+
+    /**
+     * Returns the current SIM call manager for the user-chosen default Telephony Subscription ID
+     * (see {@link SubscriptionManager#getDefaultSubscriptionId()}) and the specified user. Apps
+     * must be prepared for this method to return {@code null}, indicating that there currently
+     * exists no SIM call manager {@link PhoneAccount} for the default voice subscription.
      *
      * @return The phone account handle of the current sim call manager.
      *
@@ -888,8 +915,8 @@
 
     /**
      * Returns the current connection manager. Apps must be prepared for this method to return
-     * {@code null}, indicating that there currently exists no user-chosen default
-     * {@code PhoneAccount}.
+     * {@code null}, indicating that there currently exists no Connection Manager
+     * {@link PhoneAccount} for the default voice subscription.
      *
      * @return The phone account handle of the current connection manager.
      * @hide
@@ -1415,6 +1442,9 @@
      * foreground call is ended.
      * <p>
      * Requires permission {@link android.Manifest.permission#ANSWER_PHONE_CALLS}.
+     * <p>
+     * Note: this method CANNOT be used to end ongoing emergency calls and will return {@code false}
+     * if an attempt is made to end an emergency call.
      *
      * @return {@code true} if there is a call which will be rejected or terminated, {@code false}
      * otherwise.
@@ -1773,6 +1803,13 @@
      * Self-managed {@link ConnectionService}s require permission
      * {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
      *
+     * <p class="note"><strong>Note:</strong> If this method is used to place an emergency call, it
+     * is not guaranteed that the call will be placed on the {@link PhoneAccount} provided in
+     * the {@link #EXTRA_PHONE_ACCOUNT_HANDLE} extra (if specified) and may be placed on another
+     * {@link PhoneAccount} with the {@link PhoneAccount#CAPABILITY_PLACE_EMERGENCY_CALLS}
+     * capability, depending on external factors, such as network conditions and Modem/SIM status.
+     * </p>
+     *
      * @param address The address to make the call to.
      * @param extras Bundle of extras to use with the call.
      */
diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
index d255ed1..3ee3285 100644
--- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
@@ -28,6 +28,8 @@
 oneway interface ICallScreeningAdapter {
     void allowCall(String callId);
 
+    void silenceCall(String callId);
+
     void disallowCall(
             String callId,
             boolean shouldReject,
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index a814c03..bdd4bb3 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -97,7 +97,7 @@
     /**
      * @see TelecomServiceImpl#getSimCallManager
      */
-    PhoneAccountHandle getSimCallManager();
+    PhoneAccountHandle getSimCallManager(int subId);
 
     /**
      * @see TelecomServiceImpl#getSimCallManagerForUser
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 83aa521..3857d27 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -629,7 +629,7 @@
         }
 
         /**
-         * Contains all sent text-based SMS messages in the SMS app.
+         * Contains all draft text-based SMS messages in the SMS app.
          */
         public static final class Draft implements BaseColumns, TextBasedSmsColumns {
 
@@ -745,7 +745,15 @@
         }
 
         /**
-         * Contains all sent text-based SMS messages in the SMS app.
+         * Contains a view of SMS conversations (also referred to as threads). This is similar to
+         * {@link Threads}, but only includes SMS messages and columns relevant to SMS
+         * conversations.
+         * <p>
+         * Note that this view ignores any information about MMS messages, it is a
+         * view of conversations as if MMS messages did not exist at all. This means that all
+         * relevant information, such as snippets and message count, will ignore any MMS messages
+         * that might be in the same thread through other views and present only data based on the
+         * SMS messages in that thread.
          */
         public static final class Conversations
                 implements BaseColumns, TextBasedSmsColumns {
@@ -2954,6 +2962,20 @@
              * <P>Type: INTEGER</P>
              */
             public static final String CHARSET = "charset";
+
+            /**
+             * Generates a Addr {@link Uri} for message, used to perform Addr table operation
+             * for mms.
+             *
+             * @param messageId the messageId used to generate Addr {@link Uri} dynamically
+             * @return the addrUri used to perform Addr table operation for mms
+             */
+            @NonNull
+            public static Uri getAddrUriForMessage(@NonNull String messageId) {
+                Uri addrUri = Mms.CONTENT_URI.buildUpon()
+                        .appendPath(String.valueOf(messageId)).appendPath("addr").build();
+                return addrUri;
+            }
         }
 
         /**
@@ -2972,10 +2994,16 @@
             }
 
             /**
+             * The name of part table.
+             */
+            private static final String TABLE_PART = "part";
+
+            /**
              * The {@code content://} style URL for this table. Can be appended with a part ID to
              * address individual parts.
              */
-            public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, "part");
+            @NonNull
+            public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, TABLE_PART);
 
             /**
              * The identifier of the message which this part belongs to.
@@ -3054,6 +3082,21 @@
              * <P>Type: TEXT</P>
              */
             public static final String TEXT = "text";
+
+            /**
+             * Generates a Part {@link Uri} for message, used to perform Part table operation
+             * for mms.
+             *
+             * @param messageId the messageId used to generate Part {@link Uri} dynamically
+             * @return the partUri used to perform Part table operation for mms
+             */
+            @NonNull
+            public static Uri getPartUriForMessage(@NonNull String messageId) {
+                Uri partUri = Mms.CONTENT_URI.buildUpon()
+                        .appendPath(String.valueOf(messageId)).appendPath(
+                                TABLE_PART).build();
+                return partUri;
+            }
         }
 
         /**
@@ -3158,6 +3201,8 @@
 
         /**
          * The {@code content://} style URL for locked messages in this table.
+         * <P>This {@link Uri} is used to check at most one locked message found in the union of MMS
+         * and SMS messages. Also this will return only _id column in response.</P>
          */
         public static final Uri CONTENT_LOCKED_URI = Uri.parse(
                 "content://mms-sms/locked");
@@ -3827,9 +3872,10 @@
     }
 
     /**
-     * Contains received SMS cell broadcast messages.
+     * Contains received SMS cell broadcast messages. More details are available in 3GPP TS 23.041.
      * @hide
      */
+    @SystemApi
     public static final class CellBroadcasts implements BaseColumns {
 
         /**
@@ -3841,30 +3887,52 @@
         /**
          * The {@code content://} URI for this table.
          */
+        @NonNull
         public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts");
 
         /**
-         * Message geographical scope.
+         * Message geographical scope. Valid values are:
+         * <ul>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_CELL_WIDE}. meaning the
+         * message is for the radio service cell</li>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE},
+         * meaning the message is for the radio service cell and immediately displayed</li>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_PLMN_WIDE}, meaning the
+         * message is for the PLMN (i.e. MCC/MNC)</li>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE},
+         * meaning the message is for the location area (in GSM) or service area (in UMTS)</li>
+         * </ul>
+         *
+         * <p>A message meant for a particular scope is automatically dismissed when the device
+         * exits that scope.</p>
          * <P>Type: INTEGER</P>
          */
         public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
 
         /**
          * Message serial number.
+         * <p>
+         * A 16-bit integer which identifies a particular CBS (cell
+         * broadcast short message service) message. The core network is responsible for
+         * allocating this value, and the value may be managed cyclically (3GPP TS 23.041 section
+         * 9.2.1) once the serial message has been incremented a sufficient number of times.
+         * </p>
          * <P>Type: INTEGER</P>
          */
         public static final String SERIAL_NUMBER = "serial_number";
 
         /**
-         * PLMN of broadcast sender. {@code SERIAL_NUMBER + PLMN + LAC + CID} uniquely identifies
-         * a broadcast for duplicate detection purposes.
+         * PLMN (i.e. MCC/MNC) of broadcast sender. {@code SERIAL_NUMBER + PLMN + LAC + CID}
+         * uniquely identifies a broadcast for duplicate detection purposes.
          * <P>Type: TEXT</P>
          */
         public static final String PLMN = "plmn";
 
         /**
-         * Location Area (GSM) or Service Area (UMTS) of broadcast sender. Unused for CDMA.
-         * Only included if Geographical Scope of message is not PLMN wide (01).
+         * Location area code (LAC).
+         * <p>Code representing location area (GSM) or service area (UMTS) of broadcast sender.
+         * Unused for CDMA. Only included if Geographical Scope of message is not PLMN wide (01).
+         * This value is sent by the network based on the cell tower.
          * <P>Type: INTEGER</P>
          */
         public static final String LAC = "lac";
@@ -3879,23 +3947,29 @@
         /**
          * Message code. <em>OBSOLETE: merged into SERIAL_NUMBER.</em>
          * <P>Type: INTEGER</P>
+         * @hide
          */
         public static final String V1_MESSAGE_CODE = "message_code";
 
         /**
          * Message identifier. <em>OBSOLETE: renamed to SERVICE_CATEGORY.</em>
          * <P>Type: INTEGER</P>
+         * @hide
          */
         public static final String V1_MESSAGE_IDENTIFIER = "message_id";
 
         /**
-         * Service category (GSM/UMTS: message identifier; CDMA: service category).
+         * Service category which represents the general topic of the message.
+         * <p>
+         * For GSM/UMTS: message identifier (see 3GPP TS 23.041 section 9.4.1.2.2)
+         * For CDMA: a 16-bit CDMA service category (see 3GPP2 C.R1001-D section 9.3)
+         * </p>
          * <P>Type: INTEGER</P>
          */
         public static final String SERVICE_CATEGORY = "service_category";
 
         /**
-         * Message language code.
+         * Message language code. (See 3GPP TS 23.041 section 9.4.1.2.3 for details).
          * <P>Type: TEXT</P>
          */
         public static final String LANGUAGE_CODE = "language";
@@ -3908,6 +3982,7 @@
 
         /**
          * Message delivery time.
+         * <p>This value is a system timestamp using {@link System#currentTimeMillis}</p>
          * <P>Type: INTEGER (long)</P>
          */
         public static final String DELIVERY_TIME = "date";
@@ -3919,25 +3994,36 @@
         public static final String MESSAGE_READ = "read";
 
         /**
-         * Message format (3GPP or 3GPP2).
+         * Message format ({@link android.telephony.SmsCbMessage#MESSAGE_FORMAT_3GPP} or
+         * {@link android.telephony.SmsCbMessage#MESSAGE_FORMAT_3GPP2}).
          * <P>Type: INTEGER</P>
          */
         public static final String MESSAGE_FORMAT = "format";
 
         /**
-         * Message priority (including emergency).
+         * Message priority.
+         * <p>This includes
+         * <ul>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_NORMAL}</li>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_INTERACTIVE}</li>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_URGENT}</li>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_EMERGENCY}</li>
+         * </p>
+         * </ul>
          * <P>Type: INTEGER</P>
          */
         public static final String MESSAGE_PRIORITY = "priority";
 
         /**
-         * ETWS warning type (ETWS alerts only).
+         * ETWS (Earthquake and Tsunami Warning System) warning type (ETWS alerts only).
+         * <p>See {@link android.telephony.SmsCbEtwsInfo}</p>
          * <P>Type: INTEGER</P>
          */
         public static final String ETWS_WARNING_TYPE = "etws_warning_type";
 
         /**
-         * CMAS message class (CMAS alerts only).
+         * CMAS (Commercial Mobile Alert System) message class (CMAS alerts only).
+         * <p>See {@link android.telephony.SmsCbCmasInfo}</p>
          * <P>Type: INTEGER</P>
          */
         public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
@@ -3976,8 +4062,60 @@
         public static final String DEFAULT_SORT_ORDER = DELIVERY_TIME + " DESC";
 
         /**
-         * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
+         * The timestamp in millisecond of when the device received the message.
+         * <P>Type: BIGINT</P>
+         * @hide
          */
+        public static final String RECEIVED_TIME = "received_time";
+
+        /**
+         * Indicates that whether the message has been broadcasted to the application.
+         * <P>Type: BOOLEAN</P>
+         * @hide
+         */
+        public static final String MESSAGE_BROADCASTED = "message_broadcasted";
+
+        /**
+         * The Warning Area Coordinates Elements. This element is used for geo-fencing purpose.
+         *
+         * The geometry and its coordinates are separated vertical bar, the first item is the
+         * geometry type and the remaining items are the parameter of this geometry.
+         *
+         * Only circle and polygon are supported. The coordinates are represented in Horizontal
+         * coordinates format.
+         *
+         * Coordinate encoding:
+         * "latitude, longitude"
+         * where -90.00000 <= latitude <= 90.00000 and -180.00000 <= longitude <= 180.00000
+         *
+         * Polygon encoding:
+         * "polygon|lat1,lng1|lat2,lng2|...|latn,lngn"
+         * lat1,lng1 ... latn,lngn are the vertices coordinate of the polygon.
+         *
+         * Circle encoding:
+         * "circle|lat,lng|radius".
+         * lat,lng is the center of the circle. The unit of circle's radius is meter.
+         *
+         * Example:
+         * "circle|0,0|100" mean a circle which center located at (0,0) and its radius is 100 meter.
+         * "polygon|0,1.5|0,1|1,1|1,0" mean a polygon has vertices (0,1.5),(0,1),(1,1),(1,0).
+         *
+         * There could be more than one geometry store in this field, they are separated by a
+         * semicolon.
+         *
+         * Example:
+         * "circle|0,0|100;polygon|0,0|0,1.5|1,1|1,0;circle|100.123,100|200.123"
+         *
+         * <P>Type: TEXT</P>
+         * @hide
+         */
+        public static final String GEOMETRIES = "geometries";
+
+        /**
+         * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
+         * @hide
+         */
+        @NonNull
         public static final String[] QUERY_COLUMNS = {
                 _ID,
                 GEOGRAPHICAL_SCOPE,
@@ -4000,6 +4138,34 @@
                 CMAS_URGENCY,
                 CMAS_CERTAINTY
         };
+
+        /**
+         * Query columns for instantiating {@link android.telephony.SmsCbMessage} objects.
+         * @hide
+         */
+        public static final String[] QUERY_COLUMNS_FWK = {
+                _ID,
+                GEOGRAPHICAL_SCOPE,
+                PLMN,
+                LAC,
+                CID,
+                SERIAL_NUMBER,
+                SERVICE_CATEGORY,
+                LANGUAGE_CODE,
+                MESSAGE_BODY,
+                MESSAGE_FORMAT,
+                MESSAGE_PRIORITY,
+                ETWS_WARNING_TYPE,
+                CMAS_MESSAGE_CLASS,
+                CMAS_CATEGORY,
+                CMAS_RESPONSE_TYPE,
+                CMAS_SEVERITY,
+                CMAS_URGENCY,
+                CMAS_CERTAINTY,
+                RECEIVED_TIME,
+                MESSAGE_BROADCASTED,
+                GEOMETRIES
+        };
     }
 
     /**
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index 2ff2d91..a52ad23 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -44,6 +45,7 @@
         this.mCallQuality = callQuality;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType
@@ -109,7 +111,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o == null || !(o instanceof CallAttributes) || hashCode() != o.hashCode()) {
             return false;
         }
diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java
index cbe62284..5ae3df3 100644
--- a/telephony/java/android/telephony/CallQuality.java
+++ b/telephony/java/android/telephony/CallQuality.java
@@ -17,6 +17,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -252,6 +254,7 @@
     }
 
     // Parcelable things
+    @NonNull
     @Override
     public String toString() {
         return "CallQuality: {downlinkCallQualityLevel=" + mDownlinkCallQualityLevel
@@ -285,7 +288,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o == null || !(o instanceof CallQuality) || hashCode() != o.hashCode()) {
             return false;
         }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0ebbbc6..4f6abfe 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -672,6 +672,22 @@
     public static final String KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING
             = "carrier_data_service_wlan_package_override_string";
 
+    /**
+     * Override the device's configuration for the cellular data service class to use
+     * for this SIM card.
+     * @hide
+     */
+    public static final String KEY_CARRIER_DATA_SERVICE_WWAN_CLASS_OVERRIDE_STRING =
+            "carrier_data_service_wwan_class_override_string";
+
+    /**
+     * Override the device's configuration for the IWLAN data service class to use
+     * for this SIM card.
+     * @hide
+     */
+    public static final String KEY_CARRIER_DATA_SERVICE_WLAN_CLASS_OVERRIDE_STRING =
+            "carrier_data_service_wlan_class_override_string";
+
     /** Flag specifying whether VoLTE TTY is supported. */
     public static final String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL
             = "carrier_volte_tty_supported_bool";
@@ -836,6 +852,19 @@
             "carrier_metered_roaming_apn_types_strings";
 
     /**
+     * APN types that are not allowed on cellular
+     * @hide
+     */
+    public static final String KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
+            "carrier_wwan_disallowed_apn_types_string_array";
+
+    /**
+     * APN types that are not allowed on IWLAN
+     * @hide
+     */
+    public static final String KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
+            "carrier_wlan_disallowed_apn_types_string_array";
+    /**
      * CDMA carrier ERI (Enhanced Roaming Indicator) file name
      * @hide
      */
@@ -1021,6 +1050,15 @@
             "support_manage_ims_conference_call_bool";
 
     /**
+     * Determines whether the IMS conference merge process supports and returns its participants
+     * data. When {@code true}, on merge complete, conference call would have a list of its
+     * participants returned in XML format, {@code false otherwise}.
+     * @hide
+     */
+    public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL =
+            "support_ims_conference_event_package_bool";
+
+    /**
      * Determines whether High Definition audio property is displayed in the dialer UI.
      * If {@code false}, remove the HD audio property from the connection so that HD audio related
      * UI is not displayed. If {@code true}, keep HD audio property as it is configured.
@@ -2352,6 +2390,14 @@
     public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL =
             "check_pricing_with_carrier_data_roaming_bool";
 
+     /**
+      * Determines whether we should show a notification when the phone established a data
+      * connection in roaming network, to warn users about possible roaming charges.
+      * @hide
+      */
+    public static final String KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL =
+            "show_data_connected_roaming_notification";
+
     /**
      * A list of 4 LTE RSRP thresholds above which a signal level is considered POOR,
      * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
@@ -2375,6 +2421,14 @@
             "carrier_network_service_wlan_package_override_string";
 
     /**
+     * Decides when clients try to bind to iwlan network service, which class name will
+     * the binding intent go to.
+     * @hide
+     */
+    public static final String KEY_CARRIER_NETWORK_SERVICE_WLAN_CLASS_OVERRIDE_STRING =
+            "carrier_network_service_wlan_class_override_string";
+
+    /**
      * Decides when clients try to bind to wwan (cellular) network service, which package name will
      * the binding intent go to.
      * @hide
@@ -2383,12 +2437,28 @@
             "carrier_network_service_wwan_package_override_string";
 
     /**
+     * Decides when clients try to bind to wwan (cellular) network service, which class name will
+     * the binding intent go to.
+     * @hide
+     */
+    public static final String KEY_CARRIER_NETWORK_SERVICE_WWAN_CLASS_OVERRIDE_STRING =
+            "carrier_network_service_wwan_class_override_string";
+
+    /**
      * The package name of qualified networks service that telephony binds to.
      *
      * @hide
      */
     public static final String KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING =
             "carrier_qualified_networks_service_package_override_string";
+
+    /**
+     * The class name of qualified networks service that telephony binds to.
+     *
+     * @hide
+     */
+    public static final String KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_CLASS_OVERRIDE_STRING =
+            "carrier_qualified_networks_service_class_override_string";
     /**
      * A list of 4 LTE RSCP thresholds above which a signal level is considered POOR,
      * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
@@ -2600,6 +2670,68 @@
     public static final String KEY_AUTO_CANCEL_CS_REJECT_NOTIFICATION =
             "carrier_auto_cancel_cs_notification";
 
+    /**
+     * GPS configs. See android.hardware.gnss@1.0 IGnssConfiguration.
+     * @hide
+     */
+    public static final class Gps {
+        /** Prefix of all Gps.KEY_* constants. */
+        public static final String KEY_PREFIX = "gps.";
+
+        /**
+         * Location information during (and after) an emergency call is only provided over control
+         * plane signaling from the network.
+         * @hide
+         */
+        public static final int SUPL_EMERGENCY_MODE_TYPE_CP_ONLY = 0;
+
+        /**
+         * Location information during (and after) an emergency call is provided over the data
+         * plane and serviced by the framework GNSS service, but if it fails, the carrier also
+         * supports control plane backup signaling.
+         * @hide
+         */
+        public static final int SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK = 1;
+
+        /**
+         * Location information during (and after) an emergency call is provided over the data plane
+         * and serviced by the framework GNSS service only. There is no backup signalling over the
+         * control plane if it fails.
+         * @hide
+         */
+        public static final int SUPL_EMERGENCY_MODE_TYPE_DP_ONLY = 2;
+
+        /**
+         * Control Plane / SUPL NI emergency extension time in seconds. Default to "0".
+         */
+        public static final String KEY_ES_EXTENSION_SEC_STRING = KEY_PREFIX + "es_extension_sec";
+
+        /**
+         * Determines whether or not SUPL ES mode supports a control-plane mechanism to get a user's
+         * location in the event that data plane SUPL fails or is otherwise unavailable.
+         * <p>
+         * An integer value determines the support type of this carrier. If this carrier only
+         * supports data plane SUPL ES, then the value will be
+         * {@link #SUPL_EMERGENCY_MODE_TYPE_DP_ONLY}. If the carrier supports control plane fallback
+         * for emergency SUPL, the value will be {@link #SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK}.
+         * If the carrier does not support data plane SUPL using the framework, the value will be
+         * {@link #SUPL_EMERGENCY_MODE_TYPE_CP_ONLY}.
+         * <p>
+         * The default value for this configuration is {@link #SUPL_EMERGENCY_MODE_TYPE_CP_ONLY}.
+         * @hide
+         */
+        public static final String KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT = KEY_PREFIX
+                + "es_supl_control_plane_support_int";
+
+        private static PersistableBundle getDefaults() {
+            PersistableBundle defaults = new PersistableBundle();
+            defaults.putString(KEY_ES_EXTENSION_SEC_STRING, "0");
+            defaults.putInt(KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
+                    SUPL_EMERGENCY_MODE_TYPE_CP_ONLY);
+            return defaults;
+        }
+    }
+
    /**
     * An int array containing CDMA enhanced roaming indicator values for Home (non-roaming) network.
     * The default values come from 3GPP2 C.R1001 table 8.1-1.
@@ -2693,6 +2825,23 @@
             "is_opportunistic_subscription_bool";
 
     /**
+     * Configs used by the IMS stack.
+     */
+    public static final class Ims {
+        /** Prefix of all Ims.KEY_* constants. */
+        public static final String KEY_PREFIX = "ims.";
+
+        //TODO: Add configs related to IMS.
+
+        private Ims() {}
+
+        private static PersistableBundle getDefaults() {
+            PersistableBundle defaults = new PersistableBundle();
+            return defaults;
+        }
+    }
+
+    /**
      * A list of 4 GSM RSSI thresholds above which a signal level is considered POOR,
      * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
      *
@@ -2706,6 +2855,15 @@
     public static final String KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY =
             "gsm_rssi_thresholds_int_array";
 
+    /**
+     * Determines whether Wireless Priority Service call is supported over IMS.
+     *
+     * See Wireless Priority Service from https://www.fcc.gov/general/wireless-priority-service-wps
+     * @hide
+     */
+    public static final String KEY_SUPPORT_WPS_OVER_IMS_BOOL =
+            "support_wps_over_ims_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -2840,6 +2998,10 @@
                 new String[]{"default", "mms", "dun", "supl"});
         sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
+        sDefaults.putStringArray(KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+                new String[]{""});
+        sDefaults.putStringArray(KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+                new String[]{""});
         sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
                 new int[]{
                     4, /* IS95A */
@@ -2864,6 +3026,7 @@
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
+        sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
         sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false);
         sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
@@ -3045,6 +3208,7 @@
         sDefaults.putString(KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING, "");
         sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
         sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
+        sDefaults.putBoolean(KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL, false);
         sDefaults.putIntArray(KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
                 new int[] {
                         -128, /* SIGNAL_STRENGTH_POOR */
@@ -3083,6 +3247,7 @@
         sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_ENTRY_OR_EXIT_HYSTERESIS_TIME_LONG, 10000);
         /* Default value is 10 seconds. */
         sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG, 10000);
+        sDefaults.putAll(Gps.getDefaults());
         sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY,
                 new int[] {
                         1 /* Roaming Indicator Off */
@@ -3104,6 +3269,8 @@
                         -97, /* SIGNAL_STRENGTH_GOOD */
                         -89,  /* SIGNAL_STRENGTH_GREAT */
                 });
+        sDefaults.putBoolean(KEY_SUPPORT_WPS_OVER_IMS_BOOL, true);
+        sDefaults.putAll(Ims.getDefaults());
     }
 
     /**
@@ -3300,4 +3467,75 @@
         return ICarrierConfigLoader.Stub
                 .asInterface(ServiceManager.getService(Context.CARRIER_CONFIG_SERVICE));
     }
+
+    /**
+     * Gets the configuration values for a component using its prefix.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @param prefix prefix of the component.
+     * @param subId the subscription ID, normally obtained from {@link SubscriptionManager}.
+     *
+     * @see #getConfigForSubId
+     */
+    @Nullable
+    public PersistableBundle getConfigByComponentForSubId(@NonNull String prefix, int subId) {
+        PersistableBundle configs = getConfigForSubId(subId);
+
+        if (configs == null) {
+            return null;
+        }
+
+        PersistableBundle ret = new PersistableBundle();
+        for (String configKey : configs.keySet()) {
+            if (configKey.startsWith(prefix)) {
+                addConfig(configKey, configs.get(configKey), ret);
+            }
+        }
+
+        return ret;
+    }
+
+    private void addConfig(String key, Object value, PersistableBundle configs) {
+        if (value instanceof String) {
+            configs.putString(key, (String) value);
+        }
+
+        if (value instanceof String[]) {
+            configs.putStringArray(key, (String[]) value);
+        }
+
+        if (value instanceof Integer) {
+            configs.putInt(key, (Integer) value);
+        }
+
+        if (value instanceof Long) {
+            configs.putLong(key, (Long) value);
+        }
+
+        if (value instanceof Double) {
+            configs.putDouble(key, (Double) value);
+        }
+
+        if (value instanceof Boolean) {
+            configs.putBoolean(key, (Boolean) value);
+        }
+
+        if (value instanceof int[]) {
+            configs.putIntArray(key, (int[]) value);
+        }
+
+        if (value instanceof double[]) {
+            configs.putDoubleArray(key, (double[]) value);
+        }
+
+        if (value instanceof boolean[]) {
+            configs.putBooleanArray(key, (boolean[]) value);
+        }
+
+        if (value instanceof long[]) {
+            configs.putLongArray(key, (long[]) value);
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index cb15d7b..4881261 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -323,6 +323,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index 127eabd..31b3a05 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -143,7 +143,7 @@
     }
 
     /**
-     * Get the signal strength as dBm
+     * Get the signal strength as dBm.
      */
     @Override
     public int getDbm() {
@@ -163,18 +163,17 @@
     }
 
     /**
-     * Return the Received Signal Strength Indicator
+     * Return the Received Signal Strength Indicator.
      *
      * @return the RSSI in dBm (-113, -51) or
      *         {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}.
-     * @hide
      */
     public int getRssi() {
         return mRssi;
     }
 
     /**
-     * Return the Bit Error Rate
+     * Return the Bit Error Rate.
      *
      * @return the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
      *         {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}.
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 3dd9318..407ced7 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -134,6 +135,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return new StringBuilder().append(this.getClass().getName())
@@ -155,7 +157,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
 
         if (!(o instanceof DataSpecificRegistrationInfo)) return false;
diff --git a/telephony/java/android/telephony/LteVopsSupportInfo.java b/telephony/java/android/telephony/LteVopsSupportInfo.java
index fda20bd..8068231 100644
--- a/telephony/java/android/telephony/LteVopsSupportInfo.java
+++ b/telephony/java/android/telephony/LteVopsSupportInfo.java
@@ -17,6 +17,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -94,7 +96,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o == null || !(o instanceof LteVopsSupportInfo)) {
             return false;
         }
@@ -112,6 +114,7 @@
     /**
      * @return string representation.
      */
+    @NonNull
     @Override
     public String toString() {
         return ("LteVopsSupportInfo : "
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 2fae949..a76b8da 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -501,6 +501,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         return new StringBuilder("NetworkRegistrationInfo{")
@@ -531,7 +532,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
 
         if (!(o instanceof NetworkRegistrationInfo)) {
diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java
index 1c64bcd..89b9665 100644
--- a/telephony/java/android/telephony/NetworkServiceCallback.java
+++ b/telephony/java/android/telephony/NetworkServiceCallback.java
@@ -24,7 +24,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
 
 /**
  * Network service callback. Object of this class is passed to NetworkServiceProvider upon
@@ -61,11 +60,11 @@
     /** Request failed */
     public static final int RESULT_ERROR_FAILED         = 5;
 
-    private final WeakReference<INetworkServiceCallback> mCallback;
+    private final INetworkServiceCallback mCallback;
 
     /** @hide */
     public NetworkServiceCallback(INetworkServiceCallback callback) {
-        mCallback = new WeakReference<>(callback);
+        mCallback = callback;
     }
 
     /**
@@ -78,15 +77,14 @@
      */
     public void onRequestNetworkRegistrationInfoComplete(int result,
                                                          @Nullable NetworkRegistrationInfo state) {
-        INetworkServiceCallback callback = mCallback.get();
-        if (callback != null) {
+        if (mCallback != null) {
             try {
-                callback.onRequestNetworkRegistrationInfoComplete(result, state);
+                mCallback.onRequestNetworkRegistrationInfoComplete(result, state);
             } catch (RemoteException e) {
                 Rlog.e(mTag, "Failed to onRequestNetworkRegistrationInfoComplete on the remote");
             }
         } else {
-            Rlog.e(mTag, "Weak reference of callback is null.");
+            Rlog.e(mTag, "onRequestNetworkRegistrationInfoComplete callback is null.");
         }
     }
 }
\ No newline at end of file
diff --git a/telephony/java/android/telephony/PhoneNumberRange.java b/telephony/java/android/telephony/PhoneNumberRange.java
index 12df9b5..0cb3544 100644
--- a/telephony/java/android/telephony/PhoneNumberRange.java
+++ b/telephony/java/android/telephony/PhoneNumberRange.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -104,7 +105,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         PhoneNumberRange that = (PhoneNumberRange) o;
@@ -119,6 +120,7 @@
         return Objects.hash(mCountryCode, mPrefix, mLowerBound, mUpperBound);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "PhoneNumberRange{"
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index b75e515..af3ba5e 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -22,9 +22,11 @@
 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
 
 import android.annotation.IntDef;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.location.CountryDetector;
 import android.net.Uri;
@@ -164,6 +166,33 @@
         return c == 'w'||c == 'W';
     }
 
+    private static int sMinMatch = 0;
+
+    private static int getMinMatch() {
+        if (sMinMatch == 0) {
+            sMinMatch = Resources.getSystem().getInteger(
+                    com.android.internal.R.integer.config_phonenumber_compare_min_match);
+        }
+        return sMinMatch;
+    }
+
+    /**
+     * A Test API to get current sMinMatch.
+     * @hide
+     */
+    @TestApi
+    public static int getMinMatchForTest() {
+        return getMinMatch();
+    }
+
+    /**
+     * A Test API to set sMinMatch.
+     * @hide
+     */
+    @TestApi
+    public static void setMinMatchForTest(int minMatch) {
+        sMinMatch = minMatch;
+    }
 
     /** Returns true if ch is not dialable or alpha char */
     private static boolean isSeparator(char ch) {
@@ -188,6 +217,9 @@
         }
 
         String scheme = uri.getScheme();
+        if (scheme == null) {
+            return null;
+        }
 
         if (scheme.equals("tel") || scheme.equals("sip")) {
             return uri.getSchemeSpecificPart();
@@ -475,7 +507,7 @@
      * enough for caller ID purposes.
      *
      * - Compares from right to left
-     * - requires MIN_MATCH (7) characters to match
+     * - requires minimum characters to match
      * - handles common trunk prefixes and international prefixes
      *   (basically, everything except the Russian trunk prefix)
      *
@@ -491,6 +523,7 @@
         int matched;
         int numNonDialableCharsInA = 0;
         int numNonDialableCharsInB = 0;
+        int minMatch = getMinMatch();
 
         if (a == null || b == null) return a == b;
 
@@ -530,12 +563,12 @@
             }
         }
 
-        if (matched < MIN_MATCH) {
+        if (matched < minMatch) {
             int effectiveALen = a.length() - numNonDialableCharsInA;
             int effectiveBLen = b.length() - numNonDialableCharsInB;
 
 
-            // if the number of dialable chars in a and b match, but the matched chars < MIN_MATCH,
+            // if the number of dialable chars in a and b match, but the matched chars < minMatch,
             // treat them as equal (i.e. 404-04 and 40404)
             if (effectiveALen == effectiveBLen && effectiveALen == matched) {
                 return true;
@@ -545,7 +578,7 @@
         }
 
         // At least one string has matched completely;
-        if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) {
+        if (matched >= minMatch && (ia < 0 || ib < 0)) {
             return true;
         }
 
@@ -736,7 +769,7 @@
     }
 
     /**
-     * Returns the rightmost MIN_MATCH (5) characters in the network portion
+     * Returns the rightmost minimum matched characters in the network portion
      * in *reversed* order
      *
      * This can be used to do a database lookup against the column
@@ -747,7 +780,7 @@
     public static String
     toCallerIDMinMatch(String phoneNumber) {
         String np = extractNetworkPortionAlt(phoneNumber);
-        return internalGetStrippedReversed(np, MIN_MATCH);
+        return internalGetStrippedReversed(np, getMinMatch());
     }
 
     /**
@@ -1709,26 +1742,6 @@
         return normalizedDigits.toString();
     }
 
-    // Three and four digit phone numbers for either special services,
-    // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should
-    // not match.
-    //
-    // This constant used to be 5, but SMS short codes has increased in length and
-    // can be easily 6 digits now days. Most countries have SMS short code length between
-    // 3 to 6 digits. The exceptions are
-    //
-    // Australia: Short codes are six or eight digits in length, starting with the prefix "19"
-    //            followed by an additional four or six digits and two.
-    // Czechia: Codes are seven digits in length for MO and five (not billed) or
-    //            eight (billed) for MT direction
-    //
-    // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference
-    //
-    // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match
-    // to 7.
-    @UnsupportedAppUsage
-    static final int MIN_MATCH = 7;
-
     /**
      * Checks a given number against the list of
      * emergency numbers provided by the RIL and SIM card.
diff --git a/telephony/java/android/telephony/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java
index 19e1931..0c98c4c 100644
--- a/telephony/java/android/telephony/PreciseCallState.java
+++ b/telephony/java/android/telephony/PreciseCallState.java
@@ -17,6 +17,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
@@ -276,7 +278,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -294,6 +296,7 @@
                 && mPreciseDisconnectCause == other.mPreciseDisconnectCause);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuffer sb = new StringBuffer();
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index d593678..d40b6a2 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -177,7 +177,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
 
         if (!(obj instanceof PreciseDataConnectionState)) {
             return false;
@@ -191,6 +191,7 @@
                 && mState == other.mState;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index daad41f..5dda75b 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import static android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -341,6 +343,7 @@
 
     private String mOperatorAlphaLongRaw;
     private String mOperatorAlphaShortRaw;
+    private boolean mIsIwlanPreferred;
 
     /**
      * get String description of roaming type
@@ -368,15 +371,16 @@
     /**
      * Create a new ServiceState from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
+     * This method is used by PhoneStateIntentReceiver, CellBroadcastReceiver, and maybe by
      * external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created ServiceState
      * @hide
      */
+    @NonNull
     @UnsupportedAppUsage
-    public static ServiceState newFromBundle(Bundle m) {
+    public static ServiceState newFromBundle(@NonNull Bundle m) {
         ServiceState ret;
         ret = new ServiceState();
         ret.setFromNotifierBundle(m);
@@ -427,6 +431,7 @@
         mNrFrequencyRange = s.mNrFrequencyRange;
         mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
         mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw;
+        mIsIwlanPreferred = s.mIsIwlanPreferred;
     }
 
     /**
@@ -463,6 +468,7 @@
         mNrFrequencyRange = in.readInt();
         mOperatorAlphaLongRaw = in.readString();
         mOperatorAlphaShortRaw = in.readString();
+        mIsIwlanPreferred = in.readBoolean();
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -492,6 +498,7 @@
         out.writeInt(mNrFrequencyRange);
         out.writeString(mOperatorAlphaLongRaw);
         out.writeString(mOperatorAlphaShortRaw);
+        out.writeBoolean(mIsIwlanPreferred);
     }
 
     public int describeContents() {
@@ -853,7 +860,8 @@
                     mNetworkRegistrationInfos,
                     mNrFrequencyRange,
                     mOperatorAlphaLongRaw,
-                    mOperatorAlphaShortRaw);
+                    mOperatorAlphaShortRaw,
+                    mIsIwlanPreferred);
         }
     }
 
@@ -885,7 +893,8 @@
                     && equalsHandlesNulls(mOperatorAlphaShortRaw, s.mOperatorAlphaShortRaw)
                     && mNetworkRegistrationInfos.size() == s.mNetworkRegistrationInfos.size()
                     && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos)
-                    && mNrFrequencyRange == s.mNrFrequencyRange;
+                    && mNrFrequencyRange == s.mNrFrequencyRange
+                    && mIsIwlanPreferred == s.mIsIwlanPreferred;
         }
     }
 
@@ -1043,6 +1052,7 @@
                     .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
                     .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
                     .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
+                    .append(", mIsIwlanPreferred=").append(mIsIwlanPreferred)
                     .append("}").toString();
         }
     }
@@ -1085,6 +1095,7 @@
         }
         mOperatorAlphaLongRaw = null;
         mOperatorAlphaShortRaw = null;
+        mIsIwlanPreferred = false;
     }
 
     public void setStateOutOfService() {
@@ -1459,20 +1470,9 @@
     /** @hide */
     @UnsupportedAppUsage
     public int getRilDataRadioTechnology() {
-        NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        NetworkRegistrationInfo wlanRegInfo = getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-        if (wlanRegInfo != null
-                && wlanRegInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_IWLAN
-                && wlanRegInfo.getRegistrationState()
-                == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
-            return RIL_RADIO_TECHNOLOGY_IWLAN;
-        } else if (wwanRegInfo != null) {
-            return networkTypeToRilRadioTechnology(wwanRegInfo.getAccessNetworkTechnology());
-        }
-        return RIL_RADIO_TECHNOLOGY_UNKNOWN;
+        return networkTypeToRilRadioTechnology(getDataNetworkType());
     }
+
     /**
      * @hide
      * @Deprecated to be removed Q3 2013 use {@link #getRilDataRadioTechnology} or
@@ -1609,25 +1609,45 @@
     }
 
     /** @hide */
+    public static int networkTypeToAccessNetworkType(@TelephonyManager.NetworkType
+            int networkType) {
+        return rilRadioTechnologyToAccessNetworkType(networkTypeToRilRadioTechnology(networkType));
+    }
+
+    /**
+     * Get current data network type.
+     *
+     * Note that for IWLAN AP-assisted mode device, which is reporting both camped access networks
+     * (cellular RAT and IWLAN)at the same time, this API is simulating the old legacy mode device
+     * behavior,
+     *
+     * @return Current data network type
+     * @hide
+     */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public @TelephonyManager.NetworkType int getDataNetworkType() {
-        final NetworkRegistrationInfo iwlanRegState = getNetworkRegistrationInfo(
+        final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-        if (iwlanRegState != null && iwlanRegState.getRegistrationState()
-                == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
-            // If the device is on IWLAN, return IWLAN as the network type. This is to simulate the
-            // behavior of legacy mode device. In the future caller should use
-            // requestNetworkRegistrationInfo() to retrieve the actual data network type on cellular
-            // or on IWLAN.
-            return iwlanRegState.getAccessNetworkTechnology();
+        final NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        // For legacy mode device, or AP-assisted mode device but IWLAN is out of service, use
+        // the RAT from cellular.
+        if (iwlanRegInfo == null || !iwlanRegInfo.isInService()) {
+            return (wwanRegInfo != null) ? wwanRegInfo.getAccessNetworkTechnology()
+                    : TelephonyManager.NETWORK_TYPE_UNKNOWN;
         }
 
-        final NetworkRegistrationInfo regState = getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        if (regState != null) {
-            return regState.getAccessNetworkTechnology();
+        // At this point, it must be an AP-assisted mode device and IWLAN is in service. We should
+        // use the RAT from IWLAN service is cellular is out of service, or when both are in service
+        // and any APN type of data is preferred on IWLAN.
+        if (!wwanRegInfo.isInService() || mIsIwlanPreferred) {
+            return iwlanRegInfo.getAccessNetworkTechnology();
         }
-        return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+
+        // If both cellular and IWLAN are in service, but no APN is preferred on IWLAN, still use
+        // the RAT from cellular.
+        return wwanRegInfo.getAccessNetworkTechnology();
     }
 
     /** @hide */
@@ -1719,6 +1739,36 @@
         return false;
     }
 
+    /**
+     *
+     * Returns whether the bearerBitmask includes a networkType that matches the accessNetworkType.
+     *
+     * The NetworkType refers to NetworkType in TelephonyManager. For example
+     * {@link TelephonyManager#NETWORK_TYPE_GPRS}.
+     *
+     * The accessNetworkType refers to {@link AccessNetworkType}.
+     *
+     * @hide
+     * */
+    public static boolean networkBitmaskHasAccessNetworkType(
+            @TelephonyManager.NetworkTypeBitMask int networkBitmask, int accessNetworkType) {
+        if (networkBitmask == NETWORK_TYPE_BITMASK_UNKNOWN) return true;
+        if (accessNetworkType == AccessNetworkType.UNKNOWN) return false;
+
+        int networkType = 1;
+        while (networkBitmask != 0) {
+            if ((networkBitmask & 1) != 0) {
+                if (networkTypeToAccessNetworkType(networkType) == accessNetworkType) {
+                    return true;
+                }
+            }
+            networkBitmask = networkBitmask >> 1;
+            networkType++;
+        }
+
+        return false;
+    }
+
     /** @hide */
     public static int getBitmaskForTech(int radioTech) {
         if (radioTech >= 1) {
@@ -1976,4 +2026,28 @@
     public String getOperatorAlphaShortRaw() {
         return mOperatorAlphaShortRaw;
     }
+
+    /**
+     * Set to {@code true} if any data network is preferred on IWLAN.
+     *
+     * @param isIwlanPreferred {@code true} if IWLAN is preferred.
+     * @hide
+     */
+    public void setIwlanPreferred(boolean isIwlanPreferred) {
+        mIsIwlanPreferred = isIwlanPreferred;
+    }
+
+    /**
+     * @return {@code true} if any data network is preferred on IWLAN.
+     *
+     * Note only when this value is true, {@link #getDataNetworkType()} will return
+     * {@link TelephonyManager#NETWORK_TYPE_IWLAN} when AP-assisted mode device camps on both
+     * cellular and IWLAN. This value does not affect legacy mode devices as the data network
+     * type is directly reported by the modem.
+     *
+     * @hide
+     */
+    public boolean isIwlanPreferred() {
+        return mIsIwlanPreferred;
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java b/telephony/java/android/telephony/SmsCbCmasInfo.java
similarity index 72%
rename from telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
rename to telephony/java/android/telephony/SmsCbCmasInfo.java
index c912924..2c10a09 100644
--- a/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
+++ b/telephony/java/android/telephony/SmsCbCmasInfo.java
@@ -16,17 +16,25 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
- * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}.
+ * Contains CMAS (Commercial Mobile Alert System) warning notification Type 1 elements for a
+ * {@link SmsCbMessage}.
  * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and
  * 3GPP TS 23.041 (for GSM/UMTS).
  *
  * {@hide}
  */
-public class SmsCbCmasInfo implements Parcelable {
+@SystemApi
+public final class SmsCbCmasInfo implements Parcelable {
 
     // CMAS message class (in GSM/UMTS message identifier or CDMA service category).
 
@@ -54,6 +62,21 @@
     /** CMAS category for warning types that are reserved for future extension. */
     public static final int CMAS_CLASS_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_CLASS_"},
+            value = {
+                    CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT,
+                    CMAS_CLASS_EXTREME_THREAT,
+                    CMAS_CLASS_SEVERE_THREAT,
+                    CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY,
+                    CMAS_CLASS_REQUIRED_MONTHLY_TEST,
+                    CMAS_CLASS_CMAS_EXERCISE,
+                    CMAS_CLASS_OPERATOR_DEFINED_USE,
+                    CMAS_CLASS_UNKNOWN,
+            })
+    public @interface Class {}
+
     // CMAS alert category (in CDMA type 1 elements record).
 
     /** CMAS alert category: Geophysical including landslide. */
@@ -98,6 +121,26 @@
      */
     public static final int CMAS_CATEGORY_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_CATEORY_"},
+            value = {
+                    CMAS_CATEGORY_GEO,
+                    CMAS_CATEGORY_MET,
+                    CMAS_CATEGORY_SAFETY,
+                    CMAS_CATEGORY_SECURITY,
+                    CMAS_CATEGORY_RESCUE,
+                    CMAS_CATEGORY_FIRE,
+                    CMAS_CATEGORY_HEALTH,
+                    CMAS_CATEGORY_ENV,
+                    CMAS_CATEGORY_TRANSPORT,
+                    CMAS_CATEGORY_INFRA,
+                    CMAS_CATEGORY_CBRNE,
+                    CMAS_CATEGORY_OTHER,
+                    CMAS_CATEGORY_UNKNOWN,
+            })
+    public @interface Category {}
+
     // CMAS response type (in CDMA type 1 elements record).
 
     /** CMAS response type: Take shelter in place. */
@@ -130,6 +173,22 @@
      */
     public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_RESPONSE_TYPE_"},
+            value = {
+                    CMAS_RESPONSE_TYPE_SHELTER,
+                    CMAS_RESPONSE_TYPE_EVACUATE,
+                    CMAS_RESPONSE_TYPE_PREPARE,
+                    CMAS_RESPONSE_TYPE_EXECUTE,
+                    CMAS_RESPONSE_TYPE_MONITOR,
+                    CMAS_RESPONSE_TYPE_AVOID,
+                    CMAS_RESPONSE_TYPE_ASSESS,
+                    CMAS_RESPONSE_TYPE_NONE,
+                    CMAS_RESPONSE_TYPE_UNKNOWN,
+    })
+    public @interface ResponseType {}
+
     // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record).
 
     /** CMAS severity type: Extraordinary threat to life or property. */
@@ -145,6 +204,16 @@
      */
     public static final int CMAS_SEVERITY_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_SEVERITY_"},
+            value = {
+                    CMAS_SEVERITY_EXTREME,
+                    CMAS_SEVERITY_SEVERE,
+                    CMAS_SEVERITY_UNKNOWN,
+            })
+    public @interface Severity {}
+
     // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record).
 
     /** CMAS urgency type: Responsive action should be taken immediately. */
@@ -160,6 +229,16 @@
      */
     public static final int CMAS_URGENCY_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_URGENCY_"},
+            value = {
+                    CMAS_URGENCY_IMMEDIATE,
+                    CMAS_URGENCY_EXPECTED,
+                    CMAS_URGENCY_UNKNOWN,
+            })
+    public @interface Urgency {}
+
     // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record).
 
     /** CMAS certainty type: Determined to have occurred or to be ongoing. */
@@ -175,27 +254,38 @@
      */
     public static final int CMAS_CERTAINTY_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_CERTAINTY_"},
+            value = {
+                    CMAS_CERTAINTY_OBSERVED,
+                    CMAS_CERTAINTY_LIKELY,
+                    CMAS_CERTAINTY_UNKNOWN,
+            })
+    public @interface Certainty {}
+
     /** CMAS message class. */
-    private final int mMessageClass;
+    private final @Class int mMessageClass;
 
     /** CMAS category. */
-    private final int mCategory;
+    private final @Category int mCategory;
 
     /** CMAS response type. */
-    private final int mResponseType;
+    private final @ResponseType int mResponseType;
 
     /** CMAS severity. */
-    private final int mSeverity;
+    private final @Severity int mSeverity;
 
     /** CMAS urgency. */
-    private final int mUrgency;
+    private final @Urgency int mUrgency;
 
     /** CMAS certainty. */
-    private final int mCertainty;
+    private final @Certainty int mCertainty;
 
     /** Create a new SmsCbCmasInfo object with the specified values. */
-    public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity,
-            int urgency, int certainty) {
+    public SmsCbCmasInfo(@Class int messageClass, @Category int category,
+            @ResponseType int responseType,
+            @Severity int severity, @Urgency int urgency, @Certainty int certainty) {
         mMessageClass = messageClass;
         mCategory = category;
         mResponseType = responseType;
@@ -234,7 +324,7 @@
      * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}.
      * @return one of the {@code CMAS_CLASS} values
      */
-    public int getMessageClass() {
+    public @Class int getMessageClass() {
         return mMessageClass;
     }
 
@@ -242,7 +332,7 @@
      * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}.
      * @return one of the {@code CMAS_CATEGORY} values
      */
-    public int getCategory() {
+    public @Category int getCategory() {
         return mCategory;
     }
 
@@ -250,7 +340,7 @@
      * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}.
      * @return one of the {@code CMAS_RESPONSE_TYPE} values
      */
-    public int getResponseType() {
+    public @ResponseType int getResponseType() {
         return mResponseType;
     }
 
@@ -258,7 +348,7 @@
      * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}.
      * @return one of the {@code CMAS_SEVERITY} values
      */
-    public int getSeverity() {
+    public @Severity int getSeverity() {
         return mSeverity;
     }
 
@@ -266,15 +356,16 @@
      * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}.
      * @return one of the {@code CMAS_URGENCY} values
      */
-    public int getUrgency() {
+    public @Urgency int getUrgency() {
         return mUrgency;
     }
 
     /**
      * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}.
+     *
      * @return one of the {@code CMAS_CERTAINTY} values
      */
-    public int getCertainty() {
+    public @Certainty int getCertainty() {
         return mCertainty;
     }
 
@@ -287,6 +378,7 @@
 
     /**
      * Describe the kinds of special objects contained in the marshalled representation.
+     *
      * @return a bitmask indicating this Parcelable contains no special objects
      */
     @Override
@@ -295,8 +387,9 @@
     }
 
     /** Creator for unparcelling objects. */
-    public static final Parcelable.Creator<SmsCbCmasInfo>
-            CREATOR = new Parcelable.Creator<SmsCbCmasInfo>() {
+    @NonNull
+    public static final Parcelable.Creator<SmsCbCmasInfo> CREATOR =
+            new Parcelable.Creator<SmsCbCmasInfo>() {
         @Override
         public SmsCbCmasInfo createFromParcel(Parcel in) {
             return new SmsCbCmasInfo(in);
diff --git a/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java b/telephony/java/android/telephony/SmsCbEtwsInfo.java
similarity index 65%
rename from telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
rename to telephony/java/android/telephony/SmsCbEtwsInfo.java
index 14e02de..2a7f7ad 100644
--- a/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
+++ b/telephony/java/android/telephony/SmsCbEtwsInfo.java
@@ -16,21 +16,29 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.text.format.Time;
 
 import com.android.internal.telephony.uicc.IccUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 import java.util.Arrays;
 
 /**
- * Contains information elements for a GSM or UMTS ETWS warning notification.
- * Supported values for each element are defined in 3GPP TS 23.041.
+ * Contains information elements for a GSM or UMTS ETWS (Earthquake and Tsunami Warning
+ * System) warning notification. Supported values for each element are defined in 3GPP TS 23.041.
  *
  * {@hide}
  */
-public class SmsCbEtwsInfo implements Parcelable {
+@SystemApi
+public final class SmsCbEtwsInfo implements Parcelable {
 
     /** ETWS warning type for earthquake. */
     public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
@@ -50,17 +58,30 @@
     /** Unknown ETWS warning type. */
     public static final int ETWS_WARNING_TYPE_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ETWS_WARNING_TYPE_"},
+            value = {
+                    ETWS_WARNING_TYPE_EARTHQUAKE,
+                    ETWS_WARNING_TYPE_TSUNAMI,
+                    ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI,
+                    ETWS_WARNING_TYPE_TEST_MESSAGE,
+                    ETWS_WARNING_TYPE_OTHER_EMERGENCY,
+                    ETWS_WARNING_TYPE_UNKNOWN,
+            })
+    public @interface WarningType {}
+
     /** One of the ETWS warning type constants defined in this class. */
-    private final int mWarningType;
+    private final @WarningType int mWarningType;
 
     /** Whether or not to activate the emergency user alert tone and vibration. */
-    private final boolean mEmergencyUserAlert;
+    private final boolean mIsEmergencyUserAlert;
 
     /** Whether or not to activate a popup alert. */
-    private final boolean mActivatePopup;
+    private final boolean mIsPopupAlert;
 
     /** Whether ETWS primary message or not/ */
-    private final boolean mPrimary;
+    private final boolean mIsPrimary;
 
     /**
      * 50-byte security information (ETWS primary notification for GSM only). As of Release 10,
@@ -69,24 +90,35 @@
      * parceled with the broadcast intent if present, but the timestamp is only computed if an
      * application asks for the individual components.
      */
+    @Nullable
     private final byte[] mWarningSecurityInformation;
 
-    /** Create a new SmsCbEtwsInfo object with the specified values. */
-    public SmsCbEtwsInfo(int warningType, boolean emergencyUserAlert, boolean activatePopup,
-                boolean primary, byte[] warningSecurityInformation) {
+    /**
+     * Create a new SmsCbEtwsInfo object with the specified values.
+     * @param warningType the type of ETWS warning
+     * @param isEmergencyUserAlert whether the warning is an emergency alert, which will activate
+     *                             the user alert tone and vibration
+     * @param isPopupAlert whether the warning will activate a popup alert
+     * @param isPrimary whether this is an ETWS primary message
+     * @param warningSecurityInformation 50-byte security information (for primary notifications
+     *                                   on GSM only).
+     */
+    public SmsCbEtwsInfo(@WarningType int warningType, boolean isEmergencyUserAlert,
+            boolean isPopupAlert,
+            boolean isPrimary, @Nullable byte[] warningSecurityInformation) {
         mWarningType = warningType;
-        mEmergencyUserAlert = emergencyUserAlert;
-        mActivatePopup = activatePopup;
-        mPrimary = primary;
+        mIsEmergencyUserAlert = isEmergencyUserAlert;
+        mIsPopupAlert = isPopupAlert;
+        mIsPrimary = isPrimary;
         mWarningSecurityInformation = warningSecurityInformation;
     }
 
     /** Create a new SmsCbEtwsInfo object from a Parcel. */
     SmsCbEtwsInfo(Parcel in) {
         mWarningType = in.readInt();
-        mEmergencyUserAlert = (in.readInt() != 0);
-        mActivatePopup = (in.readInt() != 0);
-        mPrimary = (in.readInt() != 0);
+        mIsEmergencyUserAlert = (in.readInt() != 0);
+        mIsPopupAlert = (in.readInt() != 0);
+        mIsPrimary = (in.readInt() != 0);
         mWarningSecurityInformation = in.createByteArray();
     }
 
@@ -99,9 +131,9 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mWarningType);
-        dest.writeInt(mEmergencyUserAlert ? 1 : 0);
-        dest.writeInt(mActivatePopup ? 1 : 0);
-        dest.writeInt(mPrimary ? 1 : 0);
+        dest.writeInt(mIsEmergencyUserAlert ? 1 : 0);
+        dest.writeInt(mIsPopupAlert ? 1 : 0);
+        dest.writeInt(mIsPrimary ? 1 : 0);
         dest.writeByteArray(mWarningSecurityInformation);
     }
 
@@ -109,16 +141,17 @@
      * Returns the ETWS warning type.
      * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE}
      */
-    public int getWarningType() {
+    public @WarningType int getWarningType() {
         return mWarningType;
     }
 
     /**
-     * Returns the ETWS emergency user alert flag.
+     * Returns the ETWS emergency user alert flag. If the ETWS message is an emergency alert, it
+     * will activate an alert tone and vibration.
      * @return true to notify terminal to activate emergency user alert; false otherwise
      */
     public boolean isEmergencyUserAlert() {
-        return mEmergencyUserAlert;
+        return mIsEmergencyUserAlert;
     }
 
     /**
@@ -126,7 +159,7 @@
      * @return true to notify terminal to activate display popup; false otherwise
      */
     public boolean isPopupAlert() {
-        return mActivatePopup;
+        return mIsPopupAlert;
     }
 
     /**
@@ -134,7 +167,7 @@
      * @return true if the message is primary message, otherwise secondary message
      */
     public boolean isPrimary() {
-        return mPrimary;
+        return mIsPrimary;
     }
 
     /**
@@ -165,19 +198,21 @@
         int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
 
         timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
+        // timezoneOffset is in quarter hours.
+        int timeZoneOffsetSeconds = timezoneOffset * 15 * 60;
 
-        Time time = new Time(Time.TIMEZONE_UTC);
+        LocalDateTime localDateTime = LocalDateTime.of(
+                // We only need to support years above 2000.
+                year + 2000,
+                month /* 1-12 */,
+                day,
+                hour,
+                minute,
+                second);
 
-        // We only need to support years above 2000.
-        time.year = year + 2000;
-        time.month = month - 1;
-        time.monthDay = day;
-        time.hour = hour;
-        time.minute = minute;
-        time.second = second;
-
-        // Timezone offset is in quarter hours.
-        return time.toMillis(true) - timezoneOffset * 15 * 60 * 1000;
+        long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds;
+        // Convert to milliseconds, ignore overflow.
+        return epochSeconds * 1000;
     }
 
     /**
@@ -185,6 +220,7 @@
      * 3GPP TS 23.041 states that the UE shall ignore this value if received.
      * @return a byte array containing a copy of the primary notification digital signature
      */
+    @Nullable
     public byte[] getPrimaryNotificationSignature() {
         if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) {
             return null;
@@ -195,7 +231,7 @@
     @Override
     public String toString() {
         return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert="
-                + mEmergencyUserAlert + ", activatePopup=" + mActivatePopup + '}';
+                + mIsEmergencyUserAlert + ", activatePopup=" + mIsPopupAlert + '}';
     }
 
     /**
@@ -208,6 +244,7 @@
     }
 
     /** Creator for unparcelling objects. */
+    @NonNull
     public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() {
         @Override
         public SmsCbEtwsInfo createFromParcel(Parcel in) {
diff --git a/telephony/java/com/android/internal/telephony/SmsCbLocation.java b/telephony/java/android/telephony/SmsCbLocation.java
similarity index 90%
rename from telephony/java/com/android/internal/telephony/SmsCbLocation.java
rename to telephony/java/android/telephony/SmsCbLocation.java
index 6eb72a8..adf7154 100644
--- a/telephony/java/com/android/internal/telephony/SmsCbLocation.java
+++ b/telephony/java/android/telephony/SmsCbLocation.java
@@ -16,6 +16,9 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -27,9 +30,11 @@
  *
  * @hide
  */
-public class SmsCbLocation implements Parcelable {
+@SystemApi
+public final class SmsCbLocation implements Parcelable {
 
-    /** The PLMN. Note that this field may be an empty string, but isn't allowed to be null. */
+    /** The PLMN. Note that this field may be an empty string. */
+    @NonNull
     private final String mPlmn;
 
     private final int mLac;
@@ -38,6 +43,7 @@
     /**
      * Construct an empty location object. This is used for some test cases, and for
      * cell broadcasts saved in older versions of the database without location info.
+     * @hide
      */
     public SmsCbLocation() {
         mPlmn = "";
@@ -48,6 +54,7 @@
     /**
      * Construct a location object for the PLMN. This class is immutable, so
      * the same object can be reused for multiple broadcasts.
+     * @hide
      */
     public SmsCbLocation(String plmn) {
         mPlmn = plmn;
@@ -58,6 +65,7 @@
     /**
      * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so
      * the same object can be reused for multiple broadcasts.
+     * @hide
      */
     public SmsCbLocation(String plmn, int lac, int cid) {
         mPlmn = plmn;
@@ -67,6 +75,7 @@
 
     /**
      * Initialize the object from a Parcel.
+     * @hide
      */
     public SmsCbLocation(Parcel in) {
         mPlmn = in.readString();
@@ -78,6 +87,7 @@
      * Returns the MCC/MNC of the network as a String.
      * @return the PLMN identifier (MCC+MNC) as a String
      */
+    @NonNull
     public String getPlmn() {
         return mPlmn;
     }
@@ -129,7 +139,7 @@
      * @param area the location area to compare with this location
      * @return true if this location is contained within the specified location area
      */
-    public boolean isInLocationArea(SmsCbLocation area) {
+    public boolean isInLocationArea(@NonNull SmsCbLocation area) {
         if (mCid != -1 && mCid != area.mCid) {
             return false;
         }
@@ -147,7 +157,7 @@
      * @param cid the Cell ID to compare with
      * @return true if this location is contained within the specified PLMN, LAC, and Cell ID
      */
-    public boolean isInLocationArea(String plmn, int lac, int cid) {
+    public boolean isInLocationArea(@Nullable String plmn, int lac, int cid) {
         if (!mPlmn.equals(plmn)) {
             return false;
         }
@@ -176,8 +186,9 @@
         dest.writeInt(mCid);
     }
 
-    public static final Parcelable.Creator<SmsCbLocation> CREATOR
-            = new Parcelable.Creator<SmsCbLocation>() {
+    @NonNull
+    public static final Parcelable.Creator<SmsCbLocation> CREATOR =
+            new Parcelable.Creator<SmsCbLocation>() {
         @Override
         public SmsCbLocation createFromParcel(Parcel in) {
             return new SmsCbLocation(in);
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
new file mode 100644
index 0000000..77231d1
--- /dev/null
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -0,0 +1,660 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony.CellBroadcasts;
+
+import com.android.internal.telephony.CbGeoUtils;
+import com.android.internal.telephony.CbGeoUtils.Geometry;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Parcelable object containing a received cell broadcast message. There are four different types
+ * of Cell Broadcast messages:
+ *
+ * <ul>
+ * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li>
+ * <li>cell information messages, broadcast on channel 50, indicating the current cell name for
+ *  roaming purposes (required to display on the idle screen in Brazil)</li>
+ * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li>
+ * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li>
+ * </ul>
+ *
+ * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
+ * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
+ * unified under a common name, avoiding some names, such as "Message Identifier", that refer to
+ * two completely different concepts in 3GPP and CDMA.
+ *
+ * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
+ * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
+ * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
+ * application should
+ *
+ * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
+ * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
+ * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
+ * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
+ * are considered unique to the PLMN, to the current cell, or to the current Location Area (or
+ * Service Area in UMTS). The relevant values are concatenated into a single String which will be
+ * unique if the messages are not duplicates.
+ *
+ * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
+ * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
+ *
+ * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
+ * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
+ * Only system applications such as the CellBroadcastReceiver may receive notifications for
+ * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
+ * interference with the immediate display of the alert message and playing of the alert sound and
+ * vibration pattern, which could be caused by poorly written or malicious non-system code.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmsCbMessage implements Parcelable {
+
+    /** @hide */
+    public static final String LOG_TAG = "SMSCB";
+
+    /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
+    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
+
+    /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
+    public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
+
+    /** Location / service area wide geographical scope (GSM/UMTS only). */
+    public static final int GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE = 2;
+
+    /** Cell wide geographical scope (GSM/UMTS only). */
+    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
+
+    /** @hide */
+    @IntDef(prefix = { "GEOGRAPHICAL_SCOPE_" }, value = {
+            GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE,
+            GEOGRAPHICAL_SCOPE_PLMN_WIDE,
+            GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE,
+            GEOGRAPHICAL_SCOPE_CELL_WIDE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GeographicalScope {}
+
+    /** GSM or UMTS format cell broadcast. */
+    public static final int MESSAGE_FORMAT_3GPP = 1;
+
+    /** CDMA format cell broadcast. */
+    public static final int MESSAGE_FORMAT_3GPP2 = 2;
+
+    /** @hide */
+    @IntDef(prefix = { "MESSAGE_FORMAT_" }, value = {
+            MESSAGE_FORMAT_3GPP,
+            MESSAGE_FORMAT_3GPP2
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MessageFormat {}
+
+    /** Normal message priority. */
+    public static final int MESSAGE_PRIORITY_NORMAL = 0;
+
+    /** Interactive message priority. */
+    public static final int MESSAGE_PRIORITY_INTERACTIVE = 1;
+
+    /** Urgent message priority. */
+    public static final int MESSAGE_PRIORITY_URGENT = 2;
+
+    /** Emergency message priority. */
+    public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
+
+    /** @hide */
+    @IntDef(prefix = { "MESSAGE_PRIORITY_" }, value = {
+            MESSAGE_PRIORITY_NORMAL,
+            MESSAGE_PRIORITY_INTERACTIVE,
+            MESSAGE_PRIORITY_URGENT,
+            MESSAGE_PRIORITY_EMERGENCY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MessagePriority {}
+
+    /** Format of this message (for interpretation of service category values). */
+    private final int mMessageFormat;
+
+    /** Geographical scope of broadcast. */
+    private final int mGeographicalScope;
+
+    /**
+     * Serial number of broadcast (message identifier for CDMA, geographical scope + message code +
+     * update number for GSM/UMTS). The serial number plus the location code uniquely identify
+     * a cell broadcast for duplicate detection.
+     */
+    private final int mSerialNumber;
+
+    /**
+     * Location identifier for this message. It consists of the current operator MCC/MNC as a
+     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+     * message is not binary 01, the Location Area is included for comparison. If the GS is
+     * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
+     */
+    @NonNull
+    private final SmsCbLocation mLocation;
+
+    /**
+     * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings,
+     * the information provided by the category is also available via {@link #getEtwsWarningInfo()}
+     * or {@link #getCmasWarningInfo()}.
+     */
+    private final int mServiceCategory;
+
+    /** Message language, as a two-character string, e.g. "en". */
+    @Nullable
+    private final String mLanguage;
+
+    /** Message body, as a String. */
+    @Nullable
+    private final String mBody;
+
+    /** Message priority (including emergency priority). */
+    private final int mPriority;
+
+    /** ETWS warning notification information (ETWS warnings only). */
+    @Nullable
+    private final SmsCbEtwsInfo mEtwsWarningInfo;
+
+    /** CMAS warning notification information (CMAS warnings only). */
+    @Nullable
+    private final SmsCbCmasInfo mCmasWarningInfo;
+
+    /** UNIX timestamp of when the message was received. */
+    private final long mReceivedTimeMillis;
+
+    /** CMAS warning area coordinates. */
+    private final List<Geometry> mGeometries;
+
+    /**
+     * Create a new SmsCbMessage with the specified data.
+     */
+    public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
+            @NonNull SmsCbLocation location, int serviceCategory, @Nullable String language,
+            @Nullable String body, int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo,
+            @Nullable SmsCbCmasInfo cmasWarningInfo) {
+
+        this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language,
+                body, priority, etwsWarningInfo, cmasWarningInfo, null /* geometries */,
+                System.currentTimeMillis());
+    }
+
+    /**
+     * Create a new {@link SmsCbMessage} with the warning area coordinates information.
+     * @hide
+     */
+    public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
+            SmsCbLocation location, int serviceCategory, String language, String body,
+            int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo,
+            List<Geometry> geometries, long receivedTimeMillis) {
+        mMessageFormat = messageFormat;
+        mGeographicalScope = geographicalScope;
+        mSerialNumber = serialNumber;
+        mLocation = location;
+        mServiceCategory = serviceCategory;
+        mLanguage = language;
+        mBody = body;
+        mPriority = priority;
+        mEtwsWarningInfo = etwsWarningInfo;
+        mCmasWarningInfo = cmasWarningInfo;
+        mReceivedTimeMillis = receivedTimeMillis;
+        mGeometries = geometries;
+    }
+
+    /**
+     * Create a new SmsCbMessage object from a Parcel.
+     * @hide
+     */
+    public SmsCbMessage(@NonNull Parcel in) {
+        mMessageFormat = in.readInt();
+        mGeographicalScope = in.readInt();
+        mSerialNumber = in.readInt();
+        mLocation = new SmsCbLocation(in);
+        mServiceCategory = in.readInt();
+        mLanguage = in.readString();
+        mBody = in.readString();
+        mPriority = in.readInt();
+        int type = in.readInt();
+        switch (type) {
+            case 'E':
+                // unparcel ETWS warning information
+                mEtwsWarningInfo = new SmsCbEtwsInfo(in);
+                mCmasWarningInfo = null;
+                break;
+
+            case 'C':
+                // unparcel CMAS warning information
+                mEtwsWarningInfo = null;
+                mCmasWarningInfo = new SmsCbCmasInfo(in);
+                break;
+
+            default:
+                mEtwsWarningInfo = null;
+                mCmasWarningInfo = null;
+        }
+        mReceivedTimeMillis = in.readLong();
+        String geoStr = in.readString();
+        mGeometries = geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMessageFormat);
+        dest.writeInt(mGeographicalScope);
+        dest.writeInt(mSerialNumber);
+        mLocation.writeToParcel(dest, flags);
+        dest.writeInt(mServiceCategory);
+        dest.writeString(mLanguage);
+        dest.writeString(mBody);
+        dest.writeInt(mPriority);
+        if (mEtwsWarningInfo != null) {
+            // parcel ETWS warning information
+            dest.writeInt('E');
+            mEtwsWarningInfo.writeToParcel(dest, flags);
+        } else if (mCmasWarningInfo != null) {
+            // parcel CMAS warning information
+            dest.writeInt('C');
+            mCmasWarningInfo.writeToParcel(dest, flags);
+        } else {
+            // no ETWS or CMAS warning information
+            dest.writeInt('0');
+        }
+        dest.writeLong(mReceivedTimeMillis);
+        dest.writeString(
+                mGeometries != null ? CbGeoUtils.encodeGeometriesToString(mGeometries) : null);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<SmsCbMessage> CREATOR =
+            new Parcelable.Creator<SmsCbMessage>() {
+        @Override
+        public SmsCbMessage createFromParcel(Parcel in) {
+            return new SmsCbMessage(in);
+        }
+
+        @Override
+        public SmsCbMessage[] newArray(int size) {
+            return new SmsCbMessage[size];
+        }
+    };
+
+    /**
+     * Return the geographical scope of this message (GSM/UMTS only).
+     *
+     * @return Geographical scope
+     */
+    public @GeographicalScope int getGeographicalScope() {
+        return mGeographicalScope;
+    }
+
+    /**
+     * Return the broadcast serial number of broadcast (message identifier for CDMA, or
+     * geographical scope + message code + update number for GSM/UMTS). The serial number plus
+     * the location code uniquely identify a cell broadcast for duplicate detection.
+     *
+     * @return the 16-bit CDMA message identifier or GSM/UMTS serial number
+     */
+    public int getSerialNumber() {
+        return mSerialNumber;
+    }
+
+    /**
+     * Return the location identifier for this message, consisting of the MCC/MNC as a
+     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+     * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the
+     * cell ID is also included. The {@link SmsCbLocation} object includes a method to test
+     * if the location is included within another location area or within a PLMN and CellLocation.
+     *
+     * @return the geographical location code for duplicate message detection
+     */
+    @NonNull
+    public android.telephony.SmsCbLocation getLocation() {
+        return mLocation;
+    }
+
+    /**
+     * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation
+     * of the category is radio technology specific. For ETWS and CMAS warnings, the information
+     * provided by the category is available via {@link #getEtwsWarningInfo()} or
+     * {@link #getCmasWarningInfo()} in a radio technology independent format.
+     *
+     * @return the radio technology specific service category
+     */
+    public int getServiceCategory() {
+        return mServiceCategory;
+    }
+
+    /**
+     * Get the ISO-639-1 language code for this message, or null if unspecified
+     *
+     * @return Language code
+     */
+    @Nullable
+    public String getLanguageCode() {
+        return mLanguage;
+    }
+
+    /**
+     * Get the body of this message, or null if no body available
+     *
+     * @return Body, or null
+     */
+    @Nullable
+    public String getMessageBody() {
+        return mBody;
+    }
+
+    /**
+     * Get the warning area coordinates information represent by polygons and circles.
+     * @return a list of geometries, {@link Nullable} means there is no coordinate information
+     * associated to this message.
+     * @hide
+     */
+    @Nullable
+    public List<Geometry> getGeometries() {
+        return mGeometries;
+    }
+
+    /**
+     * Get the time when this message was received.
+     * @return the time in millisecond
+     */
+    public long getReceivedTime() {
+        return mReceivedTimeMillis;
+    }
+
+    /**
+     * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
+     * @return an integer representing 3GPP or 3GPP2 message format
+     */
+    public @MessageFormat int getMessageFormat() {
+        return mMessageFormat;
+    }
+
+    /**
+     * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL}
+     * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return
+     * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}.
+     * @return an integer representing the message priority
+     */
+    public @MessagePriority int getMessagePriority() {
+        return mPriority;
+    }
+
+    /**
+     * If this is an ETWS warning notification then this method will return an object containing
+     * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an
+     * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte
+     * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the
+     * ETWS primary notification timestamp and digital signature if received.
+     *
+     * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
+     */
+    @Nullable
+    public SmsCbEtwsInfo getEtwsWarningInfo() {
+        return mEtwsWarningInfo;
+    }
+
+    /**
+     * If this is a CMAS warning notification then this method will return an object containing
+     * the CMAS message class, category, response type, severity, urgency and certainty.
+     * The message class is always present. Severity, urgency and certainty are present for CDMA
+     * warning notifications containing a type 1 elements record and for GSM and UMTS warnings
+     * except for the Presidential-level alert category. Category and response type are only
+     * available for CDMA notifications containing a type 1 elements record.
+     *
+     * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
+     */
+    @Nullable
+    public SmsCbCmasInfo getCmasWarningInfo() {
+        return mCmasWarningInfo;
+    }
+
+    /**
+     * Return whether this message is an emergency (PWS) message type.
+     * @return true if the message is an emergency notification; false otherwise
+     */
+    public boolean isEmergencyMessage() {
+        return mPriority == MESSAGE_PRIORITY_EMERGENCY;
+    }
+
+    /**
+     * Return whether this message is an ETWS warning alert.
+     * @return true if the message is an ETWS warning notification; false otherwise
+     */
+    public boolean isEtwsMessage() {
+        return mEtwsWarningInfo != null;
+    }
+
+    /**
+     * Return whether this message is a CMAS warning alert.
+     * @return true if the message is a CMAS warning notification; false otherwise
+     */
+    public boolean isCmasMessage() {
+        return mCmasWarningInfo != null;
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber="
+                + mSerialNumber + ", location=" + mLocation + ", serviceCategory="
+                + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
+                + ", priority=" + mPriority
+                + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
+                + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "")
+                + ", geo=" + (mGeometries != null
+                ? CbGeoUtils.encodeGeometriesToString(mGeometries) : "null")
+                + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @return the {@link ContentValues} instance that includes the cell broadcast data.
+     */
+    @NonNull
+    public ContentValues getContentValues() {
+        ContentValues cv = new ContentValues(16);
+        cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope);
+        if (mLocation.getPlmn() != null) {
+            cv.put(CellBroadcasts.PLMN, mLocation.getPlmn());
+        }
+        if (mLocation.getLac() != -1) {
+            cv.put(CellBroadcasts.LAC, mLocation.getLac());
+        }
+        if (mLocation.getCid() != -1) {
+            cv.put(CellBroadcasts.CID, mLocation.getCid());
+        }
+        cv.put(CellBroadcasts.SERIAL_NUMBER, getSerialNumber());
+        cv.put(CellBroadcasts.SERVICE_CATEGORY, getServiceCategory());
+        cv.put(CellBroadcasts.LANGUAGE_CODE, getLanguageCode());
+        cv.put(CellBroadcasts.MESSAGE_BODY, getMessageBody());
+        cv.put(CellBroadcasts.MESSAGE_FORMAT, getMessageFormat());
+        cv.put(CellBroadcasts.MESSAGE_PRIORITY, getMessagePriority());
+
+        SmsCbEtwsInfo etwsInfo = getEtwsWarningInfo();
+        if (etwsInfo != null) {
+            cv.put(CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
+        }
+
+        SmsCbCmasInfo cmasInfo = getCmasWarningInfo();
+        if (cmasInfo != null) {
+            cv.put(CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass());
+            cv.put(CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory());
+            cv.put(CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType());
+            cv.put(CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity());
+            cv.put(CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency());
+            cv.put(CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty());
+        }
+
+        cv.put(CellBroadcasts.RECEIVED_TIME, mReceivedTimeMillis);
+
+        if (mGeometries != null) {
+            cv.put(CellBroadcasts.GEOMETRIES, CbGeoUtils.encodeGeometriesToString(mGeometries));
+        } else {
+            cv.put(CellBroadcasts.GEOMETRIES, (String) null);
+        }
+
+        return cv;
+    }
+
+    /**
+     * Create a {@link SmsCbMessage} instance from a row in the cell broadcast database.
+     * @param cursor an open SQLite cursor pointing to the row to read
+     * @return a {@link SmsCbMessage} instance.
+     * @throws IllegalArgumentException if one of the required columns is missing
+     */
+    @NonNull
+    public static SmsCbMessage createFromCursor(@NonNull Cursor cursor) {
+        int geoScope = cursor.getInt(
+                cursor.getColumnIndexOrThrow(CellBroadcasts.GEOGRAPHICAL_SCOPE));
+        int serialNum = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERIAL_NUMBER));
+        int category = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERVICE_CATEGORY));
+        String language = cursor.getString(
+                cursor.getColumnIndexOrThrow(CellBroadcasts.LANGUAGE_CODE));
+        String body = cursor.getString(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_BODY));
+        int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT));
+        int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY));
+
+        String plmn;
+        int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN);
+        if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) {
+            plmn = cursor.getString(plmnColumn);
+        } else {
+            plmn = null;
+        }
+
+        int lac;
+        int lacColumn = cursor.getColumnIndex(CellBroadcasts.LAC);
+        if (lacColumn != -1 && !cursor.isNull(lacColumn)) {
+            lac = cursor.getInt(lacColumn);
+        } else {
+            lac = -1;
+        }
+
+        int cid;
+        int cidColumn = cursor.getColumnIndex(CellBroadcasts.CID);
+        if (cidColumn != -1 && !cursor.isNull(cidColumn)) {
+            cid = cursor.getInt(cidColumn);
+        } else {
+            cid = -1;
+        }
+
+        SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
+
+        SmsCbEtwsInfo etwsInfo;
+        int etwsWarningTypeColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_WARNING_TYPE);
+        if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) {
+            int warningType = cursor.getInt(etwsWarningTypeColumn);
+            etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null);
+        } else {
+            etwsInfo = null;
+        }
+
+        SmsCbCmasInfo cmasInfo = null;
+        int cmasMessageClassColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_MESSAGE_CLASS);
+        if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) {
+            int messageClass = cursor.getInt(cmasMessageClassColumn);
+
+            int cmasCategory;
+            int cmasCategoryColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CATEGORY);
+            if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) {
+                cmasCategory = cursor.getInt(cmasCategoryColumn);
+            } else {
+                cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
+            }
+
+            int responseType;
+            int cmasResponseTypeColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_RESPONSE_TYPE);
+            if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) {
+                responseType = cursor.getInt(cmasResponseTypeColumn);
+            } else {
+                responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
+            }
+
+            int severity;
+            int cmasSeverityColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_SEVERITY);
+            if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) {
+                severity = cursor.getInt(cmasSeverityColumn);
+            } else {
+                severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+            }
+
+            int urgency;
+            int cmasUrgencyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_URGENCY);
+            if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) {
+                urgency = cursor.getInt(cmasUrgencyColumn);
+            } else {
+                urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+            }
+
+            int certainty;
+            int cmasCertaintyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CERTAINTY);
+            if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) {
+                certainty = cursor.getInt(cmasCertaintyColumn);
+            } else {
+                certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+            }
+
+            cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity,
+                    urgency, certainty);
+        }
+
+        String geoStr = cursor.getString(cursor.getColumnIndex(CellBroadcasts.GEOMETRIES));
+        List<Geometry> geometries =
+                geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
+
+        long receivedTimeSec = cursor.getLong(
+                cursor.getColumnIndexOrThrow(CellBroadcasts.RECEIVED_TIME));
+
+        return new SmsCbMessage(format, geoScope, serialNum, location, category,
+                language, body, priority, etwsInfo, cmasInfo, geometries, receivedTimeSec);
+    }
+
+    /**
+     * @return {@code True} if this message needs geo-fencing check.
+     */
+    public boolean needGeoFencingCheck() {
+        return mGeometries != null;
+    }
+}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 0ee08e1..be6e57b 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -16,32 +16,38 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressAutoDoc;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.BaseBundle;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.provider.Telephony;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.IMms;
 import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.SmsRawData;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -54,22 +60,22 @@
 
 /**
  * Manages SMS operations such as sending data, text, and pdu SMS messages.
- * Get this object by calling the static method {@link #getDefault()}.
+ * Get this object by calling the static method {@link #getDefault()}. To create an instance of
+ * {@link SmsManager} associated with a specific subscription ID, call
+ * {@link #getSmsManagerForSubscriptionId(int)}. This is typically used for devices that support
+ * multiple active subscriptions at once.
  *
  * <p>For information about how to behave as the default SMS app on Android 4.4 (API level 19)
  * and higher, see {@link android.provider.Telephony}.
+ *
+ * @see SubscriptionManager#getActiveSubscriptionInfoList()
  */
 public final class SmsManager {
     private static final String TAG = "SmsManager";
 
-    /**
-     * A psuedo-subId that represents the default subId at any given time. The actual subId it
-     * represents changes as the default subId is changed.
-     */
-    private static final int DEFAULT_SUBSCRIPTION_ID = -1002;
-
     /** Singleton object constructed during class initialization. */
-    private static final SmsManager sInstance = new SmsManager(DEFAULT_SUBSCRIPTION_ID);
+    private static final SmsManager sInstance = new SmsManager(
+            SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
     private static final Object sLockObject = new Object();
 
     /** @hide */
@@ -110,7 +116,7 @@
      * Whether MMS is enabled for the current carrier (boolean type)
      */
     public static final String
-        MMS_CONFIG_MMS_ENABLED = CarrierConfigManager.KEY_MMS_MMS_ENABLED_BOOL;
+            MMS_CONFIG_MMS_ENABLED = CarrierConfigManager.KEY_MMS_MMS_ENABLED_BOOL;
     /**
      * Whether group MMS is enabled for the current carrier (boolean type)
      */
@@ -278,12 +284,6 @@
     public static final String MMS_CONFIG_CLOSE_CONNECTION =
             CarrierConfigManager.KEY_MMS_CLOSE_CONNECTION_BOOL;
 
-    /*
-     * Forwarded constants from SimDialogActivity.
-     */
-    private static String DIALOG_TYPE_KEY = "dialog_type";
-    private static final int SMS_PICK = 2;
-
     /**
      * 3gpp2 SMS priority is not specified
      * @hide
@@ -296,6 +296,18 @@
     public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1;
 
     /**
+     * Extra key passed into a PendingIntent when the SMS operation failed due to there being no
+     * default set.
+     */
+    private static final String NO_DEFAULT_EXTRA = "noDefault";
+
+    // result of asking the user for a subscription to perform an operation.
+    private interface SubscriptionResolverResult {
+        void onSuccess(int subId);
+        void onFailure();
+    }
+
+    /**
      * Send a text based SMS.
      *
      * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
@@ -307,6 +319,15 @@
      * responsible for writing its sent messages to the SMS Provider). For information about
      * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
      *
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail sending the SMS message because no
+     * suitable default subscription could be found. In this case, if {@code sentIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the
+     * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions
+     * where this operation may fail.
+     * </p>
+     *
      *
      * @param destinationAddress the address to send the message to
      * @param scAddress is the service center address or null to use
@@ -350,15 +371,51 @@
             throw new IllegalArgumentException("Invalid message body");
         }
 
-        try {
-            // If the subscription is invalid or default, we will use the default phone to send the
-            // SMS and possibly fail later in the SMS sending process.
+        final Context context = ActivityThread.currentApplication().getApplicationContext();
+        // We will only show the SMS disambiguation dialog in the case that the message is being
+        // persisted. This is for two reasons:
+        // 1) Messages that are not persisted are sent by carrier/OEM apps for a specific
+        //    subscription and require special permissions. These messages are usually not sent by
+        //    the device user and should not have an SMS disambiguation dialog associated with them
+        //    because the device user did not trigger them.
+        // 2) The SMS disambiguation dialog ONLY checks to make sure that the user has the SEND_SMS
+        //    permission. If we call resolveSubscriptionForOperation from a carrier/OEM app that has
+        //    the correct MODIFY_PHONE_STATE or carrier permissions, but no SEND_SMS, it will throw
+        //    an incorrect SecurityException.
+        if (persistMessage) {
+            resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+                @Override
+                public void onSuccess(int subId) {
+                    ISms iSms = getISmsServiceOrThrow();
+                    try {
+                        iSms.sendTextForSubscriber(subId, packageName,
+                                destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                                persistMessage);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "sendTextMessageInternal: Couldn't send SMS, exception - "
+                                + e.getMessage());
+                        notifySmsGenericError(sentIntent);
+                    }
+                }
+
+                @Override
+                public void onFailure() {
+                    notifySmsErrorNoDefaultSet(context, sentIntent);
+                }
+            });
+        } else {
+            // Not persisting the message, used by sendTextMessageWithoutPersisting() and is not
+            // visible to the user.
             ISms iSms = getISmsServiceOrThrow();
-            iSms.sendTextForSubscriber(getSubscriptionId(), packageName,
-                    destinationAddress, scAddress, text, sentIntent, deliveryIntent,
-                    persistMessage);
-        } catch (RemoteException ex) {
-            // ignore it
+            try {
+                iSms.sendTextForSubscriber(getSubscriptionId(), packageName,
+                        destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                        persistMessage);
+            } catch (RemoteException e) {
+                Log.e(TAG, "sendTextMessageInternal (no persist): Couldn't send SMS, exception - "
+                        + e.getMessage());
+                notifySmsGenericError(sentIntent);
+            }
         }
     }
 
@@ -375,6 +432,17 @@
      * privileges (see {@link TelephonyManager#hasCarrierPrivileges}), or that the calling app is
      * the default IMS app (see
      * {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}).
+     * </p>
+     *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the SMS being sent on the subscription associated with logical
+     * slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the
+     * correct subscription.
+     * </p>
      *
      * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
      */
@@ -394,6 +462,16 @@
      * A variant of {@link SmsManager#sendTextMessage} that allows self to be the caller. This is
      * for internal use only.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the SMS being sent on the subscription associated with logical
+     * slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the
+     * correct subscription.
+     * </p>
+     *
      * @param persistMessage whether to persist the sent message in the SMS app. the caller must be
      * the Phone process if set to false.
      *
@@ -417,13 +495,22 @@
                     destinationAddress,
                     scAddress, text, sentIntent, deliveryIntent, persistMessage);
         } catch (RemoteException ex) {
-            // ignore it
+            notifySmsGenericError(sentIntent);
         }
     }
 
     /**
      * Send a text based SMS with messaging options.
      *
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail sending the SMS message because no
+     * suitable default subscription could be found. In this case, if {@code sentIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the
+     * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions
+     * where this operation may fail.
+     * </p>
+     *
      * @param destinationAddress the address to send the message to
      * @param scAddress is the service center address or null to use
      *  the current default SMSC
@@ -494,16 +581,59 @@
             validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
         }
 
-        try {
-            ISms iSms = getISmsServiceOrThrow();
-            if (iSms != null) {
-                iSms.sendTextForSubscriberWithOptions(getSubscriptionId(),
-                        ActivityThread.currentPackageName(), destinationAddress, scAddress, text,
-                        sentIntent, deliveryIntent, persistMessage,  priority, expectMore,
-                        validityPeriod);
+        final int finalPriority = priority;
+        final int finalValidity = validityPeriod;
+        final Context context = ActivityThread.currentApplication().getApplicationContext();
+        // We will only show the SMS disambiguation dialog in the case that the message is being
+        // persisted. This is for two reasons:
+        // 1) Messages that are not persisted are sent by carrier/OEM apps for a specific
+        //    subscription and require special permissions. These messages are usually not sent by
+        //    the device user and should not have an SMS disambiguation dialog associated with them
+        //    because the device user did not trigger them.
+        // 2) The SMS disambiguation dialog ONLY checks to make sure that the user has the SEND_SMS
+        //    permission. If we call resolveSubscriptionForOperation from a carrier/OEM app that has
+        //    the correct MODIFY_PHONE_STATE or carrier permissions, but no SEND_SMS, it will throw
+        //    an incorrect SecurityException.
+        if (persistMessage) {
+            resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+                @Override
+                public void onSuccess(int subId) {
+                    try {
+                        ISms iSms = getISmsServiceOrThrow();
+                        if (iSms != null) {
+                            iSms.sendTextForSubscriberWithOptions(subId,
+                                    ActivityThread.currentPackageName(), destinationAddress,
+                                    scAddress,
+                                    text, sentIntent, deliveryIntent, persistMessage, finalPriority,
+                                    expectMore, finalValidity);
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "sendTextMessageInternal: Couldn't send SMS, exception - "
+                                + e.getMessage());
+                        notifySmsGenericError(sentIntent);
+                    }
+                }
+
+                @Override
+                public void onFailure() {
+                    notifySmsErrorNoDefaultSet(context, sentIntent);
+                }
+            });
+        } else {
+            try {
+                ISms iSms = getISmsServiceOrThrow();
+                if (iSms != null) {
+                    iSms.sendTextForSubscriberWithOptions(getSubscriptionId(),
+                            ActivityThread.currentPackageName(), destinationAddress,
+                            scAddress,
+                            text, sentIntent, deliveryIntent, persistMessage, finalPriority,
+                            expectMore, finalValidity);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "sendTextMessageInternal(no persist): Couldn't send SMS, exception - "
+                        + e.getMessage());
+                notifySmsGenericError(sentIntent);
             }
-        } catch (RemoteException ex) {
-            // ignore it
         }
     }
 
@@ -515,6 +645,16 @@
      * privileges.
      * </p>
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the SMS being sent on the subscription associated with logical
+     * slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the
+     * correct subscription.
+     * </p>
+     *
      * @see #sendTextMessage(String, String, String, PendingIntent,
      * PendingIntent, int, boolean, int)
      * @hide
@@ -535,6 +675,16 @@
      * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
      * privileges per {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the SMS being injected on the subscription associated with
+     * logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is
+     * delivered to the correct subscription.
+     * </p>
+     *
      * @param pdu is the byte array of pdu to be injected into android application framework
      * @param format is the format of SMS pdu ({@link SmsMessage#FORMAT_3GPP} or
      *  {@link SmsMessage#FORMAT_3GPP2})
@@ -562,7 +712,13 @@
                         getSubscriptionId(), pdu, format, receivedIntent);
             }
         } catch (RemoteException ex) {
-          // ignore it
+            try {
+                if (receivedIntent != null) {
+                    receivedIntent.send(Telephony.Sms.Intents.RESULT_SMS_GENERIC_ERROR);
+                }
+            } catch (PendingIntent.CanceledException cx) {
+                // Don't worry about it, we do not need to notify the caller if this is the case.
+            }
         }
     }
 
@@ -594,6 +750,16 @@
      * responsible for writing its sent messages to the SMS Provider). For information about
      * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
      *
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail sending the SMS message because no
+     * suitable default subscription could be found. In this case, if {@code sentIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the
+     * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions
+     * where this operation may fail.
+     * </p>
+     *
+     *
      * @param destinationAddress the address to send the message to
      * @param scAddress is the service center address or null to use
      *   the current default SMSC
@@ -629,11 +795,22 @@
     }
 
     /**
-     * @hide
      * Similar method as #sendMultipartTextMessage(String, String, ArrayList, ArrayList, ArrayList)
-     * With an additional argument
-     * @param packageName serves as the default package name if ActivityThread.currentpackageName is
-     *                    null.
+     * With an additional argument.
+     *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use the Telephony
+     * framework and will never trigger an SMS disambiguation dialog. If this method is called on a
+     * device that has multiple active subscriptions, this {@link SmsManager} instance has been
+     * created with {@link #getDefault()}, and no user-defined default subscription is defined, the
+     * subscription ID associated with this message will be INVALID, which will result in the SMS
+     * being sent on the subscription associated with logical slot 0. Use
+     * {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the correct
+     * subscription.
+     * </p>
+     *
+     * @param packageName serves as the default package name if
+     * {@link ActivityThread#currentPackageName()} is null.
+     * @hide
      */
     public void sendMultipartTextMessageExternal(
             String destinationAddress, String scAddress, ArrayList<String> parts,
@@ -657,13 +834,52 @@
         }
 
         if (parts.size() > 1) {
-            try {
-                ISms iSms = getISmsServiceOrThrow();
-                iSms.sendMultipartTextForSubscriber(getSubscriptionId(),
-                        packageName, destinationAddress, scAddress, parts,
-                        sentIntents, deliveryIntents, persistMessage);
-            } catch (RemoteException ex) {
-                // ignore it
+            final Context context = ActivityThread.currentApplication().getApplicationContext();
+            // We will only show the SMS disambiguation dialog in the case that the message is being
+            // persisted. This is for two reasons:
+            // 1) Messages that are not persisted are sent by carrier/OEM apps for a specific
+            //    subscription and require special permissions. These messages are usually not sent
+            //    by the device user and should not have an SMS disambiguation dialog associated
+            //    with them because the device user did not trigger them.
+            // 2) The SMS disambiguation dialog ONLY checks to make sure that the user has the
+            //    SEND_SMS permission. If we call resolveSubscriptionForOperation from a carrier/OEM
+            //    app that has the correct MODIFY_PHONE_STATE or carrier permissions, but no
+            //    SEND_SMS, it will throw an incorrect SecurityException.
+            if (persistMessage) {
+                resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+                    @Override
+                    public void onSuccess(int subId) {
+                        try {
+                            ISms iSms = getISmsServiceOrThrow();
+                            iSms.sendMultipartTextForSubscriber(subId, packageName,
+                                    destinationAddress, scAddress, parts, sentIntents,
+                                    deliveryIntents, persistMessage);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "sendMultipartTextMessageInternal: Couldn't send SMS - "
+                                    + e.getMessage());
+                            notifySmsGenericError(sentIntents);
+                        }
+                    }
+
+                    @Override
+                    public void onFailure() {
+                        notifySmsErrorNoDefaultSet(context, sentIntents);
+                    }
+                });
+            } else {
+                // Called by apps that are not user facing, don't show disambiguation dialog.
+                try {
+                    ISms iSms = getISmsServiceOrThrow();
+                    if (iSms != null) {
+                        iSms.sendMultipartTextForSubscriber(getSubscriptionId(), packageName,
+                                destinationAddress, scAddress, parts, sentIntents, deliveryIntents,
+                                persistMessage);
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "sendMultipartTextMessageInternal: Couldn't send SMS - "
+                            + e.getMessage());
+                    notifySmsGenericError(sentIntents);
+                }
             }
         } else {
             PendingIntent sentIntent = null;
@@ -682,6 +898,15 @@
     /**
      * Send a multi-part text based SMS without writing it into the SMS Provider.
      *
+     * <p>
+     * If this method is called on a device with multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the SMS sent on the subscription associated with slot
+     * 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent using the
+     * correct subscription.
+     * </p>
+     *
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
      * privileges.
@@ -713,6 +938,15 @@
      * responsible for writing its sent messages to the SMS Provider). For information about
      * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
      *
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail sending the SMS message because no
+     * suitable default subscription could be found. In this case, if {@code sentIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the
+     * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions
+     * where this operation may fail.
+     * </p>
+
      * @param destinationAddress the address to send the message to
      * @param scAddress is the service center address or null to use
      *   the current default SMSC
@@ -780,24 +1014,56 @@
         }
 
         if (priority < 0x00 || priority > 0x03) {
-           priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
+            priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
         }
 
         if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
-           validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
+            validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
         }
 
         if (parts.size() > 1) {
-            try {
-                ISms iSms = getISmsServiceOrThrow();
-                if (iSms != null) {
-                    iSms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
-                            ActivityThread.currentPackageName(), destinationAddress, scAddress,
-                            parts, sentIntents, deliveryIntents, persistMessage, priority,
-                            expectMore, validityPeriod);
+            final int finalPriority = priority;
+            final int finalValidity = validityPeriod;
+            final Context context = ActivityThread.currentApplication().getApplicationContext();
+            if (persistMessage) {
+                resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+                    @Override
+                    public void onSuccess(int subId) {
+                        try {
+                            ISms iSms = getISmsServiceOrThrow();
+                            if (iSms != null) {
+                                iSms.sendMultipartTextForSubscriberWithOptions(subId,
+                                        ActivityThread.currentPackageName(), destinationAddress,
+                                        scAddress, parts, sentIntents, deliveryIntents,
+                                        persistMessage, finalPriority, expectMore, finalValidity);
+                            }
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "sendMultipartTextMessageInternal: Couldn't send SMS - "
+                                    + e.getMessage());
+                            notifySmsGenericError(sentIntents);
+                        }
+                    }
+
+                    @Override
+                    public void onFailure() {
+                        notifySmsErrorNoDefaultSet(context, sentIntents);
+                    }
+                });
+            } else {
+                // Sent by apps that are not user visible, so don't show SIM disambiguation dialog.
+                try {
+                    ISms iSms = getISmsServiceOrThrow();
+                    if (iSms != null) {
+                        iSms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
+                                ActivityThread.currentPackageName(), destinationAddress,
+                                scAddress, parts, sentIntents, deliveryIntents,
+                                persistMessage, finalPriority, expectMore, finalValidity);
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "sendMultipartTextMessageInternal (no persist): Couldn't send SMS - "
+                            + e.getMessage());
+                    notifySmsGenericError(sentIntents);
                 }
-            } catch (RemoteException ex) {
-                // ignore it
             }
         } else {
             PendingIntent sentIntent = null;
@@ -822,6 +1088,16 @@
      * privileges.
      * </p>
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use the Telephony
+     * framework and will never trigger an SMS disambiguation dialog. If this method is called on a
+     * device that has multiple active subscriptions, this {@link SmsManager} instance has been
+     * created with {@link #getDefault()}, and no user-defined default subscription is defined, the
+     * subscription ID associated with this message will be INVALID, which will result in the SMS
+     * being sent on the subscription associated with logical slot 0. Use
+     * {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the correct
+     * subscription.
+     * </p>
+     *
      * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList,
      * ArrayList, int, boolean, int)
      * @hide
@@ -835,12 +1111,21 @@
                 validityPeriod);
     }
 
-   /**
+    /**
      * Send a data based SMS to a specific application port.
      *
      * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
      * {@link android.Manifest.permission#SEND_SMS} permission.</p>
      *
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail sending the SMS message because no
+     * suitable default subscription could be found. In this case, if {@code sentIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the
+     * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions
+     * where this operation may fail.
+     * </p>
+     *
      * @param destinationAddress the address to send the message to
      * @param scAddress is the service center address or null to use
      *  the current default SMSC
@@ -876,20 +1161,41 @@
             throw new IllegalArgumentException("Invalid message data");
         }
 
-        try {
-            ISms iSms = getISmsServiceOrThrow();
-            iSms.sendDataForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
-                    destinationAddress, scAddress, destinationPort & 0xFFFF,
-                    data, sentIntent, deliveryIntent);
-        } catch (RemoteException ex) {
-            // ignore it
-        }
+        final Context context = ActivityThread.currentApplication().getApplicationContext();
+        resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+            @Override
+            public void onSuccess(int subId) {
+                try {
+                    ISms iSms = getISmsServiceOrThrow();
+                    iSms.sendDataForSubscriber(subId, ActivityThread.currentPackageName(),
+                            destinationAddress, scAddress, destinationPort & 0xFFFF, data,
+                            sentIntent, deliveryIntent);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "sendDataMessage: Couldn't send SMS - Exception: " + e.getMessage());
+                    notifySmsGenericError(sentIntent);
+                }
+            }
+            @Override
+            public void onFailure() {
+                notifySmsErrorNoDefaultSet(context, sentIntent);
+            }
+        });
     }
 
     /**
      * A variant of {@link SmsManager#sendDataMessage} that allows self to be the caller. This is
      * for internal use only.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the SMS being sent on the subscription associated with logical
+     * slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the
+     * correct subscription.
+     * </p>
+     *
      * @hide
      */
     public void sendDataMessageWithSelfPermissions(
@@ -908,30 +1214,60 @@
             iSms.sendDataForSubscriberWithSelfPermissions(getSubscriptionId(),
                     ActivityThread.currentPackageName(), destinationAddress, scAddress,
                     destinationPort & 0xFFFF, data, sentIntent, deliveryIntent);
-        } catch (RemoteException ex) {
-            // ignore it
+        } catch (RemoteException e) {
+            Log.e(TAG, "sendDataMessageWithSelfPermissions: Couldn't send SMS - Exception: "
+                    + e.getMessage());
+            notifySmsGenericError(sentIntent);
         }
     }
 
     /**
      * Get the SmsManager associated with the default subscription id. The instance will always be
-     * associated with the default subscription id, even if the default subscription id is changed.
+     * associated with the default subscription id, even if the default subscription id changes.
      *
-     * @return the SmsManager associated with the default subscription id
+     * <p class="note"><strong>Note:</strong> For devices that support multiple active subscriptions
+     * at a time, SmsManager will track the subscription set by the user as the default SMS
+     * subscription. If the user has not set a default, {@link SmsManager} may
+     * start an activity to kick off a subscription disambiguation dialog. Most operations will not
+     * complete until the user has chosen the subscription that will be associated with the
+     * operation. If the user cancels the dialog without choosing a subscription, one of the
+     * following will happen, depending on the target SDK version of the application. For
+     * compatibility purposes, if the target SDK level is <= 28, telephony will still send the SMS
+     * over the first available subscription. If the target SDK level is > 28, the operation will
+     * fail to complete.
+     * </p>
+     *
+     * <p class="note"><strong>Note:</strong> If this method is used to perform an operation on a
+     * device that has multiple active subscriptions, the user has not set a default SMS
+     * subscription, and the operation is being performed while the application is not in the
+     * foreground, the SMS disambiguation dialog will not be shown. The result of the operation will
+     * conclude as if the user cancelled the disambiguation dialog and the operation will finish as
+     * outlined above, depending on the target SDK version of the calling application. It is safer
+     * to use {@link #getSmsManagerForSubscriptionId(int)} if the application will perform the
+     * operation while in the background because this can cause unpredictable results, such as the
+     * operation being sent over the wrong subscription or failing completely, depending on the
+     * user's default SMS subscription setting.
+     * </p>
+     *
+     * @return the {@link SmsManager} associated with the default subscription id.
+     *
+     * @see SubscriptionManager#getDefaultSmsSubscriptionId()
      */
     public static SmsManager getDefault() {
         return sInstance;
     }
 
     /**
-     * Get the the instance of the SmsManager associated with a particular subscription id
+     * Get the instance of the SmsManager associated with a particular subscription ID.
      *
-     * @param subId an SMS subscription id, typically accessed using
-     *   {@link android.telephony.SubscriptionManager}
-     * @return the instance of the SmsManager associated with subId
+     * <p class="note"><strong>Note:</strong> Constructing an {@link SmsManager} in this manner will
+     * never cause an SMS disambiguation dialog to appear, unlike {@link #getDefault()}.
+     * </p>
+     *
+     * @see SubscriptionManager#getActiveSubscriptionInfoList()
+     * @see SubscriptionManager#getDefaultSmsSubscriptionId()
      */
     public static SmsManager getSmsManagerForSubscriptionId(int subId) {
-        // TODO(shri): Add javadoc link once SubscriptionManager is made public api
         synchronized(sLockObject) {
             SmsManager smsManager = sSubInstances.get(subId);
             if (smsManager == null) {
@@ -949,60 +1285,188 @@
     /**
      * Get the associated subscription id. If the instance was returned by {@link #getDefault()},
      * then this method may return different values at different points in time (if the user
-     * changes the default subscription id). It will return < 0 if the default subscription id
-     * cannot be determined.
+     * changes the default subscription id).
      *
-     * Additionally, to support legacy applications that are not multi-SIM aware,
-     * if the following are true:
-     *     - We are using a multi-SIM device
-     *     - A default SMS SIM has not been selected
-     *     - At least one SIM subscription is available
-     * then ask the user to set the default SMS SIM.
+     * <p class="note"><strong>Note:</strong> This method used to display a disambiguation dialog to
+     * the user asking them to choose a default subscription to send SMS messages over if they
+     * haven't chosen yet. Starting in API level 29, we allow the user to not have a default set as
+     * a valid option for the default SMS subscription on multi-SIM devices. We no longer show the
+     * disambiguation dialog and return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if the
+     * device has multiple active subscriptions and no default is set.
+     * </p>
      *
-     * @return associated subscription id
+     * @return associated subscription ID or {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if
+     * the default subscription id cannot be determined or the device has multiple active
+     * subscriptions and and no default is set ("ask every time") by the user.
      */
     public int getSubscriptionId() {
-        final int subId = getSubIdOrDefault();
+        try {
+            return (mSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
+                    ? getISmsServiceOrThrow().getPreferredSmsSubscription() : mSubId;
+        } catch (RemoteException e) {
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        }
+    }
+
+    /**
+     * Resolves the subscription id to use for the associated operation if
+     * {@link #getSubscriptionId()} returns {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+     *
+     * If app targets API level 28 or below and they are either sending the SMS from the background
+     * or the device has more than one active subscription available and no default is set, we will
+     * use the first logical slot to send the SMS and possibly fail later in the SMS sending
+     * process.
+     *
+     * Regardless of the API level, if the app is the foreground app, then we will show the SMS
+     * disambiguation dialog. If the app is in the background and tries to perform an operation, we
+     * will not show the disambiguation dialog.
+     *
+     * See {@link #getDefault()} for a detailed explanation of how this method operates.
+     *
+     * @param resolverResult The callback that will be called when the subscription is resolved or
+     *                       fails to be resolved.
+     */
+    private void resolveSubscriptionForOperation(SubscriptionResolverResult resolverResult) {
+        int subId = getSubscriptionId();
         boolean isSmsSimPickActivityNeeded = false;
         final Context context = ActivityThread.currentApplication().getApplicationContext();
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
+                // Determines if the SMS SIM pick activity should be shown. This is only shown if:
+                // 1) The device has multiple active subscriptions and an SMS default subscription
+                //    hasn't been set, and
+                // 2) SmsManager is being called from the foreground app.
+                // Android does not allow background activity starts, so we need to block this.
+                // if Q+, do not perform requested operation if these two operations are not set. If
+                // <P, perform these operations on phone 0 (for compatibility purposes, since we
+                // used to not wait for the result of this activity).
                 isSmsSimPickActivityNeeded = iSms.isSmsSimPickActivityNeeded(subId);
             }
         } catch (RemoteException ex) {
-            Log.e(TAG, "Exception in getSubscriptionId");
+            Log.e(TAG, "resolveSubscriptionForOperation", ex);
         }
-
-        if (isSmsSimPickActivityNeeded) {
-            Log.d(TAG, "getSubscriptionId isSmsSimPickActivityNeeded is true");
-            // ask the user for a default SMS SIM.
-            Intent intent = new Intent();
-            intent.setClassName("com.android.settings",
-                    "com.android.settings.sim.SimDialogActivity");
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            intent.putExtra(DIALOG_TYPE_KEY, SMS_PICK);
-            try {
-                context.startActivity(intent);
-            } catch (ActivityNotFoundException anfe) {
-                // If Settings is not installed, only log the error as we do not want to break
-                // legacy applications.
-                Log.e(TAG, "Unable to launch Settings application.");
-            }
+        if (!isSmsSimPickActivityNeeded) {
+            sendResolverResult(resolverResult, subId, false /*pickActivityShown*/);
+            return;
         }
-
-        return subId;
+        // We need to ask the user pick an appropriate subid for the operation.
+        Log.d(TAG, "resolveSubscriptionForOperation isSmsSimPickActivityNeeded is true for package "
+                + context.getPackageName());
+        try {
+            // Create the SMS pick activity and call back once the activity is complete. Can't do
+            // it here because we do not have access to the activity context that is performing this
+            // operation.
+            // Requires that the calling process has the SEND_SMS permission.
+            getITelephony().enqueueSmsPickResult(context.getOpPackageName(),
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int subId) {
+                            // Runs on binder thread attached to this app's process.
+                            sendResolverResult(resolverResult, subId, true /*pickActivityShown*/);
+                        }
+                    });
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Unable to launch activity", ex);
+            // pickActivityShown is true here because we want to call sendResolverResult and always
+            // have this operation fail. This is because we received a RemoteException here, which
+            // means that telephony is not available and the next operation to Telephony will fail
+            // as well anyways, so we might as well shortcut fail here first.
+            sendResolverResult(resolverResult, subId, true /*pickActivityShown*/);
+        }
     }
 
-    /**
-     * @return the subscription ID associated with this {@link SmsManager} or the default set by the
-     * user if this instance was created using {@link SmsManager#getDefault}.
-     *
-     * If there is no default set by the user, this method returns
-     * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
-     */
-    private int getSubIdOrDefault() {
-        return (mSubId == DEFAULT_SUBSCRIPTION_ID) ? getDefaultSmsSubscriptionId() : mSubId;
+    private void sendResolverResult(SubscriptionResolverResult resolverResult, int subId,
+            boolean pickActivityShown) {
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            resolverResult.onSuccess(subId);
+            return;
+        }
+
+        if (getTargetSdkVersion() <= Build.VERSION_CODES.P && !pickActivityShown) {
+            // Do not fail, return a success with an INVALID subid for apps targeting P or below
+            // that tried to perform an operation and the SMS disambiguation dialog was never shown,
+            // as these applications may not have been written to handle the failure case properly.
+            // This will resolve to performing the operation on phone 0 in telephony.
+            resolverResult.onSuccess(subId);
+        } else {
+            // Fail if the app targets Q or above or it targets P and below and the disambiguation
+            // dialog was shown and the user clicked out of it.
+            resolverResult.onFailure();
+        }
+    }
+
+    private static int getTargetSdkVersion() {
+        final Context context = ActivityThread.currentApplication().getApplicationContext();
+        int targetSdk;
+        try {
+            targetSdk = context.getPackageManager().getApplicationInfo(
+                    context.getOpPackageName(), 0).targetSdkVersion;
+        } catch (PackageManager.NameNotFoundException e) {
+            // Default to old behavior if we can not find this.
+            targetSdk = -1;
+        }
+        return targetSdk;
+    }
+
+    private static ITelephony getITelephony() {
+        ITelephony binder = ITelephony.Stub.asInterface(
+                ServiceManager.getService(Context.TELEPHONY_SERVICE));
+        if (binder == null) {
+            throw new RuntimeException("Could not find Telephony Service.");
+        }
+        return binder;
+    }
+
+    private static void notifySmsErrorNoDefaultSet(Context context, PendingIntent pendingIntent) {
+        if (pendingIntent != null) {
+            Intent errorMessage = new Intent();
+            errorMessage.putExtra(NO_DEFAULT_EXTRA, true);
+            try {
+                pendingIntent.send(context, RESULT_ERROR_GENERIC_FAILURE, errorMessage);
+            } catch (PendingIntent.CanceledException e) {
+                // Don't worry about it, we do not need to notify the caller if this is the case.
+            }
+        }
+    }
+
+    private static void notifySmsErrorNoDefaultSet(Context context,
+            List<PendingIntent> pendingIntents) {
+        if (pendingIntents != null) {
+            for (PendingIntent pendingIntent : pendingIntents) {
+                Intent errorMessage = new Intent();
+                errorMessage.putExtra(NO_DEFAULT_EXTRA, true);
+                try {
+                    pendingIntent.send(context, RESULT_ERROR_GENERIC_FAILURE, errorMessage);
+                } catch (PendingIntent.CanceledException e) {
+                    // Don't worry about it, we do not need to notify the caller if this is the
+                    // case.
+                }
+            }
+        }
+    }
+
+    private static void notifySmsGenericError(PendingIntent pendingIntent) {
+        if (pendingIntent != null) {
+            try {
+                pendingIntent.send(RESULT_ERROR_GENERIC_FAILURE);
+            } catch (PendingIntent.CanceledException e) {
+                // Don't worry about it, we do not need to notify the caller if this is the case.
+            }
+        }
+    }
+
+    private static void notifySmsGenericError(List<PendingIntent> pendingIntents) {
+        if (pendingIntents != null) {
+            for (PendingIntent pendingIntent : pendingIntents) {
+                try {
+                    pendingIntent.send(RESULT_ERROR_GENERIC_FAILURE);
+                } catch (PendingIntent.CanceledException e) {
+                    // Don't worry about it, we do not need to notify the caller if this is the
+                    // case.
+                }
+            }
+        }
     }
 
     /**
@@ -1026,6 +1490,16 @@
      * ICC (Integrated Circuit Card) is the card of the device.
      * For example, this can be the SIM or USIM for GSM.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @param smsc the SMSC for this message, or NULL for the default SMSC
      * @param pdu the raw PDU to store
      * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
@@ -1061,6 +1535,16 @@
      * ICC (Integrated Circuit Card) is the card of the device.
      * For example, this can be the SIM or USIM for GSM.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @param messageIndex is the record index of the message on ICC
      * @return true for success
      *
@@ -1070,15 +1554,13 @@
     public boolean
     deleteMessageFromIcc(int messageIndex) {
         boolean success = false;
-        byte[] pdu = new byte[SMS_RECORD_LENGTH-1];
-        Arrays.fill(pdu, (byte)0xff);
 
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
                 success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
                         ActivityThread.currentPackageName(),
-                        messageIndex, STATUS_ON_ICC_FREE, pdu);
+                        messageIndex, STATUS_ON_ICC_FREE, null /* pdu */);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -1092,6 +1574,16 @@
      * ICC (Integrated Circuit Card) is the card of the device.
      * For example, this can be the SIM or USIM for GSM.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @param messageIndex record index of message to update
      * @param newStatus new message status (STATUS_ON_ICC_READ,
      *                  STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
@@ -1124,6 +1616,16 @@
      * ICC (Integrated Circuit Card) is the card of the device.
      * For example, this can be the SIM or USIM for GSM.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @return <code>ArrayList</code> of <code>SmsMessage</code> objects
      *
      * {@hide}
@@ -1156,24 +1658,34 @@
      * Note: This call is blocking, callers may want to avoid calling it from
      * the main thread of an application.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+     * @param ranType the message format as defined in {@link SmsCbMessage]
      * @return true if successful, false otherwise
      * @see #disableCellBroadcast(int, int)
      *
      * {@hide}
      */
-    public boolean enableCellBroadcast(int messageIdentifier, int ranType) {
+    public boolean enableCellBroadcast(int messageIdentifier,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
-                // If getSubIdOrDefault() returns INVALID, we will use the default phone internally.
-                success = iSms.enableCellBroadcastForSubscriber(getSubIdOrDefault(),
+                // If getSubscriptionId() returns INVALID or an inactive subscription, we will use
+                // the default phone internally.
+                success = iSms.enableCellBroadcastForSubscriber(getSubscriptionId(),
                         messageIdentifier, ranType);
             }
         } catch (RemoteException ex) {
@@ -1192,25 +1704,35 @@
      * Note: This call is blocking, callers may want to avoid calling it from
      * the main thread of an application.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+     * @param ranType the message format as defined in {@link SmsCbMessage}
      * @return true if successful, false otherwise
      *
      * @see #enableCellBroadcast(int, int)
      *
      * {@hide}
      */
-    public boolean disableCellBroadcast(int messageIdentifier, int ranType) {
+    public boolean disableCellBroadcast(int messageIdentifier,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
-                // If getSubIdOrDefault() returns INVALID, we will use the default phone internally.
-                success = iSms.disableCellBroadcastForSubscriber(getSubIdOrDefault(),
+                // If getSubscriptionId() returns INVALID or an inactive subscription, we will use
+                // the default phone internally.
+                success = iSms.disableCellBroadcastForSubscriber(getSubscriptionId(),
                         messageIdentifier, ranType);
             }
         } catch (RemoteException ex) {
@@ -1222,29 +1744,42 @@
 
     /**
      * Enable reception of cell broadcast (SMS-CB) messages with the given
-     * message identifier range and RAN type. The RAN type specify this message ID
-     * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable
+     * message identifier range and RAN type. The RAN type specifies if this message ID
+     * belongs to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable
      * the same message identifier, they must both disable it for the device to stop
      * receiving those messages. All received messages will be broadcast in an
      * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
      * Note: This call is blocking, callers may want to avoid calling it from
      * the main thread of an application.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}, which will result in the operation
+     * being completed on the subscription associated with logical slot 0. Use
+     * {@link #getSmsManagerForSubscriptionId(int)} to ensure the operation is performed on the
+     * correct subscription.
+     * </p>
+     *
+     * <p>Requires {@link android.Manifest.permission#RECEIVE_EMERGENCY_BROADCAST}</p>
+     *
      * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
      * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
-     * @return true if successful, false otherwise
+     * @param ranType the message format as defined in {@link SmsCbMessage}
+     * @return true if successful, false if the modem reports a failure (e.g. the given range or
+     * RAN type is invalid).
      * @see #disableCellBroadcastRange(int, int, int)
      *
      * @throws IllegalArgumentException if endMessageId < startMessageId
      * {@hide}
      */
-    @UnsupportedAppUsage
-    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+    @SystemApi
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         if (endMessageId < startMessageId) {
@@ -1253,8 +1788,9 @@
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
-                // If getSubIdOrDefault() returns INVALID, we will use the default phone internally.
-                success = iSms.enableCellBroadcastRangeForSubscriber(getSubIdOrDefault(),
+                // If getSubscriptionId() returns INVALID or an inactive subscription, we will use
+                // the default phone internally.
+                success = iSms.enableCellBroadcastRangeForSubscriber(getSubscriptionId(),
                         startMessageId, endMessageId, ranType);
             }
         } catch (RemoteException ex) {
@@ -1273,22 +1809,34 @@
      * Note: This call is blocking, callers may want to avoid calling it from
      * the main thread of an application.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
+     * <p>Requires {@link android.Manifest.permission#RECEIVE_EMERGENCY_BROADCAST}</p>
+     *
      * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
      * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
-     * @return true if successful, false otherwise
+     * @param ranType the message format as defined in {@link SmsCbMessage}
+     * @return true if successful, false if the modem reports a failure (e.g. the given range or
+     * RAN type is invalid).
      *
      * @see #enableCellBroadcastRange(int, int, int)
      *
      * @throws IllegalArgumentException if endMessageId < startMessageId
      * {@hide}
      */
-    @UnsupportedAppUsage
-    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+    @SystemApi
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         if (endMessageId < startMessageId) {
@@ -1297,8 +1845,9 @@
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
-                // If getSubIdOrDefault() returns INVALID, we will use the default phone internally.
-                success = iSms.disableCellBroadcastRangeForSubscriber(getSubIdOrDefault(),
+                // If getSubscriptionId() returns INVALID or an inactive subscription, we will use
+                // the default phone internally.
+                success = iSms.disableCellBroadcastRangeForSubscriber(getSubscriptionId(),
                         startMessageId, endMessageId, ranType);
             }
         } catch (RemoteException ex) {
@@ -1312,6 +1861,16 @@
      * Create a list of <code>SmsMessage</code>s from a list of RawSmsData
      * records returned by <code>getAllMessagesFromIcc()</code>
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @param records SMS EF records, returned by
      *   <code>getAllMessagesFromIcc</code>
      * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
@@ -1339,6 +1898,16 @@
      * SMS over IMS is supported if IMS is registered and SMS is supported
      * on IMS.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @return true if SMS over IMS is supported, false otherwise
      *
      * @see #getImsSmsFormat()
@@ -1359,8 +1928,17 @@
     }
 
     /**
-     * Gets SMS format supported on IMS.  SMS over IMS format is
-     * either 3GPP or 3GPP2.
+     * Gets SMS format supported on IMS.  SMS over IMS format is either 3GPP or 3GPP2.
+     *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
      *
      * @return SmsMessage.FORMAT_3GPP,
      *         SmsMessage.FORMAT_3GPP2
@@ -1384,19 +1962,24 @@
     }
 
     /**
-     * Get default sms subscription id
+     * Get default sms subscription id.
      *
-     * @return the default SMS subscription id
+     * <p class="note"><strong>Note:</strong>This returns a value different from
+     * {@link SubscriptionManager#getDefaultSmsSubscriptionId} if the user has not chosen a default.
+     * In this case it returns the active subscription id if there's only one active subscription
+     * available.
+     *
+     * @return the user-defined default SMS subscription id, or the active subscription id if
+     * there's only one active subscription available, otherwise
+     * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
      */
     public static int getDefaultSmsSubscriptionId() {
-        ISms iSms = null;
         try {
-            iSms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
-            return iSms.getPreferredSmsSubscription();
-        } catch (RemoteException ex) {
-            return -1;
-        } catch (NullPointerException ex) {
-            return -1;
+            return getISmsService().getPreferredSmsSubscription();
+        } catch (RemoteException e) {
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        } catch (NullPointerException e) {
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
     }
 
@@ -1567,6 +2150,15 @@
     /**
      * Send an MMS message
      *
+     * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @param context application context
      * @param contentUri the content Uri from which the message pdu will be read
      * @param locationUrl the optional location url where message should be sent to
@@ -1597,6 +2189,15 @@
     /**
      * Download an MMS message from carrier by a given location URL
      *
+     * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @param context application context
      * @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
      *  from the MMS WAP push notification
@@ -1620,9 +2221,8 @@
             if (iMms == null) {
                 return;
             }
-            iMms.downloadMessage(
-                    getSubscriptionId(), ActivityThread.currentPackageName(), locationUrl,
-                    contentUri, configOverrides, downloadedIntent);
+            iMms.downloadMessage(getSubscriptionId(), ActivityThread.currentPackageName(),
+                    locationUrl, contentUri, configOverrides, downloadedIntent);
         } catch (RemoteException e) {
             // Ignore it
         }
@@ -1860,6 +2460,15 @@
      *
      * You can only send a failed text message or a draft text message.
      *
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail sending the SMS message because no
+     * suitable default subscription could be found. In this case, if {@code sentIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the
+     * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions
+     * where this operation may fail.
+     * </p>
+     *
      * @param messageUri the URI of the stored message
      * @param scAddress is the service center address or null to use the current default SMSC
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
@@ -1887,14 +2496,25 @@
         if (messageUri == null) {
             throw new IllegalArgumentException("Empty message URI");
         }
-        try {
-            ISms iSms = getISmsServiceOrThrow();
-            iSms.sendStoredText(
-                    getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
-                    scAddress, sentIntent, deliveryIntent);
-        } catch (RemoteException ex) {
-            // ignore it
-        }
+        final Context context = ActivityThread.currentApplication().getApplicationContext();
+        resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+            @Override
+            public void onSuccess(int subId) {
+                try {
+                    ISms iSms = getISmsServiceOrThrow();
+                    iSms.sendStoredText(subId, ActivityThread.currentPackageName(), messageUri,
+                            scAddress, sentIntent, deliveryIntent);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "sendStoredTextMessage: Couldn't send SMS - Exception: "
+                            + e.getMessage());
+                    notifySmsGenericError(sentIntent);
+                }
+            }
+            @Override
+            public void onFailure() {
+                notifySmsErrorNoDefaultSet(context, sentIntent);
+            }
+        });
     }
 
     /**
@@ -1904,6 +2524,15 @@
      * The provided <code>PendingIntent</code> lists should match the part number of the
      * divided text of the stored message by using <code>divideMessage</code>
      *
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail sending the SMS message because no
+     * suitable default subscription could be found. In this case, if {@code sentIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the
+     * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions
+     * where this operation may fail.
+     * </p>
+     *
      * @param messageUri the URI of the stored message
      * @param scAddress is the service center address or null to use
      *   the current default SMSC
@@ -1935,14 +2564,25 @@
         if (messageUri == null) {
             throw new IllegalArgumentException("Empty message URI");
         }
-        try {
-            ISms iSms = getISmsServiceOrThrow();
-            iSms.sendStoredMultipartText(
-                    getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
-                    scAddress, sentIntents, deliveryIntents);
-        } catch (RemoteException ex) {
-            // ignore it
-        }
+        final Context context = ActivityThread.currentApplication().getApplicationContext();
+        resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+            @Override
+            public void onSuccess(int subId) {
+                try {
+                    ISms iSms = getISmsServiceOrThrow();
+                    iSms.sendStoredMultipartText(subId, ActivityThread.currentPackageName(),
+                            messageUri, scAddress, sentIntents, deliveryIntents);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "sendStoredTextMessage: Couldn't send SMS - Exception: "
+                            + e.getMessage());
+                    notifySmsGenericError(sentIntents);
+                }
+            }
+            @Override
+            public void onFailure() {
+                notifySmsErrorNoDefaultSet(context, sentIntents);
+            }
+        });
     }
 
     /**
@@ -1951,6 +2591,15 @@
      * This is used for sending a previously sent, but failed-to-send, message or
      * for sending a text message that has been stored as a draft.
      *
+     * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @param messageUri the URI of the stored message
      * @param configOverrides the carrier-specific messaging configuration values to override for
      *  sending the message.
@@ -2024,6 +2673,16 @@
     /**
      * Get carrier-dependent configuration values.
      *
+     * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
+     * applications or the Telephony framework and will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @return bundle key/values pairs of configuration values
      */
     public Bundle getCarrierConfigValues() {
@@ -2039,7 +2698,7 @@
     }
 
     /**
-     * Create a single use app specific incoming SMS request for the the calling package.
+     * Create a single use app specific incoming SMS request for the calling package.
      *
      * This method returns a token that if included in a subsequent incoming SMS message will cause
      * {@code intent} to be sent with the SMS data.
@@ -2050,6 +2709,15 @@
      * An app can only have one request at a time, if the app already has a request pending it will
      * be replaced with a new request.
      *
+     * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
+     * dialog. If this method is called on a device that has multiple active subscriptions, this
+     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+     * default subscription is defined, the subscription ID associated with this message will be
+     * INVALID, which will result in the operation being completed on the subscription associated
+     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
+     * operation is performed on the correct subscription.
+     * </p>
+     *
      * @return Token to include in an SMS message. The token will be 11 characters long.
      * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent
      */
@@ -2135,4 +2803,84 @@
         return filtered;
     }
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SMS_CATEGORY_"},
+            value = {
+                    SmsManager.SMS_CATEGORY_NOT_SHORT_CODE,
+                    SmsManager.SMS_CATEGORY_FREE_SHORT_CODE,
+                    SmsManager.SMS_CATEGORY_STANDARD_SHORT_CODE,
+                    SmsManager.SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE,
+                    SmsManager.SMS_CATEGORY_PREMIUM_SHORT_CODE})
+    public @interface SmsShortCodeCategory {}
+
+    /**
+     * Return value from {@link #checkSmsShortCodeDestination(String, String)} ()} for regular
+     * phone numbers.
+     * @hide
+     */
+    @TestApi
+    public static final int SMS_CATEGORY_NOT_SHORT_CODE = 0;
+    /**
+     * Return value from {@link #checkSmsShortCodeDestination(String, String)} ()} for free
+     * (no cost) short codes.
+     * @hide
+     */
+    @TestApi
+    public static final int SMS_CATEGORY_FREE_SHORT_CODE = 1;
+    /**
+     * Return value from {@link #checkSmsShortCodeDestination(String, String)} ()} for
+     * standard rate (non-premium)
+     * short codes.
+     * @hide
+     */
+    @TestApi
+    public static final int SMS_CATEGORY_STANDARD_SHORT_CODE = 2;
+    /**
+     * Return value from {@link #checkSmsShortCodeDestination(String, String)} ()} for possible
+     * premium short codes.
+     * @hide
+     */
+    @TestApi
+    public static final int SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3;
+    /**
+     * Return value from {@link #checkSmsShortCodeDestination(String, String)} ()} for
+     * premium short codes.
+     * @hide
+     */
+    @TestApi
+    public static final int SMS_CATEGORY_PREMIUM_SHORT_CODE = 4;
+
+    /**
+     * Check if the destination address is a possible premium short code.
+     * NOTE: the caller is expected to strip non-digits from the destination number with
+     * {@link PhoneNumberUtils#extractNetworkPortion} before calling this method.
+     *
+     * @param destAddress the destination address to test for possible short code
+     * @param countryIso the ISO country code
+     *
+     * @return
+     * {@link SmsManager#SMS_CATEGORY_NOT_SHORT_CODE},
+     * {@link SmsManager#SMS_CATEGORY_FREE_SHORT_CODE},
+     * {@link SmsManager#SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE},
+     * {@link SmsManager#SMS_CATEGORY_PREMIUM_SHORT_CODE}, or
+     * {@link SmsManager#SMS_CATEGORY_STANDARD_SHORT_CODE}
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @TestApi
+    public @SmsShortCodeCategory int checkSmsShortCodeDestination(
+            String destAddress, String countryIso) {
+        try {
+            ISms iccISms = getISmsServiceOrThrow();
+            if (iccISms != null) {
+                return iccISms.checkSmsShortCodeDestination(getSubscriptionId(),
+                        ActivityThread.currentPackageName(), destAddress, countryIso);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "checkSmsShortCodeDestination() RemoteException", e);
+        }
+        return SmsManager.SMS_CATEGORY_NOT_SHORT_CODE;
+    }
 }
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index 7d4bcb7..b705d71 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -843,20 +843,16 @@
     }
 
     /**
-     * GSM:
-     * For an SMS-STATUS-REPORT message, this returns the status field from
-     * the status report.  This field indicates the status of a previously
-     * submitted SMS, if requested.  See TS 23.040, 9.2.3.15 TP-Status for a
-     * description of values.
-     * CDMA:
-     * For not interfering with status codes from GSM, the value is
-     * shifted to the bits 31-16.
-     * The value is composed of an error class (bits 25-24) and a status code (bits 23-16).
-     * Possible codes are described in C.S0015-B, v2.0, 4.5.21.
+     * GSM: For an SMS-STATUS-REPORT message, this returns the status field from the status report.
+     * This field indicates the status of a previously submitted SMS, if requested.
+     * See TS 23.040, 9.2.3.15 TP-Status for a description of values.
+     * CDMA: For not interfering with status codes from GSM, the value is shifted to the bits 31-16.
+     * The value is composed of an error class (bits 25-24) and a status code (bits 23-16). Possible
+     * codes are described in C.S0015-B, v2.0, 4.5.21.
      *
-     * @return 0 indicates the previously sent message was received.
-     *         See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21
-     *         for a description of other possible values.
+     * @return 0 for GSM or 2 shifted left by 16 for CDMA indicates the previously sent message was
+     *         received. See TS 23.040, 9.2.3.15 and C.S0015-B, v2.0, 4.5.21 for a description of
+     *         other possible values.
      */
     public int getStatus() {
         return mWrappedSmsMessage.getStatus();
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 70471d7..f2a8233 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -731,19 +731,19 @@
     public String toString() {
         String iccIdToPrint = givePrintableIccid(mIccId);
         String cardStringToPrint = givePrintableIccid(mCardString);
-        return "{id=" + mId + ", iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex
+        return "{id=" + mId + " iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex
                 + " carrierId=" + mCarrierId + " displayName=" + mDisplayName
                 + " carrierName=" + mCarrierName + " nameSource=" + mNameSource
-                + " iconTint=" + mIconTint + " mNumber=" + mNumber
-                + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
-                + " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded
-                + " accessRules " + Arrays.toString(mAccessRules)
+                + " iconTint=" + mIconTint + " mNumber=" + Rlog.pii(Build.IS_DEBUGGABLE, mNumber)
+                + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc=" + mMcc
+                + " mnc=" + mMnc + " mCountryIso=" + mCountryIso + " isEmbedded=" + mIsEmbedded
+                + " accessRules=" + Arrays.toString(mAccessRules)
                 + " cardString=" + cardStringToPrint + " cardId=" + mCardId
-                + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
+                + " isOpportunistic=" + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
                 + " mIsGroupDisabled=" + mIsGroupDisabled
                 + " profileClass=" + mProfileClass
-                + " ehplmns = " + Arrays.toString(mEhplmns)
-                + " hplmns = " + Arrays.toString(mHplmns)
+                + " ehplmns=" + Arrays.toString(mEhplmns)
+                + " hplmns=" + Arrays.toString(mHplmns)
                 + " subscriptionType=" + mSubscriptionType
                 + " mGroupOwner=" + mGroupOwner + "}";
     }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index addd9e0..519a954 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -302,11 +302,27 @@
      * subscription.
      *
      * Default value is 0.
+     *
+     * @deprecated Replaced by {@link #DATA_ENABLED_OVERRIDE_RULES}
+     * @hide
      */
-    /** @hide */
+    @Deprecated
     public static final String WHITE_LISTED_APN_DATA = "white_listed_apn_data";
 
     /**
+     * TelephonyProvider column name data_enabled_override_rules.
+     * It's a list of rules for overriding data enabled settings. The syntax is
+     * For example, "mms=nonDefault" indicates enabling data for mms in non-default subscription.
+     * "default=nonDefault&inVoiceCall" indicates enabling data for internet in non-default
+     * subscription and while is in voice call.
+     *
+     * Default value is empty string.
+     *
+     * @hide
+     */
+    public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules";
+
+    /**
      * This constant is to designate a subscription as a Local-SIM Subscription.
      * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on the
      * device.
@@ -775,6 +791,14 @@
     public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
 
     /**
+     * IMSI (International Mobile Subscriber Identity).
+     * <P>Type: TEXT </P>
+     * @hide
+     */
+    //TODO: add @SystemApi
+    public static final String IMSI = "imsi";
+
+    /**
      * Broadcast Action: The user has changed one of the default subs related to
      * data, phone calls, or sms</p>
      *
@@ -2249,14 +2273,19 @@
     }
 
     /**
-     * Returns the resources associated with Subscription.
+     * Returns the {@link Resources} from the given {@link Context} for the MCC/MNC associated with
+     * the subscription. If the subscription ID is invalid, the base resources are returned instead.
+     *
+     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
      * @param context Context object
-     * @param subId Subscription Id of Subscription who's resources are required
+     * @param subId Subscription Id of Subscription whose resources are required
      * @return Resources associated with Subscription.
      * @hide
      */
-    @UnsupportedAppUsage
-    public static Resources getResourcesForSubId(Context context, int subId) {
+    @NonNull
+    @SystemApi
+    public static Resources getResourcesForSubId(@NonNull Context context, int subId) {
         return getResourcesForSubId(context, subId, false);
     }
 
@@ -3054,18 +3083,10 @@
     }
 
     /**
-     * Returns whether the subscription is enabled or not. This is different from activated
-     * or deactivated for two aspects. 1) For when user disables a physical subscription, we
-     * actually disable the modem because we can't switch off the subscription. 2) For eSIM,
-     * user may enable one subscription but the system may activate another temporarily. In this
-     * case, user enabled one is different from current active one.
-
-     * @param subscriptionId The subscription it asks about.
-     * @return whether it's enabled or not. {@code true} if user set this subscription enabled
-     * earlier, or user never set subscription enable / disable on this slot explicitly, and
-     * this subscription is currently active. Otherwise, it returns {@code false}.
-     *
+     * DO NOT USE.
+     * This API is designed for features that are not finished at this point. Do not call this API.
      * @hide
+     * TODO b/135547512: further clean up
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -3083,14 +3104,10 @@
     }
 
     /**
-     * Get which subscription is enabled on this slot. See {@link #isSubscriptionEnabled(int)}
-     * for more details.
-     *
-     * @param slotIndex which slot it asks about.
-     * @return which subscription is enabled on this slot. If there's no enabled subscription
-     *         in this slot, it will return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
-     *
+     * DO NOT USE.
+     * This API is designed for features that are not finished at this point. Do not call this API.
      * @hide
+     * TODO b/135547512: further clean up
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -3159,4 +3176,24 @@
 
         return result;
     }
+
+    /**
+     * Get active data subscription id.
+     * See {@link PhoneStateListener#onActiveDataSubscriptionIdChanged(int)} for the details.
+     *
+     * @return Active data subscription id
+     *
+     * //TODO: Refactor this API in b/134702460
+     * @hide
+     */
+    public static int getActiveDataSubscriptionId() {
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                return iSub.getActiveDataSubscriptionId();
+            }
+        } catch (RemoteException ex) {
+        }
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
 }
diff --git a/telephony/java/android/telephony/SubscriptionPlan.java b/telephony/java/android/telephony/SubscriptionPlan.java
index d67169c..98291a0 100644
--- a/telephony/java/android/telephony/SubscriptionPlan.java
+++ b/telephony/java/android/telephony/SubscriptionPlan.java
@@ -131,7 +131,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj instanceof SubscriptionPlan) {
             final SubscriptionPlan other = (SubscriptionPlan) obj;
             return Objects.equals(cycleRule, other.cycleRule)
diff --git a/telephony/java/android/telephony/TelephonyHistogram.java b/telephony/java/android/telephony/TelephonyHistogram.java
index e1c3d7b..19cd2c3 100644
--- a/telephony/java/android/telephony/TelephonyHistogram.java
+++ b/telephony/java/android/telephony/TelephonyHistogram.java
@@ -15,13 +15,12 @@
  */
 package android.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
 
 /**
  * Parcelable class to store Telephony histogram.
@@ -238,6 +237,8 @@
         }
     }
 
+    @NonNull
+    @Override
     public String toString() {
         String basic = " Histogram id = " + mId + " Time(ms): min = " + mMinTimeMs + " max = "
                 + mMaxTimeMs + " avg = " + mAverageTimeMs + " Count = " + mSampleCount;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5731646..5bdfde5 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -93,6 +93,8 @@
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.TelephonyProperties;
 
+import dalvik.system.VMRuntime;
+
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -103,6 +105,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.regex.Matcher;
@@ -1484,6 +1487,48 @@
      */
     public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL = 4;
 
+    /**
+     * Integer intent extra to be used with {@link #ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED}
+     * to indicate if the SIM combination in DSDS has limitation or compatible issue.
+     * e.g. two CDMA SIMs may disrupt each other's voice call in certain scenarios.
+     *
+     * @hide
+     */
+    public static final String EXTRA_SIM_COMBINATION_WARNING_TYPE =
+            "android.telephony.extra.SIM_COMBINATION_WARNING_TYPE";
+
+    /** @hide */
+    @IntDef({
+            EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE,
+            EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SimCombinationWarningType{}
+
+    /**
+     * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE}
+     * to indicate there's no SIM combination warning.
+     * @hide
+     */
+    public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE = 0;
+
+    /**
+     * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE}
+     * to indicate two active SIMs are both CDMA hence there might be functional limitation.
+     * @hide
+     */
+    public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA = 1;
+
+    /**
+     * String intent extra to be used with {@link #ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED}
+     * to indicate what's the name of SIM combination it has limitation or compatible issue.
+     * e.g. two CDMA SIMs may disrupt each other's voice call in certain scenarios, and the
+     * name will be "operator1 & operator2".
+     *
+     * @hide
+     */
+    public static final String EXTRA_SIM_COMBINATION_NAMES =
+            "android.telephony.extra.SIM_COMBINATION_NAMES";
     //
     //
     // Device Info
@@ -2209,7 +2254,7 @@
     @UnsupportedAppUsage
     public String getNetworkOperatorForPhone(int phoneId) {
         return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
-     }
+    }
 
 
     /**
@@ -2282,6 +2327,10 @@
      * <p>
      * The ISO-3166 country code is provided in lowercase 2 character format.
      * <p>
+     * Note: In multi-sim, this returns a shared emergency network country iso from other
+     * subscription if the subscription used to create the TelephonyManager doesn't camp on
+     * a network due to some reason (e.g. pin/puk locked), or sim is absent in the corresponding
+     * slot.
      * Note: Result may be unreliable on CDMA networks (use {@link #getPhoneType()} to determine
      * if on a CDMA network).
      * <p>
@@ -2409,24 +2458,15 @@
     public @interface NetworkType{}
 
     /**
+     * Return the current data network type.
+     *
+     * @deprecated use {@link #getDataNetworkType()}
      * @return the NETWORK_TYPE_xxxx for current data connection.
      */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public @NetworkType int getNetworkType() {
-       try {
-           ITelephony telephony = getITelephony();
-           if (telephony != null) {
-               return telephony.getNetworkType();
-            } else {
-                // This can happen when the ITelephony interface is not up yet.
-                return NETWORK_TYPE_UNKNOWN;
-            }
-        } catch(RemoteException ex) {
-            // This shouldn't happen in the normal case
-            return NETWORK_TYPE_UNKNOWN;
-        } catch (NullPointerException ex) {
-            // This could happen before phone restarts due to crashing
-            return NETWORK_TYPE_UNKNOWN;
-        }
+        return getNetworkType(getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
     }
 
     /**
@@ -2457,7 +2497,7 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getNetworkType(int subId) {
         try {
             ITelephony telephony = getITelephony();
@@ -2509,7 +2549,7 @@
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public @NetworkType int getDataNetworkType() {
-        return getDataNetworkType(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
+        return getDataNetworkType(getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
     }
 
     /**
@@ -4741,7 +4781,8 @@
             ITelephony telephony = getITelephony();
             if (telephony == null)
                 return DATA_ACTIVITY_NONE;
-            return telephony.getDataActivity();
+            return telephony.getDataActivityForSubId(
+                    getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return DATA_ACTIVITY_NONE;
@@ -4789,7 +4830,8 @@
             ITelephony telephony = getITelephony();
             if (telephony == null)
                 return DATA_DISCONNECTED;
-            return telephony.getDataState();
+            return telephony.getDataStateForSubId(
+                    getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return DATA_DISCONNECTED;
@@ -5231,18 +5273,30 @@
      * Returns the MMS user agent.
      */
     public String getMmsUserAgent() {
-        if (mContext == null) return null;
-        return mContext.getResources().getString(
-                com.android.internal.R.string.config_mms_user_agent);
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getMmsUserAgent(getSubId());
+            }
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return null;
     }
 
     /**
      * Returns the MMS user agent profile URL.
      */
     public String getMmsUAProfUrl() {
-        if (mContext == null) return null;
-        return mContext.getResources().getString(
-                com.android.internal.R.string.config_mms_user_agent_profile_url);
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getMmsUAProfUrl(getSubId());
+            }
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return null;
     }
 
     /**
@@ -7241,7 +7295,7 @@
      * @hide
      */
     public boolean getTetherApnRequired() {
-        return getTetherApnRequired(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
+        return getTetherApnRequired(getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
     }
 
     /**
@@ -8007,7 +8061,7 @@
             ITelephony telephony = getITelephony();
             if (telephony != null)
                 return telephony.isDataConnectivityPossible(getSubId(SubscriptionManager
-                        .getDefaultDataSubscriptionId()));
+                        .getActiveDataSubscriptionId()));
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#isDataAllowed", e);
         }
@@ -8245,9 +8299,8 @@
             ITelephony telephony = getITelephony();
             if (telephony != null)
                 retVal = telephony.isUserDataEnabled(subId);
-        } catch (RemoteException e) {
+        } catch (RemoteException | NullPointerException e) {
             Log.e(TAG, "Error calling ITelephony#isUserDataEnabled", e);
-        } catch (NullPointerException e) {
         }
         return retVal;
     }
@@ -9121,6 +9174,10 @@
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
+        } catch (NullPointerException e) {
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+                    "getServiceStateForSubscriber " + subId + " NPE");
         }
         return null;
     }
@@ -10869,24 +10926,28 @@
     }
 
     /**
-     * Get whether reboot is required or not after making changes to modem configurations.
+     * Get whether making changes to modem configurations by {@link #switchMultiSimConfig(int)} will
+     * trigger device reboot.
      * The modem configuration change refers to switching from single SIM configuration to DSDS
      * or the other way around.
-     * @Return {@code true} if reboot is required after making changes to modem configurations,
-     * otherwise return {@code false}.
      *
-     * @hide
+     *  <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or that the
+     * calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *
+     * @return {@code true} if reboot will be triggered after making changes to modem
+     * configurations, otherwise return {@code false}.
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public boolean isRebootRequiredForModemConfigChange() {
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+    public boolean doesSwitchMultiSimConfigTriggerReboot() {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                return service.isRebootRequiredForModemConfigChange();
+                return service.doesSwitchMultiSimConfigTriggerReboot(getSubId(),
+                        getOpPackageName());
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "isRebootRequiredForModemConfigChange RemoteException", e);
+            Log.e(TAG, "doesSwitchMultiSimConfigTriggerReboot RemoteException", e);
         }
         return false;
     }
@@ -10968,4 +11029,52 @@
         }
         return true;
     }
+
+    /**
+     * Set allowing mobile data during voice call.
+     *
+     * @param allow {@code true} if allowing using data during voice call, {@code false} if
+     * disallowed
+     *
+     * @return {@code false} if the setting is changed.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public boolean setDataAllowedDuringVoiceCall(boolean allow) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.setDataAllowedDuringVoiceCall(getSubId(), allow);
+            }
+        } catch (RemoteException ex) {
+            if (!isSystemProcess()) {
+                ex.rethrowAsRuntimeException();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check whether data is allowed during voice call. Note this is for dual sim device that
+     * data might be disabled on non-default data subscription but explicitly turned on by settings.
+     *
+     * @return {@code true} if data is allowed during voice call.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isDataAllowedInVoiceCall() {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.isDataAllowedInVoiceCall(getSubId());
+            }
+        } catch (RemoteException ex) {
+            if (!isSystemProcess()) {
+                ex.rethrowAsRuntimeException();
+            }
+        }
+        return false;
+    }
 }
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index d8836dc..fce76b2 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -15,6 +15,7 @@
  */
 package android.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.pm.PackageInfo;
@@ -213,7 +214,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -236,6 +237,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " +
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 2bc6775..8260b48 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -16,6 +16,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -179,7 +181,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -210,6 +212,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "UiccSlotInfo (mIsActive="
diff --git a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
index 010ad2b..5f2f75d 100644
--- a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
+++ b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
@@ -16,6 +16,7 @@
 
 package android.telephony.cdma;
 
+import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -28,7 +29,7 @@
  *
  * {@hide}
  */
-public class CdmaSmsCbProgramData implements Parcelable {
+public final class CdmaSmsCbProgramData implements Parcelable {
 
     /** Delete the specified service category from the list of enabled categories. */
     public static final int OPERATION_DELETE_CATEGORY   = 0;
@@ -95,7 +96,7 @@
 
     /** Create a new CdmaSmsCbProgramData object with the specified values. */
     public CdmaSmsCbProgramData(int operation, int category, int language, int maxMessages,
-            int alertOption, String categoryName) {
+            int alertOption, @NonNull String categoryName) {
         mOperation = operation;
         mCategory = category;
         mLanguage = language;
@@ -174,6 +175,7 @@
      * Returns the service category name, in the language specified by {@link #getLanguage()}.
      * @return an optional service category name
      */
+    @NonNull
     public String getCategoryName() {
         return mCategoryName;
     }
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index f0c819d..041e909 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1310,6 +1310,9 @@
      * @hide
      */
     public static String getApnTypeString(int apnType) {
+        if (apnType == TYPE_ALL) {
+            return "*";
+        }
         String apnTypeString = APN_TYPE_INT_MAP.get(apnType);
         return apnTypeString == null ? "Unknown" : apnTypeString;
     }
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 3806a7e..a6aea7c 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -213,6 +213,7 @@
      */
     public int getMtu() { return mMtu; }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuffer sb = new StringBuffer();
@@ -233,7 +234,7 @@
     }
 
     @Override
-    public boolean equals (Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
 
         if (!(o instanceof DataCallResponse)) {
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index c53ade1..0d79ec9 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -257,6 +257,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DataProfile=" + mProfileId + "/" + mProtocolType + "/" + mAuthType
@@ -303,7 +304,7 @@
     };
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         DataProfile that = (DataProfile) o;
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 5d8d793..11dc78a 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -27,7 +27,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
 import java.util.List;
 
 /**
@@ -42,6 +41,8 @@
 
     private static final String TAG = DataServiceCallback.class.getSimpleName();
 
+    private static final boolean DBG = true;
+
     /**
      * Result of data requests
      * @hide
@@ -62,11 +63,11 @@
     /** Request sent in illegal state */
     public static final int RESULT_ERROR_ILLEGAL_STATE  = 4;
 
-    private final WeakReference<IDataServiceCallback> mCallback;
+    private final IDataServiceCallback mCallback;
 
     /** @hide */
     public DataServiceCallback(IDataServiceCallback callback) {
-        mCallback = new WeakReference<>(callback);
+        mCallback = callback;
     }
 
     /**
@@ -78,13 +79,15 @@
      */
     public void onSetupDataCallComplete(@ResultCode int result,
                                         @Nullable DataCallResponse response) {
-        IDataServiceCallback callback = mCallback.get();
-        if (callback != null) {
+        if (mCallback != null) {
             try {
-                callback.onSetupDataCallComplete(result, response);
+                if (DBG) Rlog.d(TAG, "onSetupDataCallComplete");
+                mCallback.onSetupDataCallComplete(result, response);
             } catch (RemoteException e) {
                 Rlog.e(TAG, "Failed to onSetupDataCallComplete on the remote");
             }
+        } else {
+            Rlog.e(TAG, "onSetupDataCallComplete: callback is null!");
         }
     }
 
@@ -95,13 +98,15 @@
      * @param result The result code. Must be one of the {@link ResultCode}.
      */
     public void onDeactivateDataCallComplete(@ResultCode int result) {
-        IDataServiceCallback callback = mCallback.get();
-        if (callback != null) {
+        if (mCallback != null) {
             try {
-                callback.onDeactivateDataCallComplete(result);
+                if (DBG) Rlog.d(TAG, "onDeactivateDataCallComplete");
+                mCallback.onDeactivateDataCallComplete(result);
             } catch (RemoteException e) {
                 Rlog.e(TAG, "Failed to onDeactivateDataCallComplete on the remote");
             }
+        } else {
+            Rlog.e(TAG, "onDeactivateDataCallComplete: callback is null!");
         }
     }
 
@@ -112,13 +117,14 @@
      * @param result The result code. Must be one of the {@link ResultCode}.
      */
     public void onSetInitialAttachApnComplete(@ResultCode int result) {
-        IDataServiceCallback callback = mCallback.get();
-        if (callback != null) {
+        if (mCallback != null) {
             try {
-                callback.onSetInitialAttachApnComplete(result);
+                mCallback.onSetInitialAttachApnComplete(result);
             } catch (RemoteException e) {
                 Rlog.e(TAG, "Failed to onSetInitialAttachApnComplete on the remote");
             }
+        } else {
+            Rlog.e(TAG, "onSetInitialAttachApnComplete: callback is null!");
         }
     }
 
@@ -129,13 +135,14 @@
      * @param result The result code. Must be one of the {@link ResultCode}.
      */
     public void onSetDataProfileComplete(@ResultCode int result) {
-        IDataServiceCallback callback = mCallback.get();
-        if (callback != null) {
+        if (mCallback != null) {
             try {
-                callback.onSetDataProfileComplete(result);
+                mCallback.onSetDataProfileComplete(result);
             } catch (RemoteException e) {
                 Rlog.e(TAG, "Failed to onSetDataProfileComplete on the remote");
             }
+        } else {
+            Rlog.e(TAG, "onSetDataProfileComplete: callback is null!");
         }
     }
 
@@ -149,13 +156,14 @@
      */
     public void onRequestDataCallListComplete(@ResultCode int result,
                                               @NonNull List<DataCallResponse> dataCallList) {
-        IDataServiceCallback callback = mCallback.get();
-        if (callback != null) {
+        if (mCallback != null) {
             try {
-                callback.onRequestDataCallListComplete(result, dataCallList);
+                mCallback.onRequestDataCallListComplete(result, dataCallList);
             } catch (RemoteException e) {
                 Rlog.e(TAG, "Failed to onRequestDataCallListComplete on the remote");
             }
+        } else {
+            Rlog.e(TAG, "onRequestDataCallListComplete: callback is null!");
         }
     }
 
@@ -166,13 +174,15 @@
      * @param dataCallList List of the current active data connection.
      */
     public void onDataCallListChanged(@NonNull List<DataCallResponse> dataCallList) {
-        IDataServiceCallback callback = mCallback.get();
-        if (callback != null) {
+        if (mCallback != null) {
             try {
-                callback.onDataCallListChanged(dataCallList);
+                if (DBG) Rlog.d(TAG, "onDataCallListChanged");
+                mCallback.onDataCallListChanged(dataCallList);
             } catch (RemoteException e) {
                 Rlog.e(TAG, "Failed to onDataCallListChanged on the remote");
             }
+        } else {
+            Rlog.e(TAG, "onDataCallListChanged: callback is null!");
         }
     }
 }
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index b144ff76..733fb1e 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -22,6 +22,7 @@
 import android.hardware.radio.V1_4.EmergencyServiceCategory;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.Rlog;
 
@@ -299,7 +300,10 @@
      * Get the dialing number of the emergency number.
      *
      * The character in the number string is only the dial pad
-     * character('0'-'9', '*', or '#'). For example: 911.
+     * character('0'-'9', '*', '+', or '#'). For example: 911.
+     *
+     * If the number starts with carrier prefix, the carrier prefix is configured in
+     * {@link CarrierConfigManager#KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY}.
      *
      * @return the dialing number.
      */
diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.java b/telephony/java/android/telephony/euicc/EuiccNotification.java
index 43a7707..d79084c 100644
--- a/telephony/java/android/telephony/euicc/EuiccNotification.java
+++ b/telephony/java/android/telephony/euicc/EuiccNotification.java
@@ -16,6 +16,7 @@
 package android.telephony.euicc;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
@@ -107,7 +108,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -132,6 +133,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "EuiccNotification (seq="
diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
index 67ae983..ee4d750 100644
--- a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
+++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
@@ -16,6 +16,7 @@
 package android.telephony.euicc;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -28,7 +29,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -204,7 +204,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
diff --git a/telephony/java/android/telephony/ims/ImsCallForwardInfo.java b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
index 70daa8b..f18398d 100644
--- a/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
+++ b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
@@ -185,6 +185,7 @@
         out.writeInt(mServiceClass);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return super.toString() + ", Condition: " + mCondition
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index fd58f7e..e1c3aba 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -534,7 +534,7 @@
         mMediaProfile = profile.mMediaProfile;
     }
 
-
+    @NonNull
     @Override
     public String toString() {
         return "{ serviceType=" + mServiceType
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 8af8cff..0c0bc76 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -176,6 +177,7 @@
         return Call.STATE_ACTIVE;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/telephony/java/android/telephony/ims/ImsException.java b/telephony/java/android/telephony/ims/ImsException.java
index 8c686f7..6187e67 100644
--- a/telephony/java/android/telephony/ims/ImsException.java
+++ b/telephony/java/android/telephony/ims/ImsException.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.pm.PackageManager;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 
 import java.lang.annotation.Retention;
@@ -38,11 +39,11 @@
      */
     public static final int CODE_ERROR_UNSPECIFIED = 0;
     /**
-     * The operation has failed because there is no {@link ImsService} available to service it. This
-     * may be due to an {@link ImsService} crash or other illegal state.
+     * The operation has failed because there is no remote process available to service it. This
+     * may be due to a process crash or other illegal state.
      * <p>
      * This is a temporary error and the operation may be retried until the connection to the
-     * {@link ImsService} is restored.
+     * remote process is restored.
      */
     public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1;
 
@@ -55,12 +56,23 @@
      */
     public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2;
 
+    /**
+     * The subscription ID associated with this operation is invalid or not active.
+     * <p>
+     * This is a configuration error and there should be no retry. The subscription used for this
+     * operation is either invalid or has become inactive. The active subscriptions can be queried
+     * with {@link SubscriptionManager#getActiveSubscriptionInfoList()}.
+     * @hide
+     */
+    public static final int CODE_ERROR_INVALID_SUBSCRIPTION = 3;
+
     /**@hide*/
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "CODE_ERROR_", value = {
             CODE_ERROR_UNSPECIFIED,
             CODE_ERROR_SERVICE_UNAVAILABLE,
-            CODE_ERROR_UNSUPPORTED_OPERATION
+            CODE_ERROR_UNSUPPORTED_OPERATION,
+            CODE_ERROR_INVALID_SUBSCRIPTION
     })
     public @interface ImsErrorCode {}
 
diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java
index c56915d..a199d8a 100644
--- a/telephony/java/android/telephony/ims/ImsExternalCallState.java
+++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java
@@ -213,6 +213,7 @@
         return mIsHeld;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ImsExternalCallState { mCallId = " + mCallId +
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index be58723..a1a7fcc 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -31,6 +31,7 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.SubscriptionManager;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
@@ -375,6 +376,13 @@
         c.setExecutor(executor);
         try {
             getITelephony().registerImsRegistrationCallback(mSubId, c.getBinder());
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException | IllegalStateException e) {
             throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
@@ -390,8 +398,6 @@
      * @param c The {@link RegistrationCallback} to be removed.
      * @see SubscriptionManager.OnSubscriptionsChangedListener
      * @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
-     * @throws IllegalArgumentException if the subscription ID associated with this callback is
-     * invalid.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void unregisterImsRegistrationCallback(@NonNull RegistrationCallback c) {
@@ -445,6 +451,13 @@
         c.setExecutor(executor);
         try {
             getITelephony().registerMmTelCapabilityCallback(mSubId, c.getBinder());
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }  catch (IllegalStateException e) {
@@ -460,8 +473,6 @@
      * inactive subscription, it will result in a no-op.
      * @param c The MmTel {@link CapabilityCallback} to be removed.
      * @see #registerMmTelCapabilityCallback(Executor, CapabilityCallback)
-     * @throws IllegalArgumentException if the subscription ID associated with this callback is
-     * invalid.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void unregisterMmTelCapabilityCallback(@NonNull CapabilityCallback c) {
@@ -482,12 +493,9 @@
      * be enabled as long as the carrier has provisioned these services for the specified
      * subscription. Other IMS services (SMS/UT) are not affected by this user setting and depend on
      * carrier requirements.
-     *
-     * Modifying this value may also trigger an IMS registration or deregistration, depending on
-     * whether or not the new value is enabled or disabled.
-     *
+     * <p>
      * Note: If the carrier configuration for advanced calling is not editable or hidden, this
-     * method will do nothing and will instead always use the default value.
+     * method will always return the default value.
      *
      * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
      * @see android.telephony.CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL
@@ -495,12 +503,21 @@
      * @see android.telephony.CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL
      * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_AVAILABLE_BOOL
      * @see #setAdvancedCallingSettingEnabled(boolean)
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @return true if the user's setting for advanced calling is enabled, false otherwise.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean isAdvancedCallingSettingEnabled() {
         try {
             return getITelephony().isAdvancedCallingSettingEnabled(mSubId);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -526,12 +543,20 @@
      * @see android.telephony.CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL
      * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_AVAILABLE_BOOL
      * @see #isAdvancedCallingSettingEnabled()
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setAdvancedCallingSettingEnabled(boolean isEnabled) {
         try {
             getITelephony().setAdvancedCallingSettingEnabled(mSubId, isEnabled);
-            return;
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -597,6 +622,9 @@
 
     /**
      * The user's setting for whether or not they have enabled the "Video Calling" setting.
+     *
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @return true if the user’s “Video Calling” setting is currently enabled.
      * @see #setVtSettingEnabled(boolean)
      */
@@ -604,6 +632,13 @@
     public boolean isVtSettingEnabled() {
         try {
             return getITelephony().isVtSettingEnabled(mSubId);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -611,13 +646,22 @@
 
     /**
      * Change the user's setting for Video Telephony and enable the Video Telephony capability.
+     *
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #isVtSettingEnabled()
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVtSettingEnabled(boolean isEnabled) {
         try {
             getITelephony().setVtSettingEnabled(mSubId, isEnabled);
-            return;
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -625,12 +669,22 @@
 
     /**
      * @return true if the user's setting for Voice over WiFi is enabled and false if it is not.
+     *
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #setVoWiFiSettingEnabled(boolean)
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean isVoWiFiSettingEnabled() {
         try {
             return getITelephony().isVoWiFiSettingEnabled(mSubId);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -638,6 +692,9 @@
 
     /**
      * Sets the user's setting for whether or not Voice over WiFi is enabled.
+     *
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @param isEnabled true if the user's setting for Voice over WiFi is enabled, false otherwise=
      * @see #isVoWiFiSettingEnabled()
      */
@@ -645,13 +702,23 @@
     public void setVoWiFiSettingEnabled(boolean isEnabled) {
         try {
             getITelephony().setVoWiFiSettingEnabled(mSubId, isEnabled);
-            return;
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
     /**
+     * Returns the user's voice over WiFi roaming setting associated with the current subscription.
+     *
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @return true if the user's setting for Voice over WiFi while roaming is enabled, false
      * if disabled.
      * @see #setVoWiFiRoamingSettingEnabled(boolean)
@@ -660,6 +727,13 @@
     public boolean isVoWiFiRoamingSettingEnabled() {
         try {
             return getITelephony().isVoWiFiRoamingSettingEnabled(mSubId);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -667,15 +741,24 @@
 
     /**
      * Change the user's setting for Voice over WiFi while roaming.
+     *
      * @param isEnabled true if the user's setting for Voice over WiFi while roaming is enabled,
      *     false otherwise.
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #isVoWiFiRoamingSettingEnabled()
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVoWiFiRoamingSettingEnabled(boolean isEnabled) {
         try {
             getITelephony().setVoWiFiRoamingSettingEnabled(mSubId, isEnabled);
-            return;
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -691,19 +774,31 @@
      * - {@link #WIFI_MODE_WIFI_ONLY}
      * - {@link #WIFI_MODE_CELLULAR_PREFERRED}
      * - {@link #WIFI_MODE_WIFI_PREFERRED}
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #setVoWiFiSettingEnabled(boolean)
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVoWiFiNonPersistent(boolean isCapable, int mode) {
         try {
             getITelephony().setVoWiFiNonPersistent(mSubId, isCapable, mode);
-            return;
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
     /**
+     * Returns the user's voice over WiFi Roaming mode setting associated with the device.
+     *
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @return The Voice over WiFi Mode preference set by the user, which can be one of the
      * following:
      * - {@link #WIFI_MODE_WIFI_ONLY}
@@ -715,6 +810,13 @@
     public @WiFiCallingMode int getVoWiFiModeSetting() {
         try {
             return getITelephony().getVoWiFiModeSetting(mSubId);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -727,13 +829,21 @@
      * - {@link #WIFI_MODE_WIFI_ONLY}
      * - {@link #WIFI_MODE_CELLULAR_PREFERRED}
      * - {@link #WIFI_MODE_WIFI_PREFERRED}
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #getVoWiFiModeSetting()
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVoWiFiModeSetting(@WiFiCallingMode int mode) {
         try {
             getITelephony().setVoWiFiModeSetting(mSubId, mode);
-            return;
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -748,12 +858,21 @@
      *     - {@link #WIFI_MODE_WIFI_ONLY}
      *     - {@link #WIFI_MODE_CELLULAR_PREFERRED}
      *     - {@link #WIFI_MODE_WIFI_PREFERRED}
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #setVoWiFiRoamingSettingEnabled(boolean)
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public @WiFiCallingMode int getVoWiFiRoamingModeSetting() {
         try {
             return getITelephony().getVoWiFiRoamingModeSetting(mSubId);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -768,13 +887,21 @@
      *     - {@link #WIFI_MODE_WIFI_ONLY}
      *     - {@link #WIFI_MODE_CELLULAR_PREFERRED}
      *     - {@link #WIFI_MODE_WIFI_PREFERRED}
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #getVoWiFiRoamingModeSetting()
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVoWiFiRoamingModeSetting(@WiFiCallingMode int mode) {
         try {
             getITelephony().setVoWiFiRoamingModeSetting(mSubId, mode);
-            return;
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -787,13 +914,21 @@
      * {@link android.provider.Settings.Secure#RTT_CALLING_MODE}, which is the global user setting
      * for RTT. That value is enabled/disabled separately by the user through the Accessibility
      * settings.
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @param isEnabled if true RTT should be enabled during calls made on this subscription.
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setRttCapabilitySetting(boolean isEnabled) {
         try {
             getITelephony().setRttCapabilitySetting(mSubId, isEnabled);
-            return;
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -801,6 +936,9 @@
 
     /**
      * @return true if TTY over VoLTE is supported
+     *
+     * @throws IllegalArgumentException if the subscription associated with this operation is not
+     * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see android.telecom.TelecomManager#getCurrentTtyMode
      * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL
      */
@@ -808,6 +946,13 @@
     boolean isTtyOverVolteEnabled() {
         try {
             return getITelephony().isTtyOverVolteEnabled(mSubId);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
+                // Rethrow as runtime error to keep API compatible.
+                throw new IllegalArgumentException(e.getMessage());
+            } else {
+                throw new RuntimeException(e.getMessage());
+            }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
new file mode 100644
index 0000000..3c343dd
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -0,0 +1,234 @@
+/*
+ * 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.telephony.ims;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.os.Binder;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.aidl.IImsCapabilityCallback;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.RcsFeature;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Manager for interfacing with the framework RCS services, including the User Capability Exchange
+ * (UCE) service, as well as managing user settings.
+ *
+ * Use {@link #createForSubscriptionId(Context, int)} to create an instance of this manager.
+ * @hide
+ */
+public class ImsRcsManager {
+
+    /**
+     * Receives RCS availability status updates from the ImsService.
+     *
+     * @see #isAvailable(int)
+     * @see #registerRcsAvailabilityCallback(Executor, AvailabilityCallback)
+     * @see #unregisterRcsAvailabilityCallback(AvailabilityCallback)
+     */
+    public static class AvailabilityCallback {
+
+        private static class CapabilityBinder extends IImsCapabilityCallback.Stub {
+
+            private final AvailabilityCallback mLocalCallback;
+            private Executor mExecutor;
+
+            CapabilityBinder(AvailabilityCallback c) {
+                mLocalCallback = c;
+            }
+
+            @Override
+            public void onCapabilitiesStatusChanged(int config) {
+                if (mLocalCallback == null) return;
+
+                Binder.withCleanCallingIdentity(() ->
+                        mExecutor.execute(() -> mLocalCallback.onAvailabilityChanged(
+                                new RcsFeature.RcsImsCapabilities(config))));
+            }
+
+            @Override
+            public void onQueryCapabilityConfiguration(int capability, int radioTech,
+                    boolean isEnabled) {
+                // This is not used for public interfaces.
+            }
+
+            @Override
+            public void onChangeCapabilityConfigurationError(int capability, int radioTech,
+                    @ImsFeature.ImsCapabilityError int reason) {
+                // This is not used for public interfaces
+            }
+
+            private void setExecutor(Executor executor) {
+                mExecutor = executor;
+            }
+        }
+
+        private final CapabilityBinder mBinder = new CapabilityBinder(this);
+
+        /**
+         * The availability of the feature's capabilities has changed to either available or
+         * unavailable.
+         * <p>
+         * If unavailable, the feature does not support the capability at the current time. This may
+         * be due to network or subscription provisioning changes, such as the IMS registration
+         * being lost, network type changing, or OMA-DM provisioning updates.
+         *
+         * @param capabilities The new availability of the capabilities.
+         */
+        public void onAvailabilityChanged(RcsFeature.RcsImsCapabilities capabilities) {
+        }
+
+        /**@hide*/
+        public final IImsCapabilityCallback getBinder() {
+            return mBinder;
+        }
+
+        private void setExecutor(Executor executor) {
+            mBinder.setExecutor(executor);
+        }
+    }
+
+    private final int mSubId;
+    private final Context mContext;
+
+
+    /**
+     * Create an instance of ImsRcsManager for the subscription id specified.
+     *
+     * @param context The context to create this ImsRcsManager instance within.
+     * @param subscriptionId The ID of the subscription that this ImsRcsManager will use.
+     * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
+     * @throws IllegalArgumentException if the subscription is invalid.
+     * @hide
+     */
+    public static ImsRcsManager createForSubscriptionId(Context context, int subscriptionId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
+            throw new IllegalArgumentException("Invalid subscription ID");
+        }
+
+        return new ImsRcsManager(context, subscriptionId);
+    }
+
+    /**
+     * Use {@link #createForSubscriptionId(Context, int)} to create an instance of this class.
+     */
+    private ImsRcsManager(Context context, int subId) {
+        mContext = context;
+        mSubId = subId;
+    }
+
+    /**
+     * Registers an {@link AvailabilityCallback} with the system, which will provide RCS
+     * availability updates for the subscription specified.
+     *
+     * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to
+     * subscription changed events and call
+     * {@link #unregisterRcsAvailabilityCallback(AvailabilityCallback)} to clean up after a
+     * subscription is removed.
+     * <p>
+     * When the callback is registered, it will initiate the callback c to be called with the
+     * current capabilities.
+     *
+     * @param executor The executor the callback events should be run on.
+     * @param c The RCS {@link AvailabilityCallback} to be registered.
+     * @see #unregisterRcsAvailabilityCallback(AvailabilityCallback)
+     * @throws ImsException if the subscription associated with this instance of
+     * {@link ImsRcsManager} is valid, but the ImsService associated with the subscription is not
+     * available. This can happen if the ImsService has crashed, for example, or if the subscription
+     * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void registerRcsAvailabilityCallback(@CallbackExecutor Executor executor,
+            @NonNull AvailabilityCallback c) throws ImsException {
+        if (c == null) {
+            throw new IllegalArgumentException("Must include a non-null AvailabilityCallback.");
+        }
+        if (executor == null) {
+            throw new IllegalArgumentException("Must include a non-null Executor.");
+        }
+        c.setExecutor(executor);
+        throw new UnsupportedOperationException("registerRcsAvailabilityCallback is not"
+                + "supported.");
+    }
+
+    /**
+     * Removes an existing RCS {@link AvailabilityCallback}.
+     * <p>
+     * When the subscription associated with this callback is removed (SIM removed, ESIM swap,
+     * etc...), this callback will automatically be unregistered. If this method is called for an
+     * inactive subscription, it will result in a no-op.
+     * @param c The RCS {@link AvailabilityCallback} to be removed.
+     * @see #registerRcsAvailabilityCallback(Executor, AvailabilityCallback)
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void unregisterRcsAvailabilityCallback(@NonNull AvailabilityCallback c) {
+        if (c == null) {
+            throw new IllegalArgumentException("Must include a non-null AvailabilityCallback.");
+        }
+        throw new UnsupportedOperationException("unregisterRcsAvailabilityCallback is not"
+                + "supported.");
+    }
+
+    /**
+     * Query for the capability of an IMS RCS service provided by the framework.
+     * <p>
+     * This only reports the status of RCS capabilities provided by the framework, not necessarily
+     * RCS capabilities provided over-the-top by applications.
+     *
+     * @param capability The RCS capability to query.
+     * @return true if the RCS capability is capable for this subscription, false otherwise. This
+     * does not necessarily mean that we are registered for IMS and the capability is available, but
+     * rather the subscription is capable of this service over IMS.
+     * @see #isAvailable(int)
+     * @see android.telephony.CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isCapable(@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) {
+        throw new UnsupportedOperationException("isCapable is not supported.");
+    }
+
+    /**
+     * Query the availability of an IMS RCS capability.
+     * <p>
+     * This only reports the status of RCS capabilities provided by the framework, not necessarily
+     * RCS capabilities provided by over-the-top by applications.
+     *
+     * @param capability the RCS capability to query.
+     * @return true if the RCS capability is currently available for the associated subscription,
+     * false otherwise. If the capability is available, IMS is registered and the service is
+     * currently available over IMS.
+     * @see #isCapable(int)
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isAvailable(@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) {
+        throw new UnsupportedOperationException("isAvailable is not supported.");
+    }
+
+    /**
+     * @return A new {@link RcsUceAdapter} used for User Capability Exchange (UCE) operations for
+     * this subscription.
+     */
+    @NonNull
+    public RcsUceAdapter getUceAdapter() {
+        return new RcsUceAdapter(mSubId);
+    }
+}
diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java
index ace3caf..2dc390d 100644
--- a/telephony/java/android/telephony/ims/ImsReasonInfo.java
+++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java
@@ -17,6 +17,7 @@
 package android.telephony.ims;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
@@ -1170,6 +1171,8 @@
     /**
      * @return the string format of {@link ImsReasonInfo}
      */
+    @NonNull
+    @Override
     public String toString() {
         return "ImsReasonInfo :: {" + mCode + ", " + mExtraCode + ", " + mExtraMessage + "}";
     }
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 0f56758..ec3838c 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -570,6 +570,8 @@
         return mCfInfo;
     }
 
+    @NonNull
+    @Override
     public String toString() {
         return "[ImsSsData] " + "ServiceType: " + getServiceType()
             + " RequestType: " + getRequestType()
diff --git a/telephony/java/android/telephony/ims/ImsSsInfo.java b/telephony/java/android/telephony/ims/ImsSsInfo.java
index fba390c..91a7503 100644
--- a/telephony/java/android/telephony/ims/ImsSsInfo.java
+++ b/telephony/java/android/telephony/ims/ImsSsInfo.java
@@ -250,8 +250,11 @@
         out.writeInt(mStatus);
         out.writeString(mIcbNum);
         out.writeInt(mProvisionStatus);
+        out.writeInt(mClirInterrogationStatus);
+        out.writeInt(mClirOutgoingState);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return super.toString() + ", Status: " + ((mStatus == 0) ? "disabled" : "enabled")
@@ -273,6 +276,8 @@
         mStatus = in.readInt();
         mIcbNum = in.readString();
         mProvisionStatus = in.readInt();
+        mClirInterrogationStatus = in.readInt();
+        mClirOutgoingState = in.readInt();
     }
 
     public static final Creator<ImsSsInfo> CREATOR =
diff --git a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
index d11a0de..4b9c251 100644
--- a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
+++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
@@ -194,6 +195,7 @@
         mRttMode = profile.mRttMode;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "{ audioQuality=" + mAudioQuality +
diff --git a/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java
index efaade8..d3014fe 100644
--- a/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java
+++ b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java
@@ -17,6 +17,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -67,6 +68,7 @@
         history = in.createStringArray();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "{ notificationType=" + notificationType +
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index cc037e3..effdf48 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -387,6 +387,24 @@
         }
     }
 
+    /**
+     * Notify the framework that an RCS autoconfiguration XML file has been received for
+     * provisioning.
+     * @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed.
+     * @param isCompressed The XML file is compressed in gzip format and must be decompressed
+     *         before being read.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
+        if (config == null) {
+            throw new IllegalArgumentException("Must include a non-null config XML file.");
+        }
+        // TODO: Connect to ImsConfigImplBase.
+        throw new UnsupportedOperationException("notifyRcsAutoConfigurationReceived is not"
+                + "supported");
+    }
+
     private static boolean isImsAvailableOnDevice() {
         IPackageManager pm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
         if (pm == null) {
diff --git a/core/java/android/service/gatekeeper/GateKeeperResponse.aidl b/telephony/java/android/telephony/ims/RcsContactUceCapability.aidl
similarity index 73%
rename from core/java/android/service/gatekeeper/GateKeeperResponse.aidl
rename to telephony/java/android/telephony/ims/RcsContactUceCapability.aidl
index 966606e..bef6a40 100644
--- a/core/java/android/service/gatekeeper/GateKeeperResponse.aidl
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,11 +14,7 @@
  * limitations under the License.
  */
 
-package android.service.gatekeeper;
 
-/**
- * Response object for a GateKeeper verification request.
- * @hide
- */
-parcelable GateKeeperResponse;
+package android.telephony.ims;
 
+parcelable RcsContactUceCapability;
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
new file mode 100644
index 0000000..492170b
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -0,0 +1,291 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Contains the User Capability Exchange capabilities corresponding to a contact's URI.
+ * @hide
+ */
+public final class RcsContactUceCapability implements Parcelable {
+
+    /** Supports 1-to-1 chat */
+    public static final int CAPABILITY_CHAT_STANDALONE = (1 << 0);
+    /** Supports group chat */
+    public static final int CAPABILITY_CHAT_SESSION = (1 << 1);
+    /** Supports full store and forward group chat information. */
+    public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = (1 << 2);
+    /**
+     * Supports file transfer via Message Session Relay Protocol (MSRP) without Store and Forward.
+     */
+    public static final int CAPABILITY_FILE_TRANSFER = (1 << 3);
+    /** Supports File Transfer Thumbnail */
+    public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = (1 << 4);
+    /** Supports File Transfer with Store and Forward */
+    public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = (1 << 5);
+    /** Supports File Transfer via HTTP */
+    public static final int CAPABILITY_FILE_TRANSFER_HTTP = (1 << 6);
+    /** Supports file transfer via SMS */
+    public static final int CAPABILITY_FILE_TRANSFER_SMS = (1 << 7);
+    /** Supports image sharing */
+    public static final int CAPABILITY_IMAGE_SHARE = (1 << 8);
+    /** Supports video sharing during a circuit-switch call (IR.74)*/
+    public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = (1 << 9);
+    /** Supports video share outside of voice call (IR.84) */
+    public static final int CAPABILITY_VIDEO_SHARE = (1 << 10);
+    /** Supports social presence information */
+    public static final int CAPABILITY_SOCIAL_PRESENCE = (1 << 11);
+    /** Supports capability discovery via presence */
+    public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = (1 << 12);
+    /** Supports IP Voice calling over LTE or IWLAN (IR.92/IR.51) */
+    public static final int CAPABILITY_IP_VOICE_CALL = (1 << 13);
+    /** Supports IP video calling (IR.94) */
+    public static final int CAPABILITY_IP_VIDEO_CALL = (1 << 14);
+    /** Supports Geolocation PUSH during 1-to-1 or multiparty chat */
+    public static final int CAPABILITY_GEOLOCATION_PUSH = (1 << 15);
+    /** Supports Geolocation PUSH via SMS for fallback.  */
+    public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = (1 << 16);
+    /** Supports Geolocation pull. */
+    public static final int CAPABILITY_GEOLOCATION_PULL = (1 << 17);
+    /** Supports Geolocation pull using file transfer support. */
+    public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = (1 << 18);
+    /** Supports RCS voice calling */
+    public static final int CAPABILITY_RCS_VOICE_CALL = (1 << 19);
+    /** Supports RCS video calling */
+    public static final int CAPABILITY_RCS_VIDEO_CALL = (1 << 20);
+    /** Supports RCS video calling, where video media can not be dropped */
+    public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = (1 << 21);
+
+    /** @hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "CAPABILITY_", flag = true, value = {
+            CAPABILITY_CHAT_STANDALONE,
+            CAPABILITY_CHAT_SESSION,
+            CAPABILITY_CHAT_SESSION_STORE_FORWARD,
+            CAPABILITY_FILE_TRANSFER,
+            CAPABILITY_FILE_TRANSFER_THUMBNAIL,
+            CAPABILITY_FILE_TRANSFER_STORE_FORWARD,
+            CAPABILITY_FILE_TRANSFER_HTTP,
+            CAPABILITY_FILE_TRANSFER_SMS,
+            CAPABILITY_IMAGE_SHARE,
+            CAPABILITY_VIDEO_SHARE_DURING_CS_CALL,
+            CAPABILITY_VIDEO_SHARE,
+            CAPABILITY_SOCIAL_PRESENCE,
+            CAPABILITY_DISCOVERY_VIA_PRESENCE,
+            CAPABILITY_IP_VOICE_CALL,
+            CAPABILITY_IP_VIDEO_CALL,
+            CAPABILITY_GEOLOCATION_PUSH,
+            CAPABILITY_GEOLOCATION_PUSH_SMS,
+            CAPABILITY_GEOLOCATION_PULL,
+            CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER,
+            CAPABILITY_RCS_VOICE_CALL,
+            CAPABILITY_RCS_VIDEO_CALL,
+            CAPABILITY_RCS_VIDEO_ONLY_CALL
+    })
+    public @interface CapabilityFlag {}
+
+    /**
+     * Builder to help construct {@link RcsContactUceCapability} instances.
+     */
+    public static class Builder {
+
+        private final RcsContactUceCapability mCapabilities;
+
+        /**
+         * Create the Builder, which can be used to set UCE capabilities as well as custom
+         * capability extensions.
+         * @param contact The contact URI that the capabilities are attached to.
+         */
+        public Builder(@NonNull Uri contact) {
+            mCapabilities = new RcsContactUceCapability(contact);
+        }
+
+        /**
+         * Add a UCE capability bit-field as well as the associated URI that the framework should
+         * use for those services. This is mainly used for capabilities that may use a URI separate
+         * from the contact's URI, for example the URI to use for VT calls.
+         * @param type The capability to map to a service URI that is different from the contact's
+         *         URI.
+         */
+        public Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
+            mCapabilities.mCapabilities |= type;
+            // Put each of these capabilities into the map separately.
+            for (int shift = 0; shift < Integer.SIZE; shift++) {
+                int cap = type & (1 << shift);
+                if (cap != 0) {
+                    mCapabilities.mServiceMap.put(cap, serviceUri);
+                    // remove that capability from the field.
+                    type &= ~cap;
+                }
+                if (type == 0) {
+                    // no need to keep going, end early.
+                    break;
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Add a UCE capability flag that this contact supports.
+         * @param type the capability that the contact supports.
+         */
+        public Builder add(@CapabilityFlag int type) {
+            mCapabilities.mCapabilities |= type;
+            return this;
+        }
+
+        /**
+         * Add a carrier specific service tag.
+         * @param extension A string containing a carrier specific service tag that is an extension
+         *         of the {@link CapabilityFlag}s that are defined here.
+         */
+        public Builder add(@NonNull String extension) {
+            mCapabilities.mExtensionTags.add(extension);
+            return this;
+        }
+
+        /**
+         * @return the constructed instance.
+         */
+        public RcsContactUceCapability build() {
+            return mCapabilities;
+        }
+    }
+
+    private final Uri mContactUri;
+    private int mCapabilities;
+    private List<String> mExtensionTags = new ArrayList<>();
+    private Map<Integer, Uri> mServiceMap = new HashMap<>();
+
+    /**
+     * Use {@link Builder} to build an instance of this interface.
+     * @param contact The URI associated with this capability information.
+     * @hide
+     */
+    RcsContactUceCapability(@NonNull Uri contact) {
+        mContactUri = contact;
+    }
+
+    private RcsContactUceCapability(Parcel in) {
+        mContactUri = in.readParcelable(Uri.class.getClassLoader());
+        mCapabilities = in.readInt();
+        in.readStringList(mExtensionTags);
+        // read mServiceMap as key,value pair
+        int mapSize = in.readInt();
+        for (int i = 0; i < mapSize; i++) {
+            mServiceMap.put(in.readInt(), in.readParcelable(Uri.class.getClassLoader()));
+        }
+    }
+
+    public static final Creator<RcsContactUceCapability> CREATOR =
+            new Creator<RcsContactUceCapability>() {
+        @Override
+        public RcsContactUceCapability createFromParcel(Parcel in) {
+            return new RcsContactUceCapability(in);
+        }
+
+        @Override
+        public RcsContactUceCapability[] newArray(int size) {
+            return new RcsContactUceCapability[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(mContactUri, 0);
+        out.writeInt(mCapabilities);
+        out.writeStringList(mExtensionTags);
+        // write mServiceMap as key,value pairs
+        int mapSize = mServiceMap.keySet().size();
+        out.writeInt(mapSize);
+        for (int key : mServiceMap.keySet()) {
+            out.writeInt(key);
+            out.writeParcelable(mServiceMap.get(key), 0);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Query for a capability
+     * @param type The capability flag to query.
+     * @return true if the capability flag specified is set, false otherwise.
+     */
+    public boolean isCapable(@CapabilityFlag int type) {
+        return (mCapabilities & type) > 0;
+    }
+
+    /**
+     * @return true if the extension service tag is set, false otherwise.
+     */
+    public boolean isCapable(@NonNull String extensionTag) {
+        return mExtensionTags.contains(extensionTag);
+    }
+
+    /**
+     * @return An immutable list containing all of the extension tags that have been set as capable.
+     * @throws UnsupportedOperationException if this list is modified.
+     */
+    public @NonNull List<String> getCapableExtensionTags() {
+        return Collections.unmodifiableList(mExtensionTags);
+    }
+
+    /**
+     * Retrieves the {@link Uri} associated with the capability being queried.
+     * <p>
+     * This will typically be the contact {@link Uri} available via {@link #getContactUri()} unless
+     * a different service {@link Uri} was associated with this capability using
+     * {@link Builder#add(int, Uri)}.
+     *
+     * @return a String containing the {@link Uri} associated with the service tag or
+     * {@code null} if this capability is not set as capable.
+     * @see #isCapable(int)
+     */
+    public @Nullable Uri getServiceUri(@CapabilityFlag int type) {
+        Uri result = mServiceMap.getOrDefault(type, null);
+        // If the capability is capable, but does not have a service URI associated, use the default
+        // contact URI.
+        if (result == null) {
+            return isCapable(type) ? getContactUri() : null;
+        }
+        return result;
+    }
+
+    /**
+     * @return the URI representing the contact associated with the capabilities.
+     */
+    public @NonNull Uri getContactUri() {
+        return mContactUri;
+    }
+}
diff --git a/telephony/java/android/telephony/ims/RcsControllerCall.java b/telephony/java/android/telephony/ims/RcsControllerCall.java
index a2d68ad..ce03c3c 100644
--- a/telephony/java/android/telephony/ims/RcsControllerCall.java
+++ b/telephony/java/android/telephony/ims/RcsControllerCall.java
@@ -19,10 +19,11 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.telephony.ims.aidl.IRcs;
+import android.telephony.ims.aidl.IRcsMessage;
 
 /**
- * A wrapper class around RPC calls that {@link RcsMessageStore} APIs to minimize boilerplate code.
+ * A wrapper class around RPC calls that {@link RcsMessageManager} APIs to minimize boilerplate
+ * code.
  *
  * @hide - not meant for public use
  */
@@ -34,13 +35,14 @@
     }
 
     <R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException {
-        IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_RCS_SERVICE));
-        if (iRcs == null) {
+        IRcsMessage iRcsMessage = IRcsMessage.Stub.asInterface(ServiceManager.getService(
+                Context.TELEPHONY_RCS_MESSAGE_SERVICE));
+        if (iRcsMessage == null) {
             throw new RcsMessageStoreException("Could not connect to RCS storage service");
         }
 
         try {
-            return serviceCall.methodOnIRcs(iRcs, mContext.getOpPackageName());
+            return serviceCall.methodOnIRcs(iRcsMessage, mContext.getOpPackageName());
         } catch (RemoteException exception) {
             throw new RcsMessageStoreException(exception.getMessage());
         }
@@ -48,17 +50,17 @@
 
     void callWithNoReturn(RcsServiceCallWithNoReturn serviceCall)
             throws RcsMessageStoreException {
-        call((iRcs, callingPackage) -> {
-            serviceCall.methodOnIRcs(iRcs, callingPackage);
+        call((iRcsMessage, callingPackage) -> {
+            serviceCall.methodOnIRcs(iRcsMessage, callingPackage);
             return null;
         });
     }
 
     interface RcsServiceCall<R> {
-        R methodOnIRcs(IRcs iRcs, String callingPackage) throws RemoteException;
+        R methodOnIRcs(IRcsMessage iRcs, String callingPackage) throws RemoteException;
     }
 
     interface RcsServiceCallWithNoReturn {
-        void methodOnIRcs(IRcs iRcs, String callingPackage) throws RemoteException;
+        void methodOnIRcs(IRcsMessage iRcs, String callingPackage) throws RemoteException;
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsManager.java b/telephony/java/android/telephony/ims/RcsManager.java
deleted file mode 100644
index 0d6ca3c..0000000
--- a/telephony/java/android/telephony/ims/RcsManager.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.telephony.ims;
-
-import android.annotation.SystemService;
-import android.content.Context;
-
-/**
- * The manager class for RCS related utilities.
- *
- * @hide
- */
-@SystemService(Context.TELEPHONY_RCS_SERVICE)
-public class RcsManager {
-    private final RcsMessageStore mRcsMessageStore;
-
-    /**
-     * @hide
-     */
-    public RcsManager(Context context) {
-        mRcsMessageStore = new RcsMessageStore(context);
-    }
-
-    /**
-     * Returns an instance of {@link RcsMessageStore}
-     */
-    public RcsMessageStore getRcsMessageStore() {
-        return mRcsMessageStore;
-    }
-}
diff --git a/telephony/java/android/telephony/ims/RcsMessageStore.java b/telephony/java/android/telephony/ims/RcsMessageManager.java
similarity index 96%
rename from telephony/java/android/telephony/ims/RcsMessageStore.java
rename to telephony/java/android/telephony/ims/RcsMessageManager.java
index d112798..a1c7c0f 100644
--- a/telephony/java/android/telephony/ims/RcsMessageStore.java
+++ b/telephony/java/android/telephony/ims/RcsMessageManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemService;
 import android.annotation.WorkerThread;
 import android.content.Context;
 import android.net.Uri;
@@ -25,15 +26,20 @@
 import java.util.List;
 
 /**
- * RcsMessageStore is the application interface to RcsProvider and provides access methods to
+ * RcsMessageManager is the application interface to RcsProvider and provides access methods to
  * RCS related database tables.
  *
  * @hide
  */
-public class RcsMessageStore {
+@SystemService(Context.TELEPHONY_RCS_MESSAGE_SERVICE)
+public class RcsMessageManager {
     RcsControllerCall mRcsControllerCall;
 
-    RcsMessageStore(Context context) {
+    /**
+     * Use {@link Context#getSystemService(String)} to get an instance of this service.
+     * @hide
+     */
+    public RcsMessageManager(Context context) {
         mRcsControllerCall = new RcsControllerCall(context);
     }
 
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
new file mode 100644
index 0000000..a6a7a84
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -0,0 +1,276 @@
+/*
+ * 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.telephony.ims;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.net.Uri;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Manages RCS User Capability Exchange for the subscription specified.
+ *
+ * @see ImsRcsManager#getUceAdapter() for information on creating an instance of this class.
+ * @hide
+ */
+public class RcsUceAdapter {
+
+    /**
+     * An unknown error has caused the request to fail.
+     */
+    public static final int ERROR_GENERIC_FAILURE = 1;
+    /**
+     * The carrier network does not have UCE support enabled for this subscriber.
+     */
+    public static final int ERROR_NOT_ENABLED = 2;
+    /**
+     * The data network that the device is connected to does not support UCE currently (e.g. it is
+     * 1x only currently).
+     */
+    public static final int ERROR_NOT_AVAILABLE = 3;
+    /**
+     * The network has responded with SIP 403 error and a reason "User not registered."
+     */
+    public static final int ERROR_NOT_REGISTERED = 4;
+    /**
+     * The network has responded to this request with a SIP 403 error and reason "not authorized for
+     * presence" for this subscriber.
+     */
+    public static final int ERROR_NOT_AUTHORIZED = 5;
+    /**
+     * The network has responded to this request with a SIP 403 error and no reason.
+     */
+    public static final int ERROR_FORBIDDEN = 6;
+    /**
+     * The contact URI requested is not provisioned for VoLTE or it is not known as an IMS
+     * subscriber to the carrier network.
+     */
+    public static final int ERROR_NOT_FOUND = 7;
+    /**
+     * The capabilities request contained too many URIs for the carrier network to handle. Retry
+     * with a lower number of contact numbers. The number varies per carrier.
+     */
+    // TODO: Try to integrate this into the API so that the service will split based on carrier.
+    public static final int ERROR_REQUEST_TOO_LARGE = 8;
+    /**
+     * The network did not respond to the capabilities request before the request timed out.
+     */
+    public static final int ERROR_REQUEST_TIMEOUT = 10;
+    /**
+     * The request failed due to the service having insufficient memory.
+     */
+    public static final int ERROR_INSUFFICIENT_MEMORY = 11;
+    /**
+     * The network was lost while trying to complete the request.
+     */
+    public static final int ERROR_LOST_NETWORK = 12;
+    /**
+     * The request has failed because the same request has already been added to the queue.
+     */
+    public static final int ERROR_ALREADY_IN_QUEUE = 13;
+
+    /**@hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "ERROR_", value = {
+            ERROR_GENERIC_FAILURE,
+            ERROR_NOT_ENABLED,
+            ERROR_NOT_AVAILABLE,
+            ERROR_NOT_REGISTERED,
+            ERROR_NOT_AUTHORIZED,
+            ERROR_FORBIDDEN,
+            ERROR_NOT_FOUND,
+            ERROR_REQUEST_TOO_LARGE,
+            ERROR_REQUEST_TIMEOUT,
+            ERROR_INSUFFICIENT_MEMORY,
+            ERROR_LOST_NETWORK,
+            ERROR_ALREADY_IN_QUEUE
+    })
+    public @interface ErrorCode {}
+
+    /**
+     * The last publish has resulted in a "200 OK" response or the device is using SIP OPTIONS for
+     * UCE.
+     */
+    public static final int PUBLISH_STATE_200_OK = 1;
+
+    /**
+     * The hasn't published its capabilities since boot or hasn't gotten any publish response yet.
+     */
+    public static final int PUBLISH_STATE_NOT_PUBLISHED = 2;
+
+    /**
+     * The device has tried to publish its capabilities, which has resulted in an error. This error
+     * is related to the fact that the device is not VoLTE provisioned.
+     */
+    public static final int PUBLISH_STATE_VOLTE_PROVISION_ERROR = 3;
+
+    /**
+     * The device has tried to publish its capabilities, which has resulted in an error. This error
+     * is related to the fact that the device is not RCS or UCE provisioned.
+     */
+    public static final int PUBLISH_STATE_RCS_PROVISION_ERROR = 4;
+
+    /**
+     * The last publish resulted in a "408 Request Timeout" response.
+     */
+    public static final int PUBLISH_STATE_REQUEST_TIMEOUT = 5;
+
+    /**
+     * The last publish resulted in another unknown error, such as SIP 503 - "Service Unavailable"
+     * or SIP 423 - "Interval too short".
+     * <p>
+     * Device shall retry with exponential back-off.
+     */
+    public static final int PUBLISH_STATE_OTHER_ERROR = 6;
+
+    /**@hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "PUBLISH_STATE_", value = {
+            PUBLISH_STATE_200_OK,
+            PUBLISH_STATE_NOT_PUBLISHED,
+            PUBLISH_STATE_VOLTE_PROVISION_ERROR,
+            PUBLISH_STATE_RCS_PROVISION_ERROR,
+            PUBLISH_STATE_REQUEST_TIMEOUT,
+            PUBLISH_STATE_OTHER_ERROR
+    })
+    public @interface PublishState {}
+
+
+    /**
+     * Provides a one-time callback for the response to a UCE request. After this callback is called
+     * by the framework, the reference to this callback will be discarded on the service side.
+     * @see #requestCapabilities(Executor, List, CapabilitiesCallback)
+     */
+    public static class CapabilitiesCallback {
+
+        /**
+         * Notify this application that the pending capability request has returned successfully.
+         * @param contactCapabilities List of capabilities associated with each contact requested.
+         */
+        public void onCapabilitiesReceived(
+                @NonNull List<RcsContactUceCapability> contactCapabilities) {
+
+        }
+
+        /**
+         * The pending request has resulted in an error and may need to be retried, depending on the
+         * error code.
+         * @param errorCode The reason for the framework being unable to process the request.
+         */
+        public void onError(@ErrorCode int errorCode) {
+
+        }
+    }
+
+    private final int mSubId;
+
+    /**
+     * Not to be instantiated directly, use
+     * {@link ImsRcsManager#createForSubscriptionId(Context, int)} and
+     * {@link ImsRcsManager#getUceAdapter()} to instantiate this manager class.
+     */
+    RcsUceAdapter(int subId) {
+        mSubId = subId;
+    }
+
+    /**
+     * Request the User Capability Exchange capabilities for one or more contacts.
+     * <p>
+     * Be sure to check the availability of this feature using
+     * {@link ImsRcsManager#isAvailable(int)} and ensuring
+     * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
+     * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else
+     * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
+     *
+     * @param executor The executor that will be used when the request is completed and the
+     *         {@link CapabilitiesCallback} is called.
+     * @param contactNumbers A list of numbers that the capabilities are being requested for.
+     * @param c A one-time callback for when the request for capabilities completes or there is an
+     *         error processing the request.
+     * @throws ImsException if the subscription associated with this instance of
+     * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
+     * available. This can happen if the ImsService has crashed, for example, or if the subscription
+     * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void requestCapabilities(@CallbackExecutor Executor executor,
+            @NonNull List<Uri> contactNumbers,
+            @NonNull CapabilitiesCallback c) throws ImsException {
+        throw new UnsupportedOperationException("isUceSettingEnabled is not supported.");
+    }
+
+    /**
+     * Gets the last publish result from the UCE service if the device is using an RCS presence
+     * server.
+     * @return The last publish result from the UCE service. If the device is using SIP OPTIONS,
+     * this method will return {@link #PUBLISH_STATE_200_OK} as well.
+     * @throws ImsException if the subscription associated with this instance of
+     * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
+     * available. This can happen if the ImsService has crashed, for example, or if the subscription
+     * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @PublishState int getUcePublishState() throws ImsException {
+        throw new UnsupportedOperationException("getPublishState is not supported.");
+    }
+
+    /**
+     * The user’s setting for whether or not Presence and User Capability Exchange (UCE) is enabled
+     * for the associated subscription.
+     *
+     * @return true if the user’s setting for UCE is enabled, false otherwise. If false,
+     * {@link ImsRcsManager#isCapable(int)} will return false for
+     * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} and
+     * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE}
+     * @see #setUceSettingEnabled(boolean)
+     * @throws ImsException if the subscription associated with this instance of
+     * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
+     * available. This can happen if the ImsService has crashed, for example, or if the subscription
+     * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isUceSettingEnabled() throws ImsException {
+        // TODO: add SubscriptionController column for this property.
+        throw new UnsupportedOperationException("isUceSettingEnabled is not supported.");
+    }
+    /**
+     * Change the user’s setting for whether or not UCE is enabled for the associated subscription.
+     * @param isEnabled the user's setting for whether or not they wish for Presence and User
+     *         Capability Exchange to be enabled. If false,
+     *         {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} and
+     *         {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} capability will be
+     *         disabled, depending on which type of UCE the carrier supports.
+     * @see #isUceSettingEnabled()
+     * @throws ImsException if the subscription associated with this instance of
+     * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
+     * available. This can happen if the ImsService has crashed, for example, or if the subscription
+     * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public void setUceSettingEnabled(boolean isEnabled) throws ImsException {
+        // TODO: add SubscriptionController column for this property.
+        throw new UnsupportedOperationException("setUceSettingEnabled is not supported.");
+    }
+}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
index 4433c1c..53e4596 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
@@ -17,6 +17,8 @@
 
 package android.telephony.ims.aidl;
 
+import android.os.PersistableBundle;
+
 import android.telephony.ims.aidl.IImsConfigCallback;
 
 import com.android.ims.ImsConfigListener;
@@ -37,4 +39,5 @@
     int setConfigInt(int item, int value);
     // Return result code defined in ImsConfig#OperationStatusConstants
     int setConfigString(int item, String value);
+    void updateImsCarrierConfigs(in PersistableBundle bundle);
 }
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl
index 691cfba..4b98b79 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl
@@ -16,10 +16,38 @@
 
 package android.telephony.ims.aidl;
 
+import android.net.Uri;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.aidl.IImsCapabilityCallback;
+import android.telephony.ims.aidl.IRcsFeatureListener;
+import android.telephony.ims.feature.CapabilityChangeRequest;
+
+import java.util.List;
+
 /**
  * See RcsFeature for more information.
  * {@hide}
  */
 interface IImsRcsFeature {
-    //Empty Default Implementation
+    // Not oneway because we need to verify this completes before doing anything else.
+    void setListener(IRcsFeatureListener listener);
+    int queryCapabilityStatus();
+    // Inherited from ImsFeature
+    int getFeatureState();
+    oneway void addCapabilityCallback(IImsCapabilityCallback c);
+    oneway void removeCapabilityCallback(IImsCapabilityCallback c);
+    oneway void changeCapabilitiesConfiguration(in CapabilityChangeRequest r,
+            IImsCapabilityCallback c);
+    oneway void queryCapabilityConfiguration(int capability, int radioTech,
+            IImsCapabilityCallback c);
+    // RcsPresenceExchangeImplBase specific api
+    oneway void requestCapabilities(in List<Uri> uris, int operationToken);
+    oneway void updateCapabilities(in RcsContactUceCapability capabilities, int operationToken);
+    // RcsSipOptionsImplBase specific api
+    oneway void sendCapabilityRequest(in Uri contactUri,
+            in RcsContactUceCapability capabilities, int operationToken);
+    oneway void respondToCapabilityRequest(in String contactUri,
+            in RcsContactUceCapability ownCapabilities, int operationToken);
+    oneway void respondToCapabilityRequestWithError(in Uri contactUri, int code, in String reason,
+            int operationToken);
 }
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl b/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
new file mode 100644
index 0000000..881b477
--- /dev/null
+++ b/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.telephony.ims.aidl;
+
+import android.net.Uri;
+import android.telephony.ims.RcsContactUceCapability;
+
+import java.util.List;
+
+/**
+ * Listener interface for updates from the RcsFeature back to the framework.
+ * {@hide}
+ */
+interface IRcsFeatureListener {
+    //RcsCapabilityExchange specific
+    oneway void onCommandUpdate(int commandCode, int operationToken);
+    // RcsPresenceExchangeImplBase Specific
+    oneway void onNetworkResponse(int code, in String reason, int operationToken);
+    oneway void onCapabilityRequestResponsePresence(in List<RcsContactUceCapability> infos,
+    int operationToken);
+    oneway void onNotifyUpdateCapabilities();
+    oneway void onUnpublish();
+    // RcsSipOptionsImplBase specific
+    oneway void onCapabilityRequestResponseOptions(int code, in String reason,
+            in RcsContactUceCapability info, int operationToken);
+    oneway void onRemoteCapabilityRequest(in Uri contactUri, in RcsContactUceCapability remoteInfo,
+            int operationToken);
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/aidl/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcsMessage.aidl
similarity index 99%
rename from telephony/java/android/telephony/ims/aidl/IRcs.aidl
rename to telephony/java/android/telephony/ims/aidl/IRcsMessage.aidl
index 9ee15da..0ae6303 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcs.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcsMessage.aidl
@@ -35,7 +35,7 @@
  * RPC definition between RCS storage APIs and phone process.
  * {@hide}
  */
-interface IRcs {
+interface IRcsMessage {
     /////////////////////////
     // RcsMessageStore APIs
     /////////////////////////
diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
index 1ee8563..7ca34fa 100644
--- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
+++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims.feature;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -98,6 +99,7 @@
             return radioTech;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "CapabilityPair{"
@@ -219,6 +221,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "CapabilityChangeRequest{"
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 74af6bf..3a9979d 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -33,10 +33,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.WeakHashMap;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Base class for all IMS features that are supported by the framework. Use a concrete subclass
@@ -50,35 +48,6 @@
     private static final String LOG_TAG = "ImsFeature";
 
     /**
-     * Action to broadcast when ImsService is up.
-     * Internal use only.
-     * Only defined here separately for compatibility purposes with the old ImsService.
-     *
-     * @hide
-     */
-    public static final String ACTION_IMS_SERVICE_UP =
-            "com.android.ims.IMS_SERVICE_UP";
-
-    /**
-     * Action to broadcast when ImsService is down.
-     * Internal use only.
-     * Only defined here separately for compatibility purposes with the old ImsService.
-     *
-     * @hide
-     */
-    public static final String ACTION_IMS_SERVICE_DOWN =
-            "com.android.ims.IMS_SERVICE_DOWN";
-
-    /**
-     * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents.
-     * A long value; the phone ID corresponding to the IMS service coming up or down.
-     * Only defined here separately for compatibility purposes with the old ImsService.
-     *
-     * @hide
-     */
-    public static final String EXTRA_PHONE_ID = "android:phone_id";
-
-    /**
      * Invalid feature value
      * @hide
      */
@@ -106,6 +75,16 @@
     public static final int FEATURE_MAX = 3;
 
     /**
+     * Used for logging purposes.
+     * @hide
+     */
+    public static final Map<Integer, String> FEATURE_LOG_MAP = new HashMap<Integer, String>() {{
+            put(FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL");
+            put(FEATURE_MMTEL, "MMTEL");
+            put(FEATURE_RCS, "RCS");
+        }};
+
+    /**
      * Integer values defining IMS features that are supported in ImsFeature.
      * @hide
      */
@@ -132,19 +111,34 @@
     public @interface ImsState {}
 
     /**
-     * This {@link ImsFeature}'s state is unavailable and should not be communicated with.
+     * This {@link ImsFeature}'s state is unavailable and should not be communicated with. This will
+     * remove all bindings back to the framework. Any attempt to communicate with the framework
+     * during this time will result in an {@link IllegalStateException}.
      */
     public static final int STATE_UNAVAILABLE = 0;
     /**
-     * This {@link ImsFeature} state is initializing and should not be communicated with.
+     * This {@link ImsFeature} state is initializing and should not be communicated with. This will
+     * remove all bindings back to the framework. Any attempt to communicate with the framework
+     * during this time will result in an {@link IllegalStateException}.
      */
     public static final int STATE_INITIALIZING = 1;
     /**
-     * This {@link ImsFeature} is ready for communication.
+     * This {@link ImsFeature} is ready for communication. Do not attempt to call framework methods
+     * until {@link #onFeatureReady()} is called.
      */
     public static final int STATE_READY = 2;
 
     /**
+     * Used for logging purposes.
+     * @hide
+     */
+    public static final Map<Integer, String> STATE_LOG_MAP = new HashMap<Integer, String>() {{
+            put(STATE_UNAVAILABLE, "UNAVAILABLE");
+            put(STATE_INITIALIZING, "INITIALIZING");
+            put(STATE_READY, "READY");
+        }};
+
+    /**
      * Integer values defining the result codes that should be returned from
      * {@link #changeEnabledCapabilities} when the framework tries to set a feature's capability.
      * @hide
@@ -208,11 +202,14 @@
 
     /**
      * Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask.
+     * <p>
+     * Typically this class is not used directly, but rather extended in subclasses of
+     * {@link ImsFeature} to provide service specific capabilities.
      * @hide
-     * @deprecated Use {@link MmTelFeature.MmTelCapabilities} instead.
      */
-    @SystemApi  // SystemApi only because it was leaked through type usage in a previous release.
+    @SystemApi
     public static class Capabilities {
+        /** @deprecated Use getters and accessors instead. */
         protected int mCapabilities = 0;
 
         /**
@@ -305,12 +302,12 @@
     /** @hide */
     protected final Object mLock = new Object();
 
-    private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap(
-            new WeakHashMap<IImsFeatureStatusCallback, Boolean>());
+    private final RemoteCallbackList<IImsFeatureStatusCallback> mStatusCallbacks =
+            new RemoteCallbackList<>();
     private @ImsState int mState = STATE_UNAVAILABLE;
     private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-    private final RemoteCallbackList<IImsCapabilityCallback> mCapabilityCallbacks
-            = new RemoteCallbackList<>();
+    private final RemoteCallbackList<IImsCapabilityCallback> mCapabilityCallbacks =
+            new RemoteCallbackList<>();
     private Capabilities mCapabilityStatus = new Capabilities();
 
     /**
@@ -322,6 +319,16 @@
     }
 
     /**
+     * @return The SIM slot index associated with this ImsFeature.
+     *
+     * @see SubscriptionManager#getSubscriptionIds(int) for more information on getting the
+     * subscription IDs associated with this slot.
+     */
+    public final int getSlotIndex() {
+        return mSlotId;
+    }
+
+    /**
      * @return The current state of the feature, defined as {@link #STATE_UNAVAILABLE},
      * {@link #STATE_INITIALIZING}, or {@link #STATE_READY}.
      * @hide
@@ -357,9 +364,7 @@
             // If we have just connected, send queued status.
             c.notifyImsFeatureStatus(getFeatureState());
             // Add the callback if the callback completes successfully without a RemoteException.
-            synchronized (mLock) {
-                mStatusCallbacks.add(c);
-            }
+            mStatusCallbacks.register(c);
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
         }
@@ -371,29 +376,21 @@
      */
     @VisibleForTesting
     public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) {
-        synchronized (mLock) {
-            mStatusCallbacks.remove(c);
-        }
+        mStatusCallbacks.unregister(c);
     }
 
     /**
      * Internal method called by ImsFeature when setFeatureState has changed.
      */
     private void notifyFeatureState(@ImsState int state) {
-        synchronized (mLock) {
-            for (Iterator<IImsFeatureStatusCallback> iter = mStatusCallbacks.iterator();
-                    iter.hasNext(); ) {
-                IImsFeatureStatusCallback callback = iter.next();
-                try {
-                    Log.i(LOG_TAG, "notifying ImsFeatureState=" + state);
-                    callback.notifyImsFeatureStatus(state);
-                } catch (RemoteException e) {
-                    // remove if the callback is no longer alive.
-                    iter.remove();
-                    Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
-                }
+        mStatusCallbacks.broadcast((c) -> {
+            try {
+                c.notifyImsFeatureStatus(state);
+            } catch (RemoteException e) {
+                Log.w(LOG_TAG, e + " notifyFeatureState() - Skipping "
+                        + "callback.");
             }
-        }
+        });
     }
 
     /**
@@ -412,10 +409,23 @@
     /**
      * @hide
      */
-    public final void removeCapabilityCallback(IImsCapabilityCallback c) {
+    final void removeCapabilityCallback(IImsCapabilityCallback c) {
         mCapabilityCallbacks.unregister(c);
     }
 
+    /**@hide*/
+    final void queryCapabilityConfigurationInternal(int capability, int radioTech,
+            IImsCapabilityCallback c) {
+        boolean enabled = queryCapabilityConfiguration(capability, radioTech);
+        try {
+            if (c != null) {
+                c.onQueryCapabilityConfiguration(capability, radioTech, enabled);
+            }
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "queryCapabilityConfigurationInternal called on dead binder!");
+        }
+    }
+
     /**
      * @return the cached capabilities status for this feature.
      * @hide
@@ -444,31 +454,36 @@
     /**
      * Called by the ImsFeature when the capabilities status has changed.
      *
-     * @param c A {@link Capabilities} containing the new Capabilities status.
+     * @param caps the new {@link Capabilities} status of the {@link ImsFeature}.
      *
      * @hide
      */
-    protected final void notifyCapabilitiesStatusChanged(Capabilities c) {
+    protected final void notifyCapabilitiesStatusChanged(Capabilities caps) {
         synchronized (mLock) {
-            mCapabilityStatus = c.copy();
+            mCapabilityStatus = caps.copy();
         }
-        int count = mCapabilityCallbacks.beginBroadcast();
-        try {
-            for (int i = 0; i < count; i++) {
-                try {
-                    mCapabilityCallbacks.getBroadcastItem(i).onCapabilitiesStatusChanged(
-                            c.mCapabilities);
-                } catch (RemoteException e) {
-                    Log.w(LOG_TAG, e + " " + "notifyCapabilitiesStatusChanged() - Skipping " +
-                            "callback.");
-                }
+        mCapabilityCallbacks.broadcast((callback) -> {
+            try {
+                callback.onCapabilitiesStatusChanged(caps.mCapabilities);
+            } catch (RemoteException e) {
+                Log.w(LOG_TAG, e + " notifyCapabilitiesStatusChanged() - Skipping "
+                        + "callback.");
             }
-        } finally {
-            mCapabilityCallbacks.finishBroadcast();
-        }
+        });
     }
 
     /**
+     * Provides the ImsFeature with the ability to return the framework Capability Configuration
+     * for a provided Capability. If the framework calls {@link #changeEnabledCapabilities} and
+     * includes a capability A to enable or disable, this method should return the correct enabled
+     * status for capability A.
+     * @param capability The capability that we are querying the configuration for.
+     * @return true if the capability is enabled, false otherwise.
+     * @hide
+     */
+    public abstract boolean queryCapabilityConfiguration(int capability, int radioTech);
+
+    /**
      * Features should override this method to receive Capability preference change requests from
      * the framework using the provided {@link CapabilityChangeRequest}. If any of the capabilities
      * in the {@link CapabilityChangeRequest} are not able to be completed due to an error,
@@ -490,7 +505,9 @@
     public abstract void onFeatureRemoved();
 
     /**
-     * Called when the feature has been initialized and communication with the framework is set up.
+     * Called after this ImsFeature has been initialized and has been set to the
+     * {@link ImsState#STATE_READY} state.
+     * <p>
      * Any attempt by this feature to access the framework before this method is called will return
      * with an {@link IllegalStateException}.
      * The IMS provider should use this method to trigger registration for this feature on the IMS
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 898ca48..20c191d 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -37,7 +37,6 @@
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsSmsImplBase;
 import android.telephony.ims.stub.ImsUtImplBase;
-import android.util.Log;
 
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsEcbm;
@@ -154,17 +153,13 @@
         @Override
         public void changeCapabilitiesConfiguration(CapabilityChangeRequest request,
                 IImsCapabilityCallback c) {
-            synchronized (mLock) {
-                MmTelFeature.this.requestChangeEnabledCapabilities(request, c);
-            }
+            MmTelFeature.this.requestChangeEnabledCapabilities(request, c);
         }
 
         @Override
         public void queryCapabilityConfiguration(int capability, int radioTech,
                 IImsCapabilityCallback c) {
-            synchronized (mLock) {
-                queryCapabilityConfigurationInternal(capability, radioTech, c);
-            }
+            queryCapabilityConfigurationInternal(capability, radioTech, c);
         }
 
         @Override
@@ -296,6 +291,7 @@
             return super.isCapable(capabilities);
         }
 
+        @NonNull
         @Override
         public String toString() {
             StringBuilder builder = new StringBuilder("MmTel Capabilities - [");
@@ -381,18 +377,6 @@
         }
     }
 
-    private void queryCapabilityConfigurationInternal(int capability, int radioTech,
-            IImsCapabilityCallback c) {
-        boolean enabled = queryCapabilityConfiguration(capability, radioTech);
-        try {
-            if (c != null) {
-                c.onQueryCapabilityConfiguration(capability, radioTech, enabled);
-            }
-        } catch (RemoteException e) {
-            Log.e(LOG_TAG, "queryCapabilityConfigurationInternal called on dead binder!");
-        }
-    }
-
     /**
      * The current capability status that this MmTelFeature has defined is available. This
      * configuration will be used by the platform to figure out which capabilities are CURRENTLY
@@ -512,6 +496,7 @@
      * @param capability The capability that we are querying the configuration for.
      * @return true if the capability is enabled, false otherwise.
      */
+    @Override
     public boolean queryCapabilityConfiguration(@MmTelCapabilities.MmTelCapability int capability,
             @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
         // Base implementation - Override to provide functionality
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index a637e16..f69b434 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -16,8 +16,32 @@
 
 package android.telephony.ims.feature;
 
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsRcsFeature;
+import android.telephony.ims.aidl.IRcsFeatureListener;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.RcsPresenceExchangeImplBase;
+import android.telephony.ims.stub.RcsSipOptionsImplBase;
+import android.util.Log;
+
+import com.android.internal.util.FunctionalUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
 
 /**
  * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend
@@ -27,23 +51,329 @@
 @SystemApi
 public class RcsFeature extends ImsFeature {
 
-    /**{@inheritDoc}*/
-    private final IImsRcsFeature mImsRcsBinder = new IImsRcsFeature.Stub() {
-        // Empty Default Implementation.
-    };
+    private static final String LOG_TAG = "RcsFeature";
 
+    private static final class RcsFeatureBinder extends IImsRcsFeature.Stub {
+        // Reference the outer class in order to have better test coverage metrics instead of
+        // creating a inner class referencing the outer class directly.
+        private final RcsFeature mReference;
+        private final Executor mExecutor;
 
-    public RcsFeature() {
-        super();
+        RcsFeatureBinder(RcsFeature classRef, @CallbackExecutor Executor executor) {
+            mReference = classRef;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void setListener(IRcsFeatureListener listener) {
+            mReference.setListener(listener);
+        }
+
+        @Override
+        public int queryCapabilityStatus() throws RemoteException {
+            return executeMethodAsyncForResult(
+                    () -> mReference.queryCapabilityStatus().mCapabilities,
+                    "queryCapabilityStatus");
+        }
+
+        @Override
+        public void addCapabilityCallback(IImsCapabilityCallback c) throws RemoteException {
+            executeMethodAsync(() -> mReference.addCapabilityCallback(c), "addCapabilityCallback");
+        }
+
+        @Override
+        public void removeCapabilityCallback(IImsCapabilityCallback c) throws RemoteException {
+            executeMethodAsync(() -> mReference.removeCapabilityCallback(c),
+                    "removeCapabilityCallback");
+        }
+
+        @Override
+        public void changeCapabilitiesConfiguration(CapabilityChangeRequest r,
+                IImsCapabilityCallback c) throws RemoteException {
+            executeMethodAsync(() -> mReference.requestChangeEnabledCapabilities(r, c),
+                    "changeCapabilitiesConfiguration");
+        }
+
+        @Override
+        public void queryCapabilityConfiguration(int capability, int radioTech,
+                IImsCapabilityCallback c) throws RemoteException {
+            executeMethodAsync(() -> mReference.queryCapabilityConfigurationInternal(capability,
+                    radioTech, c), "queryCapabilityConfiguration");
+        }
+
+        @Override
+        public int getFeatureState() throws RemoteException {
+            return executeMethodAsyncForResult(mReference::getFeatureState, "getFeatureState");
+        }
+
+        // RcsPresenceExchangeImplBase specific APIS
+        @Override
+        public void requestCapabilities(List<Uri> uris, int operationToken) throws RemoteException {
+            executeMethodAsync(() -> mReference.getPresenceExchangeInternal()
+                    .requestCapabilities(uris, operationToken), "requestCapabilities");
+        }
+        @Override
+        public void updateCapabilities(RcsContactUceCapability capabilities, int operationToken)
+                throws RemoteException {
+            executeMethodAsync(() -> mReference.getPresenceExchangeInternal()
+                            .updateCapabilities(capabilities, operationToken),
+                    "updateCapabilities");
+
+        }
+        // RcsSipOptionsImplBase specific APIS
+        @Override
+        public void sendCapabilityRequest(Uri contactUri, RcsContactUceCapability capabilities,
+                int operationToken) throws RemoteException {
+            executeMethodAsync(() -> mReference.getOptionsExchangeInternal()
+                            .sendCapabilityRequest(contactUri, capabilities, operationToken),
+                    "sendCapabilityRequest");
+
+        }
+        @Override
+        public void respondToCapabilityRequest(String contactUri,
+                RcsContactUceCapability ownCapabilities, int operationToken)
+                throws RemoteException {
+            executeMethodAsync(() -> mReference.getOptionsExchangeInternal()
+                            .respondToCapabilityRequest(contactUri, ownCapabilities,
+                                    operationToken), "respondToCapabilityRequest");
+
+        }
+        @Override
+        public void respondToCapabilityRequestWithError(Uri contactUri, int code, String reason,
+                int operationToken) throws RemoteException {
+            executeMethodAsync(() -> mReference.getOptionsExchangeInternal()
+                            .respondToCapabilityRequestWithError(contactUri, code, reason,
+                                    operationToken), "respondToCapabilityRequestWithError");
+        }
+
+        // Call the methods with a clean calling identity on the executor and wait indefinitely for
+        // the future to return.
+        private void executeMethodAsync(FunctionalUtils.ThrowingRunnable r, String errorLogName)
+                throws RemoteException {
+            // call with a clean calling identity on the executor and wait indefinitely for the
+            // future to return.
+            try {
+                CompletableFuture.runAsync(
+                        () -> Binder.withCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(LOG_TAG, "RcsFeatureBinder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        private <T> T executeMethodAsyncForResult(FunctionalUtils.ThrowingSupplier<T> r,
+                String errorLogName) throws RemoteException {
+            // call with a clean calling identity on the executor and wait indefinitely for the
+            // future to return.
+            CompletableFuture<T> future = CompletableFuture.supplyAsync(
+                    () -> Binder.withCleanCallingIdentity(r), mExecutor);
+            try {
+                return future.get();
+            } catch (ExecutionException | InterruptedException e) {
+                Log.w(LOG_TAG, "RcsFeatureBinder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                throw new RemoteException(e.getMessage());
+            }
+        }
     }
 
     /**
-     * {@inheritDoc}
+     * Contains the capabilities defined and supported by a {@link RcsFeature} in the
+     * form of a bitmask. The capabilities that are used in the RcsFeature are
+     * defined as:
+     * {@link RcsImsCapabilityFlag#CAPABILITY_TYPE_OPTIONS_UCE}
+     * {@link RcsImsCapabilityFlag#CAPABILITY_TYPE_PRESENCE_UCE}
+     *
+     * The enabled capabilities of this RcsFeature will be set by the framework
+     * using {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}.
+     * After the capabilities have been set, the RcsFeature may then perform the necessary bring up
+     * of the capability and notify the capability status as true using
+     * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the
+     * framework that the capability is available for usage.
+     * @hide
+     */
+    public static class RcsImsCapabilities extends Capabilities {
+        /** @hide*/
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = "CAPABILITY_TYPE_", flag = true, value = {
+                CAPABILITY_TYPE_OPTIONS_UCE,
+                CAPABILITY_TYPE_PRESENCE_UCE
+        })
+        public @interface RcsImsCapabilityFlag {}
+
+        /**
+         * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the
+         * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS.
+         * If not set, this RcsFeature should not service capability requests.
+         * @hide
+         */
+        public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1 << 0;
+
+        /**
+         * This carrier supports User Capability Exchange using a presence server as defined by the
+         * framework. If set, the RcsFeature should support capability exchange using a presence
+         * server. If not set, this RcsFeature should not publish capabilities or service capability
+         * requests using presence.
+         * @hide
+         */
+        public static final int CAPABILITY_TYPE_PRESENCE_UCE =  1 << 1;
+
+        /**@hide*/
+        public RcsImsCapabilities(@RcsImsCapabilityFlag int capabilities) {
+            super(capabilities);
+        }
+
+        /**@hide*/
+        private RcsImsCapabilities(Capabilities c) {
+            super(c.getMask());
+        }
+
+        /**@hide*/
+        @Override
+        public void addCapabilities(@RcsImsCapabilityFlag int capabilities) {
+            super.addCapabilities(capabilities);
+        }
+
+        /**@hide*/
+        @Override
+        public void removeCapabilities(@RcsImsCapabilityFlag int capabilities) {
+            super.removeCapabilities(capabilities);
+        }
+
+        /**@hide*/
+        @Override
+        public boolean isCapable(@RcsImsCapabilityFlag int capabilities) {
+            return super.isCapable(capabilities);
+        }
+    }
+
+    private final RcsFeatureBinder mImsRcsBinder;
+    private IRcsFeatureListener mListenerBinder;
+    private RcsPresenceExchangeImplBase mPresExchange;
+    private RcsSipOptionsImplBase mSipOptions;
+
+    /**
+     * Create a new RcsFeature.
+     * <p>
+     * Method stubs called from the framework will be called asynchronously. To specify the
+     * {@link Executor} that the methods stubs will be called, use
+     * {@link RcsFeature#RcsFeature(Executor)} instead.
+     */
+    public RcsFeature() {
+        super();
+        // Run on the Binder threads that call them.
+        mImsRcsBinder = new RcsFeatureBinder(this, Runnable::run);
+    }
+
+    /**
+     * Create a new RcsFeature using the Executor specified for methods being called by the
+     * framework.
+     * @param executor The executor for the framework to use when making calls to this service.
+     * @hide
+     */
+    public RcsFeature(@NonNull Executor executor) {
+        super();
+        if (executor == null) {
+            throw new IllegalArgumentException("executor can not be null.");
+        }
+        // Run on the Binder thread by default.
+        mImsRcsBinder = new RcsFeatureBinder(this, executor);
+    }
+
+    /**
+     * Query the current {@link RcsImsCapabilities} status set by the RcsFeature. If a capability is
+     * set, the {@link RcsFeature} has brought up the capability and is ready for framework
+     * requests. To change the status of the capabilities
+     * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called.
+     * @hide
+     */
+    @Override
+    public final RcsImsCapabilities queryCapabilityStatus() {
+        return new RcsImsCapabilities(super.queryCapabilityStatus());
+    }
+
+    /**
+     * Notify the framework that the capabilities status has changed. If a capability is enabled,
+     * this signals to the framework that the capability has been initialized and is ready.
+     * Call {@link #queryCapabilityStatus()} to return the current capability status.
+     * @hide
+     */
+    public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities c) {
+        if (c == null) {
+            throw new IllegalArgumentException("RcsImsCapabilities must be non-null!");
+        }
+        super.notifyCapabilitiesStatusChanged(c);
+    }
+
+    /**
+     * Provides the RcsFeature with the ability to return the framework capability configuration set
+     * by the framework. When the framework calls
+     * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} to
+     * enable or disable capability A, this method should return the correct configuration for
+     * capability A afterwards (until it has changed).
+     * @hide
+     */
+    public boolean queryCapabilityConfiguration(
+            @RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+            @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
+        // Base Implementation - Override to provide functionality
+        return false;
+    }
+    /**
+     * Called from the framework when the {@link RcsImsCapabilities} that have been configured for
+     * this {@link RcsFeature} has changed.
+     * <p>
+     * For each newly enabled capability flag, the corresponding capability should be brought up in
+     * the {@link RcsFeature} and registered on the network. For each newly disabled capability
+     * flag, the corresponding capability should be brought down, and deregistered. Once a new
+     * capability has been initialized and is ready for usage, the status of that capability should
+     * also be set to true using {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This
+     * will notify the framework that the capability is ready.
+     * <p>
+     * If for some reason one or more of these capabilities can not be enabled/disabled,
+     * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError(int, int, int)} should
+     * be called for each capability change that resulted in an error.
+     * @hide
      */
     @Override
     public void changeEnabledCapabilities(CapabilityChangeRequest request,
             CapabilityCallbackProxy c) {
-        // Do nothing for base implementation.
+        // Base Implementation - Override to provide functionality
+    }
+
+    /**
+     * Retrieve the implementation of SIP OPTIONS for this {@link RcsFeature}.
+     * <p>
+     * Will only be requested by the framework if capability exchange via SIP OPTIONS is
+     * configured as capable during a
+     * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}
+     * operation and the RcsFeature sets the status of the capability to true using
+     * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}.
+     *
+     * @return An instance of {@link RcsSipOptionsImplBase} that implements SIP options exchange if
+     * it is supported by the device.
+     * @hide
+     */
+    public RcsSipOptionsImplBase getOptionsExchangeImpl() {
+        // Base Implementation, override to implement functionality
+        return new RcsSipOptionsImplBase();
+    }
+
+    /**
+     * Retrieve the implementation of UCE presence for this {@link RcsFeature}.
+     * Will only be requested by the framework if presence exchang is configured as capable during
+     * a {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}
+     * operation and the RcsFeature sets the status of the capability to true using
+     * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}.
+     *
+     * @return An instance of {@link RcsPresenceExchangeImplBase} that implements presence
+     * exchange if it is supported by the device.
+     * @hide
+     */
+    public RcsPresenceExchangeImplBase getPresenceExchangeImpl() {
+        // Base Implementation, override to implement functionality.
+        return new RcsPresenceExchangeImplBase();
     }
 
     /**{@inheritDoc}*/
@@ -65,4 +395,40 @@
     public final IImsRcsFeature getBinder() {
         return mImsRcsBinder;
     }
+
+    /**@hide*/
+    public IRcsFeatureListener getListener() {
+        synchronized (mLock) {
+            return mListenerBinder;
+        }
+    }
+
+    private void setListener(IRcsFeatureListener listener) {
+        synchronized (mLock) {
+            mListenerBinder = listener;
+            if (mListenerBinder != null) {
+                onFeatureReady();
+            }
+        }
+    }
+
+    private RcsPresenceExchangeImplBase getPresenceExchangeInternal() {
+        synchronized (mLock) {
+            if (mPresExchange == null) {
+                mPresExchange = getPresenceExchangeImpl();
+                mPresExchange.initialize(this);
+            }
+            return mPresExchange;
+        }
+    }
+
+    private RcsSipOptionsImplBase getOptionsExchangeInternal() {
+        synchronized (mLock) {
+            if (mSipOptions == null) {
+                mSipOptions = getOptionsExchangeImpl();
+                mSipOptions.initialize(this);
+            }
+            return mSipOptions;
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index 321bfff..3e135cc 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.os.PersistableBundle;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.telephony.ims.aidl.IImsConfig;
@@ -182,6 +183,11 @@
             return retVal;
         }
 
+        @Override
+        public void updateImsCarrierConfigs(PersistableBundle bundle) throws RemoteException {
+            getImsConfigImpl().updateImsCarrierConfigs(bundle);
+        }
+
         private ImsConfigImplBase getImsConfigImpl() throws RemoteException {
             ImsConfigImplBase ref = mImsConfigImplBaseWeakReference.get();
             if (ref == null) {
@@ -342,6 +348,17 @@
     }
 
     /**
+     * The framework has received an RCS autoconfiguration XML file for provisioning.
+     *
+     * @param config The XML file to be read, if not compressed, it should be in ASCII/UTF8 format.
+     * @param isCompressed The XML file is compressed in gzip format and must be decompressed
+     *         before being read.
+     * @hide
+     */
+    public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) {
+    }
+
+    /**
      * Sets the configuration value for this ImsService.
      *
      * @param item an integer key.
@@ -387,4 +404,11 @@
         // Base Implementation - To be overridden.
         return null;
     }
+
+    /**
+     * @hide
+     */
+    public void updateImsCarrierConfigs(PersistableBundle bundle) {
+        // Base Implementation - Should be overridden
+    }
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java
index dfb6e2c..4e00e10 100644
--- a/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java
+++ b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java
@@ -16,12 +16,13 @@
 
 package android.telephony.ims.stub;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.ims.feature.ImsFeature;
 import android.util.ArraySet;
-import android.util.Pair;
 
 import java.util.Set;
 
@@ -61,7 +62,7 @@
         }
 
         @Override
-        public boolean equals(Object o) {
+        public boolean equals(@Nullable Object o) {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
 
@@ -78,9 +79,10 @@
             return result;
         }
 
+        @NonNull
         @Override
         public String toString() {
-            return "{s=" + slotId + ", f=" + featureType + "}";
+            return "{s=" + slotId + ", f=" + ImsFeature.FEATURE_LOG_MAP.get(featureType) + "}";
         }
     }
 
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchange.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchange.java
new file mode 100644
index 0000000..fda295a
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchange.java
@@ -0,0 +1,122 @@
+/*
+ * 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.telephony.ims.stub;
+
+import android.annotation.IntDef;
+import android.os.RemoteException;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.aidl.IRcsFeatureListener;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.RcsFeature;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base class for different types of Capability exchange, presence using
+ * {@link RcsPresenceExchangeImplBase} and SIP OPTIONS exchange using {@link RcsSipOptionsImplBase}.
+ *
+ * @hide
+ */
+public class RcsCapabilityExchange {
+
+    /**  Service is unknown. */
+    public static final int COMMAND_CODE_SERVICE_UNKNOWN = 0;
+    /** The command completed successfully. */
+    public static final int COMMAND_CODE_SUCCESS = 1;
+    /** The command failed with an unknown error. */
+    public static final int COMMAND_CODE_GENERIC_FAILURE = 2;
+    /**  Invalid parameter(s). */
+    public static final int COMMAND_CODE_INVALID_PARAM = 3;
+    /**  Fetch error. */
+    public static final int COMMAND_CODE_FETCH_ERROR = 4;
+    /**  Request timed out. */
+    public static final int COMMAND_CODE_REQUEST_TIMEOUT = 5;
+    /**  Failure due to insufficient memory available. */
+    public static final int COMMAND_CODE_INSUFFICIENT_MEMORY = 6;
+    /**  Network connection is lost. */
+    public static final int COMMAND_CODE_LOST_NETWORK_CONNECTION = 7;
+    /**  Requested feature/resource is not supported. */
+    public static final int COMMAND_CODE_NOT_SUPPORTED = 8;
+    /**  Contact or resource is not found. */
+    public static final int COMMAND_CODE_NOT_FOUND = 9;
+    /**  Service is not available. */
+    public static final int COMMAND_CODE_SERVICE_UNAVAILABLE = 10;
+    /**  No Change in Capabilities */
+    public static final int COMMAND_CODE_NO_CHANGE_IN_CAP = 11;
+
+    /** @hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "COMMAND_CODE_", value = {
+            COMMAND_CODE_SERVICE_UNKNOWN,
+            COMMAND_CODE_SUCCESS,
+            COMMAND_CODE_GENERIC_FAILURE,
+            COMMAND_CODE_INVALID_PARAM,
+            COMMAND_CODE_FETCH_ERROR,
+            COMMAND_CODE_REQUEST_TIMEOUT,
+            COMMAND_CODE_INSUFFICIENT_MEMORY,
+            COMMAND_CODE_LOST_NETWORK_CONNECTION,
+            COMMAND_CODE_NOT_SUPPORTED,
+            COMMAND_CODE_NOT_FOUND,
+            COMMAND_CODE_SERVICE_UNAVAILABLE,
+            COMMAND_CODE_NO_CHANGE_IN_CAP
+    })
+    public @interface CommandCode {}
+
+
+    private RcsFeature mFeature;
+
+    /** @hide */
+    public final void initialize(RcsFeature feature) {
+        mFeature = feature;
+    }
+
+    /** @hide */
+    protected final IRcsFeatureListener getListener() throws ImsException {
+        IRcsFeatureListener listener = mFeature.getListener();
+        if (listener == null) {
+            throw new ImsException("Connection to Framework has not been established, wait for "
+                    + "onFeatureReady().", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+        return mFeature.getListener();
+    }
+
+    /**
+     * Provides the framework with an update as to whether or not a command completed successfully
+     * locally. This includes capabilities requests and updates from the network. If it does not
+     * complete successfully, then the framework may retry the command again later, depending on the
+     * error. If the command does complete successfully, the framework will then wait for network
+     * updates.
+     *
+     * @param code The result of the pending command. If {@link #COMMAND_CODE_SUCCESS}, further
+     *             updates will be sent for this command using the associated operationToken.
+     * @param operationToken the token associated with the pending command.
+     * @throws ImsException If this {@link RcsCapabilityExchange} instance is not currently
+     * connected to the framework. This can happen if the {@link RcsFeature} is not
+     * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
+     * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the
+     * Telephony stack has crashed.
+     */
+    public final void onCommandUpdate(@CommandCode int code, int operationToken)
+            throws ImsException {
+        try {
+            getListener().onCommandUpdate(code, operationToken);
+        } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
new file mode 100644
index 0000000..055fca5
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
@@ -0,0 +1,246 @@
+/*
+ * 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.telephony.ims.stub;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.RcsFeature;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Base implementation for RCS User Capability Exchange using Presence. Any ImsService implementing
+ * this service must implement the stub methods {@link #requestCapabilities(List, int)}  and
+ * {@link #updateCapabilities(RcsContactUceCapability, int)}.
+ *
+ * @hide
+ */
+public class RcsPresenceExchangeImplBase extends RcsCapabilityExchange {
+
+    private static final String LOG_TAG = "RcsPresenceExchangeIB";
+
+    /**
+     * The request has resulted in any other 4xx/5xx/6xx that is not covered below. No retry will be
+     * attempted.
+     */
+    public static final int RESPONSE_SUBSCRIBE_GENERIC_FAILURE = -1;
+
+    /**
+     * The request has succeeded with a “200” message from the network.
+     */
+    public static final int RESPONSE_SUCCESS = 0;
+
+    /**
+     * The request has resulted in a “403” (User Not Registered) error from the network. Will retry
+     * capability polling with an exponential backoff.
+     */
+    public static final int RESPONSE_NOT_REGISTERED = 1;
+
+    /**
+     * The request has resulted in a “403” (not authorized (Requestor)) error from the network. No
+     * retry will be attempted.
+     */
+    public static final int RESPONSE_NOT_AUTHORIZED_FOR_PRESENCE = 2;
+
+    /**
+     * The request has resulted in a "403” (Forbidden) or other “403” error from the network and
+     * will be handled the same as “404” Not found. No retry will be attempted.
+     */
+    public static final int RESPONSE_FORBIDDEN = 3;
+
+    /**
+     * The request has resulted in a “404” (Not found) result from the network. No retry will be
+     * attempted.
+     */
+    public static final int RESPONSE_NOT_FOUND = 4;
+
+    /**
+     * The request has resulted in a “408” response. Retry after exponential backoff.
+     */
+    public static final int RESPONSE_SIP_REQUEST_TIMEOUT = 5;
+
+    /**
+     *  The network has responded with a “413” (Too Large) response from the network. Capability
+     *  request contains too many items and must be shrunk before the request will be accepted.
+     */
+    public static final int RESPONSE_SUBSCRIBE_TOO_LARGE = 6;
+
+    /**
+     * The request has resulted in a “423” response. Retry after exponential backoff.
+     */
+    public static final int RESPONSE_SIP_INTERVAL_TOO_SHORT = 7;
+
+    /**
+     * The request has resulted in a “503” response. Retry after exponential backoff.
+     */
+    public static final int RESPONSE_SIP_SERVICE_UNAVAILABLE = 8;
+
+    /** @hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "RESPONSE_", value = {
+            RESPONSE_SUBSCRIBE_GENERIC_FAILURE,
+            RESPONSE_SUCCESS,
+            RESPONSE_NOT_REGISTERED,
+            RESPONSE_NOT_AUTHORIZED_FOR_PRESENCE,
+            RESPONSE_FORBIDDEN,
+            RESPONSE_NOT_FOUND,
+            RESPONSE_SIP_REQUEST_TIMEOUT,
+            RESPONSE_SUBSCRIBE_TOO_LARGE,
+            RESPONSE_SIP_INTERVAL_TOO_SHORT,
+            RESPONSE_SIP_SERVICE_UNAVAILABLE
+    })
+    public @interface PresenceResponseCode {}
+
+    /**
+     * Provide the framework with a subsequent network response update to
+     * {@link #updateCapabilities(RcsContactUceCapability, int)} and
+     * {@link #requestCapabilities(List, int)} operations.
+     *
+     * @param code The SIP response code sent from the network for the operation token specified.
+     * @param reason The optional reason response from the network. If the network provided no
+     *         reason with the code, the string should be empty.
+     * @param operationToken The token associated with the operation this service is providing a
+     *         response for.
+     * @throws ImsException If this {@link RcsPresenceExchangeImplBase} instance is not currently
+     * connected to the framework. This can happen if the {@link RcsFeature} is not
+     * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
+     * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the
+     * Telephony stack has crashed.
+     */
+    public final void onNetworkResponse(@PresenceResponseCode int code, @NonNull String reason,
+            int operationToken) throws ImsException {
+        try {
+            getListener().onNetworkResponse(code, reason, operationToken);
+        } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * Provides the framework with the requested contacts’ capabilities requested by the framework
+     * using {@link #requestCapabilities(List, int)}.
+     *
+     * @throws ImsException If this {@link RcsPresenceExchangeImplBase} instance is not currently
+     * connected to the framework. This can happen if the {@link RcsFeature} is not
+     * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
+     * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the
+     * Telephony stack has crashed.
+     */
+    public final void onCapabilityRequestResponse(@NonNull List<RcsContactUceCapability> infos,
+            int operationToken) throws ImsException {
+        try {
+            getListener().onCapabilityRequestResponsePresence(infos, operationToken);
+        } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * Trigger the framework to provide a capability update using
+     * {@link #updateCapabilities(RcsContactUceCapability, int)}.
+     * <p>
+     * This is typically used when trying to generate an initial PUBLISH for a new subscription to
+     * the network. The device will cache all presence publications after boot until this method is
+     * called once.
+     * @throws ImsException If this {@link RcsPresenceExchangeImplBase} instance is not currently
+     * connected to the framework. This can happen if the {@link RcsFeature} is not
+     * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
+     * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the
+     * Telephony stack has crashed.
+     */
+    public final void onNotifyUpdateCapabilites() throws ImsException {
+        try {
+            getListener().onNotifyUpdateCapabilities();
+        } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * Notify the framework that the device’s capabilities have been unpublished from the network.
+     *
+     * @throws ImsException If this {@link RcsPresenceExchangeImplBase} instance is not currently
+     * connected to the framework. This can happen if the {@link RcsFeature} is not
+     * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
+     * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the
+     * Telephony stack has crashed.
+     */
+    public final void onUnpublish() throws ImsException {
+        try {
+            getListener().onUnpublish();
+        } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * The user capabilities of one or multiple contacts have been requested by the framework.
+     * <p>
+     * The implementer must follow up this call with an {@link #onCommandUpdate(int, int)} call to
+     * indicate whether or not this operation succeeded.  If this operation succeeds, network
+     * response updates should be sent to the framework using
+     * {@link #onNetworkResponse(int, String, int)}. When the operation is completed,
+     * {@link #onCapabilityRequestResponse(List, int)} should be called with the presence
+     * information for the contacts specified.
+     * @param uris A {@link List} of the {@link Uri}s that the framework is requesting the UCE
+     *             capabilities for.
+     * @param operationToken The token associated with this operation. Updates to this request using
+     *         {@link #onCommandUpdate(int, int)}, {@link #onNetworkResponse(int, String, int)}, and
+     *         {@link #onCapabilityRequestResponse(List, int)}  must use the same operation token
+     *         in response.
+     */
+    public void requestCapabilities(@NonNull List<Uri> uris, int operationToken) {
+        // Stub - to be implemented by service
+        Log.w(LOG_TAG, "requestCapabilities called with no implementation.");
+        try {
+            getListener().onCommandUpdate(COMMAND_CODE_NOT_SUPPORTED, operationToken);
+        } catch (RemoteException | ImsException e) {
+            // Do not do anything, this is a stub implementation.
+        }
+    }
+
+    /**
+     * The capabilities of this device have been updated and should be published to the network.
+     * <p>
+     * The implementer must follow up this call with an {@link #onCommandUpdate(int, int)} call to
+     * indicate whether or not this operation succeeded. If this operation succeeds, network
+     * response updates should be sent to the framework using
+     * {@link #onNetworkResponse(int, String, int)}.
+     * @param capabilities The capabilities for this device.
+     * @param operationToken The token associated with this operation. Any subsequent
+     *         {@link #onCommandUpdate(int, int)} or {@link #onNetworkResponse(int, String, int)}
+     *         calls regarding this update must use the same token.
+     */
+    public void updateCapabilities(@NonNull RcsContactUceCapability capabilities,
+            int operationToken) {
+        // Stub - to be implemented by service
+        Log.w(LOG_TAG, "updateCapabilities called with no implementation.");
+        try {
+            getListener().onCommandUpdate(COMMAND_CODE_NOT_SUPPORTED, operationToken);
+        } catch (RemoteException | ImsException e) {
+            // Do not do anything, this is a stub implementation.
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/ims/stub/RcsSipOptionsImplBase.java b/telephony/java/android/telephony/ims/stub/RcsSipOptionsImplBase.java
new file mode 100644
index 0000000..1c68fc0
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/RcsSipOptionsImplBase.java
@@ -0,0 +1,203 @@
+/*
+ * 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.telephony.ims.stub;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.RcsFeature;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base implementation for RCS User Capability Exchange using SIP OPTIONS.
+ *
+ * @hide
+ */
+public class RcsSipOptionsImplBase extends RcsCapabilityExchange {
+
+    private static final String LOG_TAG = "RcsSipOptionsImplBase";
+
+    /**
+     * Indicates a SIP response from the remote user other than 200, 480, 408, 404, or 604.
+     */
+    public static final int RESPONSE_GENERIC_FAILURE = -1;
+
+    /**
+     * Indicates that the remote user responded with a 200 OK response.
+     */
+    public static final int RESPONSE_SUCCESS = 0;
+
+    /**
+     * Indicates that the remote user responded with a 480 TEMPORARY UNAVAILABLE response.
+     */
+    public static final int RESPONSE_TEMPORARILY_UNAVAILABLE = 1;
+
+    /**
+     * Indicates that the remote user responded with a 408 REQUEST TIMEOUT response.
+     */
+    public static final int RESPONSE_REQUEST_TIMEOUT = 2;
+
+    /**
+     * Indicates that the remote user responded with a 404 NOT FOUND response.
+     */
+    public static final int RESPONSE_NOT_FOUND = 3;
+
+    /**
+     * Indicates that the remote user responded with a 604 DOES NOT EXIST ANYWHERE response.
+     */
+    public static final int RESPONSE_DOES_NOT_EXIST_ANYWHERE = 4;
+
+    /** @hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "RESPONSE_", value = {
+            RESPONSE_GENERIC_FAILURE,
+            RESPONSE_SUCCESS,
+            RESPONSE_TEMPORARILY_UNAVAILABLE,
+            RESPONSE_REQUEST_TIMEOUT,
+            RESPONSE_NOT_FOUND,
+            RESPONSE_DOES_NOT_EXIST_ANYWHERE
+    })
+    public @interface SipResponseCode {}
+
+    /**
+     * Send the response of a SIP OPTIONS capability exchange to the framework. If {@code code} is
+     * {@link #RESPONSE_SUCCESS}, info must be non-null.
+     * @param code The SIP response code that was sent by the network in response to the request
+     *        sent by {@link #sendCapabilityRequest(Uri, RcsContactUceCapability, int)}.
+     * @param reason The optional SIP response reason sent by the network. If none was sent, this
+     *        should be an empty string.
+     * @param info the contact's UCE capabilities associated with the capability request.
+     * @param operationToken The token associated with the original capability request, set by
+     *        {@link #sendCapabilityRequest(Uri, RcsContactUceCapability, int)}.
+     * @throws ImsException If this {@link RcsSipOptionsImplBase} instance is not currently
+     * connected to the framework. This can happen if the {@link RcsFeature} is not
+     * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
+     * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the
+     * Telephony stack has crashed.
+     */
+    public final void onCapabilityRequestResponse(@SipResponseCode int code, @NonNull String reason,
+            @Nullable RcsContactUceCapability info, int operationToken) throws ImsException {
+        try {
+            getListener().onCapabilityRequestResponseOptions(code, reason, info, operationToken);
+        } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * Inform the framework of a query for this device's UCE capabilities.
+     * <p>
+     * The framework will respond via the
+     * {@link #respondToCapabilityRequest(String, RcsContactUceCapability, int)} or
+     * {@link #respondToCapabilityRequestWithError(Uri, int, String, int)} method.
+     * @param contactUri The URI associated with the remote contact that is requesting capabilities.
+     * @param remoteInfo The remote contact's capability information.
+     * @param operationToken An unique operation token that you have generated that will be returned
+     *         by the framework in
+     *         {@link #respondToCapabilityRequest(String, RcsContactUceCapability, int)}.
+     * @throws ImsException If this {@link RcsSipOptionsImplBase} instance is not currently
+     * connected to the framework. This can happen if the {@link RcsFeature} is not
+     * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
+     * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the
+     * Telephony stack has crashed.
+     */
+    public final void onRemoteCapabilityRequest(@NonNull Uri contactUri,
+            @NonNull RcsContactUceCapability remoteInfo, int operationToken) throws ImsException {
+        try {
+            getListener().onRemoteCapabilityRequest(contactUri, remoteInfo, operationToken);
+        } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * Push one's own capabilities to a remote user via the SIP OPTIONS presence exchange mechanism
+     * in order to receive the capabilities of the remote user in response.
+     * <p>
+     * The implementer must call
+     * {@link #onCapabilityRequestResponse(int, String, RcsContactUceCapability, int)} to send the
+     * response of this query back to the framework.
+     * @param contactUri The URI of the remote user that we wish to get the capabilities of.
+     * @param capabilities The capabilities of this device to send to the remote user.
+     * @param operationToken A token generated by the framework that will be passed through
+     * {@link #onCapabilityRequestResponse(int, String, RcsContactUceCapability, int)} when this
+     *         operation has succeeded.
+     */
+    public void sendCapabilityRequest(@NonNull Uri contactUri,
+            @NonNull RcsContactUceCapability capabilities, int operationToken) {
+        // Stub - to be implemented by service
+        Log.w(LOG_TAG, "sendCapabilityRequest called with no implementation.");
+        try {
+            getListener().onCommandUpdate(COMMAND_CODE_NOT_SUPPORTED, operationToken);
+        } catch (RemoteException | ImsException e) {
+            // Do not do anything, this is a stub implementation.
+        }
+    }
+
+    /**
+     * Respond to a remote capability request from the contact specified with the capabilities of
+     * this device.
+     * <p>
+     * The framework will use the same token and uri as what was passed in to
+     * {@link #onRemoteCapabilityRequest(Uri, RcsContactUceCapability, int)}.
+     * @param contactUri The URI of the remote contact.
+     * @param ownCapabilities The capabilities of this device.
+     * @param operationToken The token generated by the framework that this service obtained when
+     *         {@link #onRemoteCapabilityRequest(Uri, RcsContactUceCapability, int)} was called.
+     */
+    public void respondToCapabilityRequest(@NonNull String contactUri,
+            @NonNull RcsContactUceCapability ownCapabilities, int operationToken) {
+        // Stub - to be implemented by service
+        Log.w(LOG_TAG, "respondToCapabilityRequest called with no implementation.");
+        try {
+            getListener().onCommandUpdate(COMMAND_CODE_NOT_SUPPORTED, operationToken);
+        } catch (RemoteException | ImsException e) {
+            // Do not do anything, this is a stub implementation.
+        }
+    }
+
+    /**
+     * Respond to a remote capability request from the contact specified with the specified error.
+     * <p>
+     * The framework will use the same token and uri as what was passed in to
+     * {@link #onRemoteCapabilityRequest(Uri, RcsContactUceCapability, int)}.
+     * @param contactUri A URI containing the remote contact.
+     * @param code The SIP response code to respond with.
+     * @param reason A non-null String containing the reason associated with the SIP code.
+     * @param operationToken The token provided by the framework when
+     *         {@link #onRemoteCapabilityRequest(Uri, RcsContactUceCapability, int)} was called.
+     *
+     */
+    public void respondToCapabilityRequestWithError(@NonNull Uri contactUri,
+            @SipResponseCode int code, @NonNull String reason, int operationToken) {
+        // Stub - to be implemented by service
+        Log.w(LOG_TAG, "respondToCapabiltyRequestWithError called with no implementation.");
+        try {
+            getListener().onCommandUpdate(COMMAND_CODE_NOT_SUPPORTED, operationToken);
+        } catch (RemoteException | ImsException e) {
+            // Do not do anything, this is a stub implementation.
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 9e3302b..1daf0eb 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -17,6 +17,7 @@
 package android.telephony.mbms;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Intent;
@@ -381,7 +382,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null) {
             return false;
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index a9f10b1..9f22d0a 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -549,4 +549,22 @@
      */
     public void onAppCallbackDied(int uid, int subscriptionId) {
     }
+
+    // Following two methods exist to workaround b/124210145
+    /** @hide */
+    @SystemApi
+    @TestApi
+    @Override
+    public android.os.IBinder asBinder() {
+        return super.asBinder();
+    }
+
+    /** @hide */
+    @SystemApi
+    @TestApi
+    @Override
+    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
+            int flags) throws RemoteException {
+        return super.onTransact(code, data, reply, flags);
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index 5ce612d..cced447 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -294,4 +294,23 @@
      */
     public void onAppCallbackDied(int uid, int subscriptionId) {
     }
+
+
+    // Following two methods exist to workaround b/124210145
+    /** @hide */
+    @SystemApi
+    @TestApi
+    @Override
+    public android.os.IBinder asBinder() {
+        return super.asBinder();
+    }
+
+    /** @hide */
+    @SystemApi
+    @TestApi
+    @Override
+    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
+            int flags) throws RemoteException {
+        return super.onTransact(code, data, reply, flags);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/CbGeoUtils.java b/telephony/java/com/android/internal/telephony/CbGeoUtils.java
new file mode 100644
index 0000000..73dd822
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/CbGeoUtils.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.Rlog;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+/**
+ * This utils class is specifically used for geo-targeting of CellBroadcast messages.
+ * The coordinates used by this utils class are latitude and longitude, but some algorithms in this
+ * class only use them as coordinates on plane, so the calculation will be inaccurate. So don't use
+ * this class for anything other then geo-targeting of cellbroadcast messages.
+ */
+public class CbGeoUtils {
+    /** Geometric interface. */
+    public interface Geometry {
+        /**
+         * Determines if the given point {@code p} is inside the geometry.
+         * @param p point in latitude, longitude format.
+         * @return {@code True} if the given point is inside the geometry.
+         */
+        boolean contains(LatLng p);
+    }
+
+    /**
+     * Tolerance for determining if the value is 0. If the absolute value of a value is less than
+     * this tolerance, it will be treated as 0.
+     */
+    public static final double EPS = 1e-7;
+
+    /** The radius of earth. */
+    public static final int EARTH_RADIUS_METER = 6371 * 1000;
+
+    private static final String TAG = "CbGeoUtils";
+
+    /** The TLV tags of WAC, defined in ATIS-0700041 5.2.3 WAC tag coding. */
+    public static final int GEO_FENCING_MAXIMUM_WAIT_TIME = 0x01;
+    public static final int GEOMETRY_TYPE_POLYGON = 0x02;
+    public static final int GEOMETRY_TYPE_CIRCLE = 0x03;
+
+    /** The identifier of geometry in the encoded string. */
+    private static final String CIRCLE_SYMBOL = "circle";
+    private static final String POLYGON_SYMBOL = "polygon";
+
+    /** Point represent by (latitude, longitude). */
+    public static class LatLng {
+        public final double lat;
+        public final double lng;
+
+        /**
+         * Constructor.
+         * @param lat latitude, range [-90, 90]
+         * @param lng longitude, range [-180, 180]
+         */
+        public LatLng(double lat, double lng) {
+            this.lat = lat;
+            this.lng = lng;
+        }
+
+        /**
+         * @param p the point use to calculate the subtraction result.
+         * @return the result of this point subtract the given point {@code p}.
+         */
+        public LatLng subtract(LatLng p) {
+            return new LatLng(lat - p.lat, lng - p.lng);
+        }
+
+        /**
+         * Calculate the distance in meter between this point and the given point {@code p}.
+         * @param p the point use to calculate the distance.
+         * @return the distance in meter.
+         */
+        public double distance(LatLng p) {
+            double dlat = Math.sin(0.5 * Math.toRadians(lat - p.lat));
+            double dlng = Math.sin(0.5 * Math.toRadians(lng - p.lng));
+            double x = dlat * dlat
+                    + dlng * dlng * Math.cos(Math.toRadians(lat)) * Math.cos(Math.toRadians(p.lat));
+            return 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1 - x)) * EARTH_RADIUS_METER;
+        }
+
+        @Override
+        public String toString() {
+            return "(" + lat + "," + lng + ")";
+        }
+    }
+
+    /**
+     * The class represents a simple polygon with at least 3 points.
+     */
+    public static class Polygon implements Geometry {
+        /**
+         * In order to reduce the loss of precision in floating point calculations, all vertices
+         * of the polygon are scaled. Set the value of scale to 1000 can take into account the
+         * actual distance accuracy of 1 meter if the EPS is 1e-7 during the calculation.
+         */
+        private static final double SCALE = 1000.0;
+
+        private final List<LatLng> mVertices;
+        private final List<Point> mScaledVertices;
+        private final LatLng mOrigin;
+
+        /**
+         * Constructs a simple polygon from the given vertices. The adjacent two vertices are
+         * connected to form an edge of the polygon. The polygon has at least 3 vertices, and the
+         * last vertices and the first vertices must be adjacent.
+         *
+         * The longitude difference in the vertices should be less than 180 degree.
+         */
+        public Polygon(@NonNull List<LatLng> vertices) {
+            mVertices = vertices;
+
+            // Find the point with smallest longitude as the mOrigin point.
+            int idx = 0;
+            for (int i = 1; i < vertices.size(); i++) {
+                if (vertices.get(i).lng < vertices.get(idx).lng) {
+                    idx = i;
+                }
+            }
+            mOrigin = vertices.get(idx);
+
+            mScaledVertices = vertices.stream()
+                    .map(latLng -> convertAndScaleLatLng(latLng))
+                    .collect(Collectors.toList());
+        }
+
+        public List<LatLng> getVertices() {
+            return mVertices;
+        }
+
+        /**
+         * Check if the given point {@code p} is inside the polygon. This method counts the number
+         * of times the polygon winds around the point P, A.K.A "winding number". The point is
+         * outside only when this "winding number" is 0.
+         *
+         * If a point is on the edge of the polygon, it is also considered to be inside the polygon.
+         */
+        @Override
+        public boolean contains(LatLng latLng) {
+            Point p = convertAndScaleLatLng(latLng);
+
+            int n = mScaledVertices.size();
+            int windingNumber = 0;
+            for (int i = 0; i < n; i++) {
+                Point a = mScaledVertices.get(i);
+                Point b = mScaledVertices.get((i + 1) % n);
+
+                // CCW is counterclockwise
+                // CCW = ab x ap
+                // CCW > 0 -> ap is on the left side of ab
+                // CCW == 0 -> ap is on the same line of ab
+                // CCW < 0 -> ap is on the right side of ab
+                int ccw = sign(crossProduct(b.subtract(a), p.subtract(a)));
+
+                if (ccw == 0) {
+                    if (Math.min(a.x, b.x) <= p.x && p.x <= Math.max(a.x, b.x)
+                            && Math.min(a.y, b.y) <= p.y && p.y <= Math.max(a.y, b.y)) {
+                        return true;
+                    }
+                } else {
+                    if (sign(a.y - p.y) <= 0) {
+                        // upward crossing
+                        if (ccw > 0 && sign(b.y - p.y) > 0) {
+                            ++windingNumber;
+                        }
+                    } else {
+                        // downward crossing
+                        if (ccw < 0 && sign(b.y - p.y) <= 0) {
+                            --windingNumber;
+                        }
+                    }
+                }
+            }
+            return windingNumber != 0;
+        }
+
+        /**
+         * Move the given point {@code latLng} to the coordinate system with {@code mOrigin} as the
+         * origin and scale it. {@code mOrigin} is selected from the vertices of a polygon, it has
+         * the smallest longitude value among all of the polygon vertices.
+         *
+         * @param latLng the point need to be converted and scaled.
+         * @Return a {@link Point} object.
+         */
+        private Point convertAndScaleLatLng(LatLng latLng) {
+            double x = latLng.lat - mOrigin.lat;
+            double y = latLng.lng - mOrigin.lng;
+
+            // If the point is in different hemispheres(western/eastern) than the mOrigin, and the
+            // edge between them cross the 180th meridian, then its relative coordinates will be
+            // extended.
+            // For example, suppose the longitude of the mOrigin is -178, and the longitude of the
+            // point to be converted is 175, then the longitude after the conversion is -8.
+            // calculation: (-178 - 8) - (-178).
+            if (sign(mOrigin.lng) != 0 && sign(mOrigin.lng) != sign(latLng.lng)) {
+                double distCross0thMeridian = Math.abs(mOrigin.lng) + Math.abs(latLng.lng);
+                if (sign(distCross0thMeridian * 2 - 360) > 0) {
+                    y = sign(mOrigin.lng) * (360 - distCross0thMeridian);
+                }
+            }
+            return new Point(x * SCALE, y * SCALE);
+        }
+
+        private static double crossProduct(Point a, Point b) {
+            return a.x * b.y - a.y * b.x;
+        }
+
+        static final class Point {
+            public final double x;
+            public final double y;
+
+            Point(double x, double y) {
+                this.x = x;
+                this.y = y;
+            }
+
+            public Point subtract(Point p) {
+                return new Point(x - p.x, y - p.y);
+            }
+        }
+    }
+
+    /** The class represents a circle. */
+    public static class Circle implements Geometry {
+        private final LatLng mCenter;
+        private final double mRadiusMeter;
+
+        public Circle(LatLng center, double radiusMeter) {
+            this.mCenter = center;
+            this.mRadiusMeter = radiusMeter;
+        }
+
+        public LatLng getCenter() {
+            return mCenter;
+        }
+
+        public double getRadius() {
+            return mRadiusMeter;
+        }
+
+        @Override
+        public boolean contains(LatLng p) {
+            return mCenter.distance(p) <= mRadiusMeter;
+        }
+    }
+
+    /**
+     * Parse the geometries from the encoded string {@code str}. The string must follow the
+     * geometry encoding specified by {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}.
+     */
+    @NonNull
+    public static List<Geometry> parseGeometriesFromString(@NonNull String str) {
+        List<Geometry> geometries = new ArrayList<>();
+        for (String geometryStr : str.split("\\s*;\\s*")) {
+            String[] geoParameters = geometryStr.split("\\s*\\|\\s*");
+            switch (geoParameters[0]) {
+                case CIRCLE_SYMBOL:
+                    geometries.add(new Circle(parseLatLngFromString(geoParameters[1]),
+                            Double.parseDouble(geoParameters[2])));
+                    break;
+                case POLYGON_SYMBOL:
+                    List<LatLng> vertices = new ArrayList<>(geoParameters.length - 1);
+                    for (int i = 1; i < geoParameters.length; i++) {
+                        vertices.add(parseLatLngFromString(geoParameters[i]));
+                    }
+                    geometries.add(new Polygon(vertices));
+                    break;
+                default:
+                    Rlog.e(TAG, "Invalid geometry format " + geometryStr);
+            }
+        }
+        return geometries;
+    }
+
+    /**
+     * Encode a list of geometry objects to string. The encoding format is specified by
+     * {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}.
+     *
+     * @param geometries the list of geometry objects need to be encoded.
+     * @return the encoded string.
+     */
+    @NonNull
+    public static String encodeGeometriesToString(@NonNull List<Geometry> geometries) {
+        return geometries.stream()
+                .map(geometry -> encodeGeometryToString(geometry))
+                .filter(encodedStr -> !TextUtils.isEmpty(encodedStr))
+                .collect(Collectors.joining(";"));
+    }
+
+
+    /**
+     * Encode the geometry object to string. The encoding format is specified by
+     * {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}.
+     * @param geometry the geometry object need to be encoded.
+     * @return the encoded string.
+     */
+    @NonNull
+    private static String encodeGeometryToString(@NonNull Geometry geometry) {
+        StringBuilder sb = new StringBuilder();
+        if (geometry instanceof Polygon) {
+            sb.append(POLYGON_SYMBOL);
+            for (LatLng latLng : ((Polygon) geometry).getVertices()) {
+                sb.append("|");
+                sb.append(latLng.lat);
+                sb.append(",");
+                sb.append(latLng.lng);
+            }
+        } else if (geometry instanceof Circle) {
+            sb.append(CIRCLE_SYMBOL);
+            Circle circle = (Circle) geometry;
+
+            // Center
+            sb.append("|");
+            sb.append(circle.getCenter().lat);
+            sb.append(",");
+            sb.append(circle.getCenter().lng);
+
+            // Radius
+            sb.append("|");
+            sb.append(circle.getRadius());
+        } else {
+            Rlog.e(TAG, "Unsupported geometry object " + geometry);
+            return null;
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Parse {@link LatLng} from {@link String}. Latitude and longitude are separated by ",".
+     * Example: "13.56,-55.447".
+     *
+     * @param str encoded lat/lng string.
+     * @Return {@link LatLng} object.
+     */
+    @NonNull
+    public static LatLng parseLatLngFromString(@NonNull String str) {
+        String[] latLng = str.split("\\s*,\\s*");
+        return new LatLng(Double.parseDouble(latLng[0]), Double.parseDouble(latLng[1]));
+    }
+
+    /**
+     * @Return the sign of the given value {@code value} with the specified tolerance. Return 1
+     * means the sign is positive, -1 means negative, 0 means the value will be treated as 0.
+     */
+    public static int sign(double value) {
+        if (value > EPS) return 1;
+        if (value < -EPS) return -1;
+        return 0;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index bb5c251..cde6db4 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -94,7 +94,7 @@
     public static final int EVENT_ROAMING_SETTING_CHANGE = BASE + 48;
     public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49;
     public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 50;
-    public static final int EVENT_APN_WHITE_LIST_CHANGE = BASE + 51;
+    public static final int EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED = BASE + 51;
 
     /***** Constants *****/
 
diff --git a/packages/NetworkPermissionConfig/src/com/android/server/NetworkPermissionConfig.java b/telephony/java/com/android/internal/telephony/IIntegerConsumer.aidl
similarity index 70%
copy from packages/NetworkPermissionConfig/src/com/android/server/NetworkPermissionConfig.java
copy to telephony/java/com/android/internal/telephony/IIntegerConsumer.aidl
index c904e23..252460e 100644
--- a/packages/NetworkPermissionConfig/src/com/android/server/NetworkPermissionConfig.java
+++ b/telephony/java/com/android/internal/telephony/IIntegerConsumer.aidl
@@ -14,13 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.internal.telephony;
 
-import android.app.Application;
-
-/**
- * Empty application for NetworkPermissionConfig that only exists because
- * soong builds complain if APKs have no source file.
- */
-public class NetworkPermissionConfig extends Application {
-}
+// Copies consumer pattern for an operation that requires an integer result from another process to
+// finish.
+oneway interface IIntegerConsumer {
+    void accept(int result);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index a4eb424..87aff8a 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -20,20 +20,11 @@
 import android.net.Uri;
 import com.android.internal.telephony.SmsRawData;
 
-/** Interface for applications to access the ICC phone book.
+/**
+ * Interface for applications to access the ICC phone book.
  *
- * <p>The following code snippet demonstrates a static method to
- * retrieve the ISms interface from Android:</p>
- * <pre>private static ISms getSmsInterface()
-            throws DeadObjectException {
-    IServiceManager sm = ServiceManagerNative.getDefault();
-    ISms ss;
-    ss = ISms.Stub.asInterface(sm.getService("isms"));
-    return ss;
-}
- * </pre>
+ * See also SmsManager.java.
  */
-
 interface ISms {
     /**
      * Retrieves all messages currently stored on ICC.
@@ -560,4 +551,11 @@
      * @param intent PendingIntent to be sent when an SMS is received containing the token.
      */
     String createAppSpecificSmsToken(int subId, String callingPkg, in PendingIntent intent);
+
+    /**
+     * Check if the destination is a possible premium short code.
+     *
+     * @param destAddress the destination address to test for possible short code
+     */
+    int checkSmsShortCodeDestination(int subId, String callingApk, String destAddress, String countryIso);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 1cdf44d..194f461 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -188,4 +188,10 @@
     public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public int checkSmsShortCodeDestination(
+            int subid, String callingApk, String destAddress, String countryIso) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index f248893..a481c29 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -285,4 +285,6 @@
     boolean isActiveSubId(int subId, String callingPackage);
 
     boolean setAlwaysAllowMmsData(int subId, boolean alwaysAllow);
+
+    int getActiveDataSubscriptionId();
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 6b927bb..06f35a7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.content.Intent;
+import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Messenger;
@@ -52,6 +53,7 @@
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.telephony.CellNetworkScanResult;
+import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.INumberVerificationCallback;
 import com.android.internal.telephony.OperatorInfo;
 
@@ -306,18 +308,46 @@
      */
     List<NeighboringCellInfo> getNeighboringCellInfo(String callingPkg);
 
-     @UnsupportedAppUsage
-     int getCallState();
+    @UnsupportedAppUsage
+    int getCallState();
 
     /**
      * Returns the call state for a slot.
      */
-     int getCallStateForSlot(int slotIndex);
+    int getCallStateForSlot(int slotIndex);
 
-     @UnsupportedAppUsage
-     int getDataActivity();
-     @UnsupportedAppUsage
-     int getDataState();
+    /**
+     * Replaced by getDataActivityForSubId.
+     */
+    int getDataActivity();
+
+    /**
+     * Returns a constant indicating the type of activity on a data connection
+     * (cellular).
+     *
+     * @see #DATA_ACTIVITY_NONE
+     * @see #DATA_ACTIVITY_IN
+     * @see #DATA_ACTIVITY_OUT
+     * @see #DATA_ACTIVITY_INOUT
+     * @see #DATA_ACTIVITY_DORMANT
+     */
+    int getDataActivityForSubId(int subId);
+
+    /**
+     * Replaced by getDataStateForSubId.
+     */
+    int getDataState();
+
+    /**
+     * Returns a constant indicating the current data connection state
+     * (cellular).
+     *
+     * @see #DATA_DISCONNECTED
+     * @see #DATA_CONNECTING
+     * @see #DATA_CONNECTED
+     * @see #DATA_SUSPENDED
+     */
+    int getDataStateForSubId(int subId);
 
     /**
      * Returns the current active phone type as integer.
@@ -458,13 +488,6 @@
     void sendDialerSpecialCode(String callingPackageName, String inputCode);
 
     /**
-     * Returns the network type for data transmission
-     * Legacy call, permission-free
-     */
-    @UnsupportedAppUsage
-    int getNetworkType();
-
-    /**
      * Returns the network type of a subId.
      * @param subId user preferred subId.
      * @param callingPackage package making the call.
@@ -1619,7 +1642,7 @@
      * <p>
      * See {@link UiccCardInfo} for more details on the kind of information available.
      *
-     * @param callingPackage package making the call, used to evaluate carrier privileges 
+     * @param callingPackage package making the call, used to evaluate carrier privileges
      * @return a list of UiccCardInfo objects, representing information on the currently inserted
      * UICCs and eUICCs. Each UiccCardInfo in the list will have private information filtered out if
      * the caller does not have adequate permissions for that card.
@@ -1960,10 +1983,10 @@
     void switchMultiSimConfig(int numOfSims);
 
     /**
-     * Get if reboot is required upon altering modems configurations
+     * Get if altering modems configurations will trigger reboot.
      * @hide
      */
-    boolean isRebootRequiredForModemConfigChange();
+    boolean doesSwitchMultiSimConfigTriggerReboot(int subId, String callingPackage);
 
     /**
      * Get the mapping from logical slots to physical slots.
@@ -1980,4 +2003,31 @@
     boolean isDataEnabledForApn(int apnType, int subId, String callingPackage);
 
     boolean isApnMetered(int apnType, int subId);
+
+    /**
+     * Enqueue a pending sms Consumer, which will answer with the user specified selection for an
+     * outgoing SmsManager operation.
+     */
+    oneway void enqueueSmsPickResult(String callingPackage, IIntegerConsumer subIdResult);
+
+    /**
+     * Returns the MMS user agent.
+     */
+    String getMmsUserAgent(int subId);
+
+    /**
+     * Returns the MMS user agent profile URL.
+     */
+    String getMmsUAProfUrl(int subId);
+
+    /**
+     * Set allowing mobile data during voice call.
+     */
+    boolean setDataAllowedDuringVoiceCall(int subId, boolean allow);
+
+    /**
+     * Check whether data is allowed during voice call. Note this is for dual sim device that
+     * data might be disabled on non-default data subscription but explicitly turned on by settings.
+     */
+    boolean isDataAllowedInVoiceCall(int subId);
 }
diff --git a/telephony/java/com/android/internal/telephony/SmsCbMessage.java b/telephony/java/com/android/internal/telephony/SmsCbMessage.java
deleted file mode 100644
index 046bf8c..0000000
--- a/telephony/java/com/android/internal/telephony/SmsCbMessage.java
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Parcelable object containing a received cell broadcast message. There are four different types
- * of Cell Broadcast messages:
- *
- * <ul>
- * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li>
- * <li>cell information messages, broadcast on channel 50, indicating the current cell name for
- *  roaming purposes (required to display on the idle screen in Brazil)</li>
- * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li>
- * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li>
- * </ul>
- *
- * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
- * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
- * unified under a common name, avoiding some names, such as "Message Identifier", that refer to
- * two completely different concepts in 3GPP and CDMA.
- *
- * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
- * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
- * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
- * application should
- *
- * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
- * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
- * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
- * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
- * are considered unique to the PLMN, to the current cell, or to the current Location Area (or
- * Service Area in UMTS). The relevant values are concatenated into a single String which will be
- * unique if the messages are not duplicates.
- *
- * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
- * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
- *
- * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
- * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
- * Only system applications such as the CellBroadcastReceiver may receive notifications for
- * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
- * interference with the immediate display of the alert message and playing of the alert sound and
- * vibration pattern, which could be caused by poorly written or malicious non-system code.
- *
- * @hide
- */
-public class SmsCbMessage implements Parcelable {
-
-    protected static final String LOG_TAG = "SMSCB";
-
-    /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
-    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
-
-    /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
-    public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
-
-    /** Location / service area wide geographical scope (GSM/UMTS only). */
-    public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
-
-    /** Cell wide geographical scope (GSM/UMTS only). */
-    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
-
-    /** GSM or UMTS format cell broadcast. */
-    public static final int MESSAGE_FORMAT_3GPP = 1;
-
-    /** CDMA format cell broadcast. */
-    public static final int MESSAGE_FORMAT_3GPP2 = 2;
-
-    /** Normal message priority. */
-    public static final int MESSAGE_PRIORITY_NORMAL = 0;
-
-    /** Interactive message priority. */
-    public static final int MESSAGE_PRIORITY_INTERACTIVE = 1;
-
-    /** Urgent message priority. */
-    public static final int MESSAGE_PRIORITY_URGENT = 2;
-
-    /** Emergency message priority. */
-    public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
-
-    /** Format of this message (for interpretation of service category values). */
-    private final int mMessageFormat;
-
-    /** Geographical scope of broadcast. */
-    private final int mGeographicalScope;
-
-    /**
-     * Serial number of broadcast (message identifier for CDMA, geographical scope + message code +
-     * update number for GSM/UMTS). The serial number plus the location code uniquely identify
-     * a cell broadcast for duplicate detection.
-     */
-    private final int mSerialNumber;
-
-    /**
-     * Location identifier for this message. It consists of the current operator MCC/MNC as a
-     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
-     * message is not binary 01, the Location Area is included for comparison. If the GS is
-     * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
-     */
-    private final SmsCbLocation mLocation;
-
-    /**
-     * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings,
-     * the information provided by the category is also available via {@link #getEtwsWarningInfo()}
-     * or {@link #getCmasWarningInfo()}.
-     */
-    private final int mServiceCategory;
-
-    /** Message language, as a two-character string, e.g. "en". */
-    private final String mLanguage;
-
-    /** Message body, as a String. */
-    private final String mBody;
-
-    /** Message priority (including emergency priority). */
-    private final int mPriority;
-
-    /** ETWS warning notification information (ETWS warnings only). */
-    private final SmsCbEtwsInfo mEtwsWarningInfo;
-
-    /** CMAS warning notification information (CMAS warnings only). */
-    private final SmsCbCmasInfo mCmasWarningInfo;
-
-    /**
-     * Create a new SmsCbMessage with the specified data.
-     */
-    public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
-            SmsCbLocation location, int serviceCategory, String language, String body,
-            int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) {
-        mMessageFormat = messageFormat;
-        mGeographicalScope = geographicalScope;
-        mSerialNumber = serialNumber;
-        mLocation = location;
-        mServiceCategory = serviceCategory;
-        mLanguage = language;
-        mBody = body;
-        mPriority = priority;
-        mEtwsWarningInfo = etwsWarningInfo;
-        mCmasWarningInfo = cmasWarningInfo;
-    }
-
-    /** Create a new SmsCbMessage object from a Parcel. */
-    public SmsCbMessage(Parcel in) {
-        mMessageFormat = in.readInt();
-        mGeographicalScope = in.readInt();
-        mSerialNumber = in.readInt();
-        mLocation = new SmsCbLocation(in);
-        mServiceCategory = in.readInt();
-        mLanguage = in.readString();
-        mBody = in.readString();
-        mPriority = in.readInt();
-        int type = in.readInt();
-        switch (type) {
-            case 'E':
-                // unparcel ETWS warning information
-                mEtwsWarningInfo = new SmsCbEtwsInfo(in);
-                mCmasWarningInfo = null;
-                break;
-
-            case 'C':
-                // unparcel CMAS warning information
-                mEtwsWarningInfo = null;
-                mCmasWarningInfo = new SmsCbCmasInfo(in);
-                break;
-
-            default:
-                mEtwsWarningInfo = null;
-                mCmasWarningInfo = null;
-        }
-    }
-
-    /**
-     * Flatten this object into a Parcel.
-     *
-     * @param dest  The Parcel in which the object should be written.
-     * @param flags Additional flags about how the object should be written (ignored).
-     */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mMessageFormat);
-        dest.writeInt(mGeographicalScope);
-        dest.writeInt(mSerialNumber);
-        mLocation.writeToParcel(dest, flags);
-        dest.writeInt(mServiceCategory);
-        dest.writeString(mLanguage);
-        dest.writeString(mBody);
-        dest.writeInt(mPriority);
-        if (mEtwsWarningInfo != null) {
-            // parcel ETWS warning information
-            dest.writeInt('E');
-            mEtwsWarningInfo.writeToParcel(dest, flags);
-        } else if (mCmasWarningInfo != null) {
-            // parcel CMAS warning information
-            dest.writeInt('C');
-            mCmasWarningInfo.writeToParcel(dest, flags);
-        } else {
-            // no ETWS or CMAS warning information
-            dest.writeInt('0');
-        }
-    }
-
-    public static final Parcelable.Creator<SmsCbMessage> CREATOR
-            = new Parcelable.Creator<SmsCbMessage>() {
-        @Override
-        public SmsCbMessage createFromParcel(Parcel in) {
-            return new SmsCbMessage(in);
-        }
-
-        @Override
-        public SmsCbMessage[] newArray(int size) {
-            return new SmsCbMessage[size];
-        }
-    };
-
-    /**
-     * Return the geographical scope of this message (GSM/UMTS only).
-     *
-     * @return Geographical scope
-     */
-    public int getGeographicalScope() {
-        return mGeographicalScope;
-    }
-
-    /**
-     * Return the broadcast serial number of broadcast (message identifier for CDMA, or
-     * geographical scope + message code + update number for GSM/UMTS). The serial number plus
-     * the location code uniquely identify a cell broadcast for duplicate detection.
-     *
-     * @return the 16-bit CDMA message identifier or GSM/UMTS serial number
-     */
-    public int getSerialNumber() {
-        return mSerialNumber;
-    }
-
-    /**
-     * Return the location identifier for this message, consisting of the MCC/MNC as a
-     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
-     * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the
-     * cell ID is also included. The {@link SmsCbLocation} object includes a method to test
-     * if the location is included within another location area or within a PLMN and CellLocation.
-     *
-     * @return the geographical location code for duplicate message detection
-     */
-    public SmsCbLocation getLocation() {
-        return mLocation;
-    }
-
-    /**
-     * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation
-     * of the category is radio technology specific. For ETWS and CMAS warnings, the information
-     * provided by the category is available via {@link #getEtwsWarningInfo()} or
-     * {@link #getCmasWarningInfo()} in a radio technology independent format.
-     *
-     * @return the radio technology specific service category
-     */
-    public int getServiceCategory() {
-        return mServiceCategory;
-    }
-
-    /**
-     * Get the ISO-639-1 language code for this message, or null if unspecified
-     *
-     * @return Language code
-     */
-    public String getLanguageCode() {
-        return mLanguage;
-    }
-
-    /**
-     * Get the body of this message, or null if no body available
-     *
-     * @return Body, or null
-     */
-    public String getMessageBody() {
-        return mBody;
-    }
-
-    /**
-     * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
-     * @return an integer representing 3GPP or 3GPP2 message format
-     */
-    public int getMessageFormat() {
-        return mMessageFormat;
-    }
-
-    /**
-     * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL}
-     * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return
-     * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}.
-     * @return an integer representing the message priority
-     */
-    public int getMessagePriority() {
-        return mPriority;
-    }
-
-    /**
-     * If this is an ETWS warning notification then this method will return an object containing
-     * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an
-     * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte
-     * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the
-     * ETWS primary notification timestamp and digital signature if received.
-     *
-     * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
-     */
-    public SmsCbEtwsInfo getEtwsWarningInfo() {
-        return mEtwsWarningInfo;
-    }
-
-    /**
-     * If this is a CMAS warning notification then this method will return an object containing
-     * the CMAS message class, category, response type, severity, urgency and certainty.
-     * The message class is always present. Severity, urgency and certainty are present for CDMA
-     * warning notifications containing a type 1 elements record and for GSM and UMTS warnings
-     * except for the Presidential-level alert category. Category and response type are only
-     * available for CDMA notifications containing a type 1 elements record.
-     *
-     * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
-     */
-    public SmsCbCmasInfo getCmasWarningInfo() {
-        return mCmasWarningInfo;
-    }
-
-    /**
-     * Return whether this message is an emergency (PWS) message type.
-     * @return true if the message is a public warning notification; false otherwise
-     */
-    public boolean isEmergencyMessage() {
-        return mPriority == MESSAGE_PRIORITY_EMERGENCY;
-    }
-
-    /**
-     * Return whether this message is an ETWS warning alert.
-     * @return true if the message is an ETWS warning notification; false otherwise
-     */
-    public boolean isEtwsMessage() {
-        return mEtwsWarningInfo != null;
-    }
-
-    /**
-     * Return whether this message is a CMAS warning alert.
-     * @return true if the message is a CMAS warning notification; false otherwise
-     */
-    public boolean isCmasMessage() {
-        return mCmasWarningInfo != null;
-    }
-
-    @Override
-    public String toString() {
-        return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber="
-                + mSerialNumber + ", location=" + mLocation + ", serviceCategory="
-                + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
-                + ", priority=" + mPriority
-                + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
-                + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}';
-    }
-
-    /**
-     * Describe the kinds of special objects contained in the marshalled representation.
-     * @return a bitmask indicating this Parcelable contains no special objects
-     */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
index 7574a6e..2d66427 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -19,17 +19,28 @@
 
 import android.Manifest;
 import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 import java.util.function.Supplier;
 
 /** Utility class for Telephony permission enforcement. */
@@ -41,6 +52,20 @@
     private static final Supplier<ITelephony> TELEPHONY_SUPPLIER = () ->
             ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
 
+    /**
+     * Whether to disable the new device identifier access restrictions.
+     */
+    private static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED =
+            "device_identifier_access_restrictions_disabled";
+
+    // Contains a mapping of packages that did not meet the new requirements to access device
+    // identifiers and the methods they were attempting to invoke; used to prevent duplicate
+    // reporting of packages / methods.
+    private static final Map<String, Set<String>> sReportedDeviceIDPackages;
+    static {
+        sReportedDeviceIDPackages = new HashMap<>();
+    }
+
     private TelephonyPermissions() {}
 
     /**
@@ -75,6 +100,16 @@
                 callingPackage, message);
     }
 
+    /** Identical to checkCallingOrSelfReadPhoneState but never throws SecurityException */
+    public static boolean checkCallingOrSelfReadPhoneStateNoThrow(
+            Context context, int subId, String callingPackage, String message) {
+        try {
+            return checkCallingOrSelfReadPhoneState(context, subId, callingPackage, message);
+        } catch (SecurityException se) {
+            return false;
+        }
+    }
+
     /**
      * Check whether the app with the given pid/uid can read phone state.
      *
@@ -102,6 +137,19 @@
                 context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, message);
     }
 
+    /**
+     * Check whether the calling packages has carrier privileges for the passing subscription.
+     * @return {@code true} if the caller has carrier privileges, {@false} otherwise.
+     */
+    public static boolean checkCarrierPrivilegeForSubId(int subId) {
+        if (SubscriptionManager.isValidSubscriptionId(subId)
+                && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, Binder.getCallingUid())
+                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+            return true;
+        }
+        return false;
+    }
+
     @VisibleForTesting
     public static boolean checkReadPhoneState(
             Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
@@ -169,18 +217,9 @@
                 context.enforcePermission(
                         android.Manifest.permission.READ_PHONE_STATE, pid, uid, message);
             } catch (SecurityException phoneStateException) {
-                SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
-                        Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-                int[] activeSubIds = sm.getActiveSubscriptionIdList();
-                for (int activeSubId : activeSubIds) {
-                    // If we don't have the runtime permission, but do have carrier privileges, that
-                    // suffices for reading phone state.
-                    if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid)
-                            == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-                        return true;
-                    }
-                }
-                return false;
+                // If we don't have the runtime permission, but do have carrier privileges, that
+                // suffices for reading phone state.
+                return checkCarrierPrivilegeForAnySubId(context, telephonySupplier, uid);
             }
         }
 
@@ -192,6 +231,182 @@
     }
 
     /**
+     * Check whether the caller (or self, if not processing an IPC) can read device identifiers.
+     *
+     * <p>This method behaves in one of the following ways:
+     * <ul>
+     *   <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling
+     *       package passes a DevicePolicyManager Device Owner / Profile Owner device identifier
+     *       access check, or the calling package has carrier privileges.
+     *   <li>throw SecurityException: if the caller does not meet any of the requirements and is
+     *       targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission.
+     *   <li>return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE
+     *       permission. In this case the caller would expect to have access to the device
+     *       identifiers so false is returned instead of throwing a SecurityException to indicate
+     *       the calling function should return dummy data.
+     * </ul>
+     */
+    public static boolean checkCallingOrSelfReadDeviceIdentifiers(Context context,
+            String callingPackage, String message) {
+        return checkCallingOrSelfReadDeviceIdentifiers(context,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, message);
+    }
+
+    /**
+     * Check whether the caller (or self, if not processing an IPC) can read device identifiers.
+     *
+     * <p>This method behaves in one of the following ways:
+     * <ul>
+     *   <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling
+     *       package passes a DevicePolicyManager Device Owner / Profile Owner device identifier
+     *       access check, or the calling package has carrier privileges.
+     *   <li>throw SecurityException: if the caller does not meet any of the requirements and is
+     *       targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission
+     *       or carrier privileges.
+     *   <li>return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE
+     *       permission or carrier privileges. In this case the caller would expect to have access
+     *       to the device identifiers so false is returned instead of throwing a SecurityException
+     *       to indicate the calling function should return dummy data.
+     * </ul>
+     */
+    public static boolean checkCallingOrSelfReadDeviceIdentifiers(Context context, int subId,
+            String callingPackage, String message) {
+        return checkReadDeviceIdentifiers(context, TELEPHONY_SUPPLIER, subId,
+                Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, message);
+    }
+
+    /**
+     * Check whether the caller (or self, if not processing an IPC) can read subscriber identifiers.
+     *
+     * <p>This method behaves in one of the following ways:
+     * <ul>
+     *   <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling
+     *       package passes a DevicePolicyManager Device Owner / Profile Owner device identifier
+     *       access check, or the calling package has carrier privileges.
+     *   <li>throw SecurityException: if the caller does not meet any of the requirements and is
+     *       targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission.
+     *   <li>return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE
+     *       permission. In this case the caller would expect to have access to the device
+     *       identifiers so false is returned instead of throwing a SecurityException to indicate
+     *       the calling function should return dummy data.
+     * </ul>
+     */
+    public static boolean checkCallingOrSelfReadSubscriberIdentifiers(Context context, int subId,
+            String callingPackage, String message) {
+        return checkReadDeviceIdentifiers(context, TELEPHONY_SUPPLIER, subId,
+                Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, message);
+    }
+
+    /**
+     * Checks whether the app with the given pid/uid can read device identifiers.
+     *
+     * @returns true if the caller has the READ_PRIVILEGED_PHONE_STATE permission or the calling
+     * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier access
+     * check.
+     */
+    @VisibleForTesting
+    public static boolean checkReadDeviceIdentifiers(Context context,
+            Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
+            String callingPackage, String message) {
+        // Allow system and root access to the device identifiers.
+        final int appId = UserHandle.getAppId(uid);
+        if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) {
+            return true;
+        }
+        // Allow access to packages that have the READ_PRIVILEGED_PHONE_STATE permission.
+        if (context.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid,
+                uid) == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        // If the calling package has carrier privileges for any subscription then allow access.
+        if (checkCarrierPrivilegeForAnySubId(context, telephonySupplier, uid)) {
+            return true;
+        }
+        // if the calling package is not null then perform the DevicePolicyManager device /
+        // profile owner and Appop checks.
+        if (callingPackage != null) {
+            // Allow access to a device / profile owner app.
+            DevicePolicyManager devicePolicyManager =
+                    (DevicePolicyManager) context.getSystemService(
+                            Context.DEVICE_POLICY_SERVICE);
+            if (devicePolicyManager != null && devicePolicyManager.checkDeviceIdentifierAccess(
+                    callingPackage, pid, uid)) {
+                return true;
+            }
+        }
+        return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage,
+            message);
+    }
+
+    /**
+     * Reports a failure when the app with the given pid/uid cannot access the requested identifier.
+     *
+     * @returns false if the caller is targeting pre-Q and does have the READ_PHONE_STATE
+     * permission or carrier privileges.
+     * @throws SecurityException if the caller does not meet any of the requirements for the
+     *                           requested identifier and is targeting Q or is targeting pre-Q
+     *                           and does not have the READ_PHONE_STATE permission or carrier
+     *                           privileges.
+     */
+    private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid,
+            int uid, String callingPackage, String message) {
+        boolean isPreinstalled = false;
+        boolean isPrivApp = false;
+        ApplicationInfo callingPackageInfo = null;
+        try {
+            callingPackageInfo = context.getPackageManager().getApplicationInfoAsUser(
+                    callingPackage, 0, UserHandle.getUserId(uid));
+            if (callingPackageInfo != null) {
+                if (callingPackageInfo.isSystemApp()) {
+                    isPreinstalled = true;
+                    if (callingPackageInfo.isPrivilegedApp()) {
+                        isPrivApp = true;
+                    }
+                }
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // If the application info for the calling package could not be found then assume the
+            // calling app is a non-preinstalled app to detect any issues with the check
+            Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage,
+                    e);
+        }
+        // The current package should only be reported in StatsLog if it has not previously been
+        // reported for the currently invoked device identifier method.
+        boolean packageReported = sReportedDeviceIDPackages.containsKey(callingPackage);
+        if (!packageReported || !sReportedDeviceIDPackages.get(callingPackage).contains(
+                message)) {
+            Set invokedMethods;
+            if (!packageReported) {
+                invokedMethods = new HashSet<String>();
+                sReportedDeviceIDPackages.put(callingPackage, invokedMethods);
+            } else {
+                invokedMethods = sReportedDeviceIDPackages.get(callingPackage);
+            }
+            invokedMethods.add(message);
+            StatsLog.write(StatsLog.DEVICE_IDENTIFIER_ACCESS_DENIED, callingPackage, message,
+                    isPreinstalled, isPrivApp);
+        }
+        Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message
+                + ":isPreinstalled=" + isPreinstalled + ":isPrivApp=" + isPrivApp);
+        // if the target SDK is pre-Q then check if the calling package would have previously
+        // had access to device identifiers.
+        if (callingPackageInfo != null && (
+                callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
+            if (context.checkPermission(
+                    android.Manifest.permission.READ_PHONE_STATE,
+                    pid,
+                    uid) == PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+            if (checkCarrierPrivilegeForSubId(subId)) {
+                return false;
+            }
+        }
+        throw new SecurityException(message + ": The user " + uid
+                + " does not meet the requirements to access device identifiers.");
+    }
+
+    /**
      * Check whether the app with the given pid/uid can read the call log.
      * @return {@code true} if the specified app has the read call log permission and AppOpp granted
      *      to it, {@code false} otherwise.
@@ -373,6 +588,26 @@
         }
     }
 
+    /**
+     * Returns whether the provided uid has carrier privileges for any active subscription ID.
+     */
+    private static boolean checkCarrierPrivilegeForAnySubId(Context context,
+            Supplier<ITelephony> telephonySupplier, int uid) {
+        SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        int[] activeSubIds = sm.getActiveSubscriptionIdList();
+        if (activeSubIds != null) {
+            for (int activeSubId : activeSubIds) {
+                if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid)
+                        == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
     private static int getCarrierPrivilegeStatus(
             Supplier<ITelephony> telephonySupplier, int subId, int uid) {
         ITelephony telephony = telephonySupplier.get();
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 56403ff..ebc98d6 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony.cdma;
 
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_IDP_STRING;
+
+import android.content.res.Resources;
 import android.os.Parcel;
 import android.os.SystemProperties;
 import android.telephony.PhoneNumberUtils;
@@ -23,9 +26,8 @@
 import android.telephony.SmsCbMessage;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.telephony.Rlog;
-import android.util.Log;
 import android.text.TextUtils;
-import android.content.res.Resources;
+import android.util.Log;
 
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.SmsAddress;
@@ -418,7 +420,7 @@
         CharSequence newMsgBody = null;
         Resources r = Resources.getSystem();
         if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
-            newMsgBody = Sms7BitEncodingTranslator.translate(messageBody, true);
+            newMsgBody = Sms7BitEncodingTranslator.translate(messageBody, true /* isCdmaFormat */);
         }
         if (TextUtils.isEmpty(newMsgBody)) {
             newMsgBody = messageBody;
@@ -613,10 +615,11 @@
                         }
                         addr.origBytes = data;
                         Rlog.pii(LOG_TAG, "Addr=" + addr.toString());
-                        mOriginatingAddress = addr;
-                        if (parameterId == DESTINATION_ADDRESS) {
-                            // Original address awlays indicates one sender's address for 3GPP2
-                            // Here add recipient address support along with 3GPP
+                        if (parameterId == ORIGINATING_ADDRESS) {
+                            env.origAddress = addr;
+                            mOriginatingAddress = addr;
+                        } else {
+                            env.destAddress = addr;
                             mRecipientAddress = addr;
                         }
                         break;
@@ -634,6 +637,11 @@
                             subdata[index] = convertDtmfToAscii(b);
                         }
                         subAddr.origBytes = subdata;
+                        if (parameterId == ORIGINATING_SUB_ADDRESS) {
+                            env.origSubaddress = subAddr;
+                        } else {
+                            env.destSubaddress = subAddr;
+                        }
                         break;
                     case BEARER_REPLY_OPTION:
                         dis.read(parameterData, 0, parameterLen);
@@ -663,9 +671,6 @@
         }
 
         // link the filled objects to this SMS
-        mOriginatingAddress = addr;
-        env.origAddress = addr;
-        env.origSubaddress = subAddr;
         mEnvelope = env;
         mPdu = pdu;
 
@@ -673,6 +678,89 @@
     }
 
     /**
+     * Pre-processes an SMS WAP for Teleservice Id 0xFDEA(65002).
+     *
+     * It requires an additional header parsing to extract new Message Identifier and new User Data
+     * from WDP SMS User Data.
+     *
+     * - WDP SMS User Data Subparameter =
+     *   |User Data SUBPARAMETER_ID ~ NUM_FIELDS| + |CHARi| + |RESERVED|
+     *
+     * - WDP SMS User Data Subparameter CHARi =
+     *   |New Message Identifier Subparameter(HEADER_IND = 0)| +
+     *   |New User Data Subparameter(MSG_ENCODING = ENCODING_OCTET)|
+     *
+     * @return true if preprocessing is successful, false otherwise.
+     */
+    public boolean preprocessCdmaFdeaWap() {
+        try {
+            BitwiseInputStream inStream = new BitwiseInputStream(mUserData);
+
+            // Message Identifier SUBPARAMETER_ID(0x00)
+            if (inStream.read(8) != 0x00) {
+                Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier SUBPARAMETER_ID");
+                return false;
+            }
+
+            // Message Identifier SUBPARAM_LEN(0x03)
+            if (inStream.read(8) != 0x03) {
+                Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier SUBPARAM_LEN");
+                return false;
+            }
+
+            // Message Identifier MESSAGE_TYPE
+            mBearerData.messageType = inStream.read(4);
+
+            // Message Identifier MESSAGE_ID
+            int msgId = inStream.read(8) << 8;
+            msgId |= inStream.read(8);
+            mBearerData.messageId = msgId;
+            mMessageRef = msgId;
+
+            // Message Identifier HEADER_IND
+            mBearerData.hasUserDataHeader = (inStream.read(1) == 1);
+            if (mBearerData.hasUserDataHeader) {
+                Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier HEADER_IND");
+                return false;
+            }
+
+            // Message Identifier RESERVED
+            inStream.skip(3);
+
+            // User Data SUBPARAMETER_ID(0x01)
+            if (inStream.read(8) != 0x01) {
+                Rlog.e(LOG_TAG, "Invalid FDEA WDP Header User Data SUBPARAMETER_ID");
+                return false;
+            }
+
+            // User Data SUBPARAM_LEN
+            int userDataLen = inStream.read(8) * 8;
+
+            // User Data MSG_ENCODING
+            mBearerData.userData.msgEncoding = inStream.read(5);
+            int consumedBits = 5;
+            if (mBearerData.userData.msgEncoding != UserData.ENCODING_OCTET) {
+                Rlog.e(LOG_TAG, "Invalid FDEA WDP Header User Data MSG_ENCODING");
+                return false;
+            }
+
+            // User Data NUM_FIELDS
+            mBearerData.userData.numFields = inStream.read(8);
+            consumedBits += 8;
+
+            int remainingBits = userDataLen - consumedBits;
+            int dataBits = mBearerData.userData.numFields * 8;
+            dataBits = dataBits < remainingBits ? dataBits : remainingBits;
+            mBearerData.userData.payload = inStream.readByteArray(dataBits);
+            mUserData = mBearerData.userData.payload;
+            return true;
+        } catch (BitwiseInputStream.AccessException ex) {
+            Rlog.e(LOG_TAG, "Fail to preprocess FDEA WAP: " + ex);
+        }
+        return false;
+    }
+
+    /**
      * Parses a SMS message from its BearerData stream.
      */
     public void parseSms() {
@@ -704,16 +792,16 @@
 
         if (mOriginatingAddress != null) {
             decodeSmsDisplayAddress(mOriginatingAddress);
-            if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
-                    + mOriginatingAddress.address);
+            if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: " + mOriginatingAddress.address);
         }
 
         if (mRecipientAddress != null) {
             decodeSmsDisplayAddress(mRecipientAddress);
+            if (VDBG) Rlog.v(LOG_TAG, "SMS destination address: " + mRecipientAddress.address);
         }
 
         if (mBearerData.msgCenterTimeStamp != null) {
-            mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true);
+            mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis();
         }
 
         if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
@@ -726,12 +814,12 @@
             // being reported refers to.  The MsgStatus subparameter
             // is primarily useful to indicate error conditions -- a
             // message without this subparameter is assumed to
-            // indicate successful delivery (status == 0).
-            if (! mBearerData.messageStatusSet) {
+            // indicate successful delivery.
+            if (!mBearerData.messageStatusSet) {
                 Rlog.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" +
                         (mUserData == null ? "also missing" : "does have") +
                         " userData).");
-                status = 0;
+                status = (BearerData.ERROR_NONE << 8) | BearerData.STATUS_DELIVERED;
             } else {
                 status = mBearerData.errorClass << 8;
                 status |= mBearerData.messageStatus;
@@ -750,8 +838,16 @@
     }
 
     private void decodeSmsDisplayAddress(SmsAddress addr) {
+        // PCD(Plus Code Dialing)
+        // 1) Replaces IDD(International Direct Dialing) with the '+' if address starts with it.
+        // TODO: Skip it for EF SMS(SUBMIT and DELIVER) because the IDD depends on current network?
+        // 2) Adds the '+' prefix if TON is International
+        // 3) Keeps the '+' if address starts with the '+'
+        String idd = SystemProperties.get(PROPERTY_OPERATOR_IDP_STRING, null);
         addr.address = new String(addr.origBytes);
-        if (addr.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) {
+        if (!TextUtils.isEmpty(idd) && addr.address.startsWith(idd)) {
+            addr.address = "+" + addr.address.substring(idd.length());
+        } else if (addr.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) {
             if (addr.address.charAt(0) != '+') {
                 addr.address = "+" + addr.address;
             }
diff --git a/telephony/java/com/android/internal/telephony/cdma/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
similarity index 98%
rename from telephony/java/com/android/internal/telephony/cdma/BearerData.java
rename to telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index 694cc69..dab1436 100644
--- a/telephony/java/com/android/internal/telephony/cdma/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -20,7 +20,6 @@
 import android.telephony.SmsCbCmasInfo;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.telephony.cdma.CdmaSmsCbProgramResults;
-import android.text.format.Time;
 import android.telephony.Rlog;
 
 import com.android.internal.telephony.GsmAlphabet;
@@ -32,8 +31,10 @@
 import com.android.internal.util.BitwiseInputStream;
 import com.android.internal.util.BitwiseOutputStream;
 
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.ArrayList;
-import java.util.TimeZone;
 
 /**
  * An object to encode and decode CDMA SMS bearer data.
@@ -228,10 +229,23 @@
     /**
      * 6-byte-field, see 3GPP2 C.S0015-B, v2, 4.5.4
      */
-    public static class TimeStamp extends Time {
+    public static class TimeStamp {
+
+        public int second;
+        public int minute;
+        public int hour;
+        public int monthDay;
+
+        /** Month [0-11] */
+        public int month;
+
+        /** Full year. For example, 1970. */
+        public int year;
+
+        private ZoneId mZoneId;
 
         public TimeStamp() {
-            super(TimeZone.getDefault().getID());   // 3GPP2 timestamps use the local timezone
+            mZoneId = ZoneId.systemDefault();   // 3GPP2 timestamps use the local timezone
         }
 
         public static TimeStamp fromByteArray(byte[] data) {
@@ -258,6 +272,14 @@
             return ts;
         }
 
+        public long toMillis() {
+            LocalDateTime localDateTime =
+                    LocalDateTime.of(year, month + 1, monthDay, hour, minute, second);
+            Instant instant = localDateTime.toInstant(mZoneId.getRules().getOffset(localDateTime));
+            return instant.toEpochMilli();
+        }
+
+
         @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
@@ -742,13 +764,13 @@
                                       " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)");
         }
 
-        /*
-         * TODO(cleanup): figure out what the right answer is WRT paddingBits field
-         *
-         *   userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7);
-         *   userData.paddingBits = 0; // XXX this seems better, but why?
-         *
-         */
+        if (bData.userData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
+            bData.userData.paddingBits =
+                    (bData.userData.payload.length * 8) - (bData.userData.numFields * 7);
+        } else {
+            bData.userData.paddingBits = 0;
+        }
+
         int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits;
         int paramBits = dataBits + 13;
         if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
similarity index 100%
rename from telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java
rename to telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsSubaddress.java
similarity index 100%
rename from telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java
rename to telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsSubaddress.java
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
similarity index 93%
rename from telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java
rename to telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
index f73df56..bed2de1 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -39,6 +39,9 @@
     static public final int TELESERVICE_WEMT              = 0x1005;
     static public final int TELESERVICE_SCPT              = 0x1006;
 
+    /** Carriers specific Teleservice IDs. */
+    public static final int TELESERVICE_FDEA_WAP = 0xFDEA; // 65002
+
     /**
      * The following are defined as extensions to the standard teleservices
      */
@@ -97,6 +100,12 @@
     public CdmaSmsSubaddress origSubaddress;
 
     /**
+     * The destination subaddress identifies the target of the SMS message.
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.4)
+     */
+    public CdmaSmsSubaddress destSubaddress;
+
+    /**
      * The 6-bit bearer reply parameter is used to request the return of a
      * SMS Acknowledge Message.
      * (See 3GPP2 C.S0015-B, v2, 3.4.3.5)
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
index 8015b07..dca4e6b 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -22,58 +22,36 @@
 import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
 import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
 import android.telephony.SmsCbLocation;
 import android.telephony.SmsCbMessage;
 import android.util.Pair;
+import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.telephony.CbGeoUtils;
+import com.android.internal.telephony.CbGeoUtils.Circle;
+import com.android.internal.telephony.CbGeoUtils.Geometry;
+import com.android.internal.telephony.CbGeoUtils.LatLng;
+import com.android.internal.telephony.CbGeoUtils.Polygon;
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
+import com.android.internal.telephony.gsm.SmsCbHeader.DataCodingScheme;
 
 import java.io.UnsupportedEncodingException;
-import java.util.Locale;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
  * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
  */
 public class GsmSmsCbMessage {
-
-    /**
-     * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
-     */
-    private static final String[] LANGUAGE_CODES_GROUP_0 = {
-            Locale.GERMAN.getLanguage(),        // German
-            Locale.ENGLISH.getLanguage(),       // English
-            Locale.ITALIAN.getLanguage(),       // Italian
-            Locale.FRENCH.getLanguage(),        // French
-            new Locale("es").getLanguage(),     // Spanish
-            new Locale("nl").getLanguage(),     // Dutch
-            new Locale("sv").getLanguage(),     // Swedish
-            new Locale("da").getLanguage(),     // Danish
-            new Locale("pt").getLanguage(),     // Portuguese
-            new Locale("fi").getLanguage(),     // Finnish
-            new Locale("nb").getLanguage(),     // Norwegian
-            new Locale("el").getLanguage(),     // Greek
-            new Locale("tr").getLanguage(),     // Turkish
-            new Locale("hu").getLanguage(),     // Hungarian
-            new Locale("pl").getLanguage(),     // Polish
-            null
-    };
-
-    /**
-     * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
-     */
-    private static final String[] LANGUAGE_CODES_GROUP_2 = {
-            new Locale("cs").getLanguage(),     // Czech
-            new Locale("he").getLanguage(),     // Hebrew
-            new Locale("ar").getLanguage(),     // Arabic
-            new Locale("ru").getLanguage(),     // Russian
-            new Locale("is").getLanguage(),     // Icelandic
-            null, null, null, null, null, null, null, null, null, null, null
-    };
+    private static final String TAG = GsmSmsCbMessage.class.getSimpleName();
 
     private static final char CARRIAGE_RETURN = 0x0d;
 
@@ -114,8 +92,9 @@
      * @param pdus PDU bytes
      */
     public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
-                                                  SmsCbLocation location, byte[][] pdus)
+            SmsCbLocation location, byte[][] pdus)
             throws IllegalArgumentException {
+        long receivedTimeMillis = System.currentTimeMillis();
         if (header.isEtwsPrimaryNotification()) {
             // ETSI TS 23.041 ETWS Primary Notification message
             // ETWS primary message only contains 4 fields including serial number,
@@ -125,12 +104,41 @@
                     header.getSerialNumber(), location, header.getServiceCategory(), null,
                     getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
                     SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
-                    header.getCmasInfo());
+                    header.getCmasInfo(), null /* geometries */, receivedTimeMillis);
+        } else if (header.isUmtsFormat()) {
+            // UMTS format has only 1 PDU
+            byte[] pdu = pdus[0];
+            Pair<String, String> cbData = parseUmtsBody(header, pdu);
+            String language = cbData.first;
+            String body = cbData.second;
+            int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
+                    : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
+            int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
+            int wacDataOffset = SmsCbHeader.PDU_HEADER_LENGTH
+                    + 1 // number of pages
+                    + (PDU_BODY_PAGE_LENGTH + 1) * nrPages; // cb data
+
+            // Has Warning Area Coordinates information
+            List<Geometry> geometries = null;
+            if (pdu.length > wacDataOffset) {
+                try {
+                    geometries = parseWarningAreaCoordinates(pdu, wacDataOffset);
+                } catch (Exception ex) {
+                    // Catch the exception here, the message will be considered as having no WAC
+                    // information which means the message will be broadcasted directly.
+                    Slog.e(TAG, "Can't parse warning area coordinates, ex = " + ex.toString());
+                }
+            }
+
+            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
+                    header.getGeographicalScope(), header.getSerialNumber(), location,
+                    header.getServiceCategory(), language, body, priority,
+                    header.getEtwsInfo(), header.getCmasInfo(), geometries, receivedTimeMillis);
         } else {
             String language = null;
             StringBuilder sb = new StringBuilder();
             for (byte[] pdu : pdus) {
-                Pair<String, String> p = parseBody(header, pdu);
+                Pair<String, String> p = parseGsmBody(header, pdu);
                 language = p.first;
                 sb.append(p.second);
             }
@@ -140,154 +148,197 @@
             return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
                     header.getGeographicalScope(), header.getSerialNumber(), location,
                     header.getServiceCategory(), language, sb.toString(), priority,
-                    header.getEtwsInfo(), header.getCmasInfo());
+                    header.getEtwsInfo(), header.getCmasInfo(), null /* geometries */,
+                    receivedTimeMillis);
         }
     }
 
     /**
-     * Parse and unpack the body text according to the encoding in the DCS.
-     * After completing successfully this method will have assigned the body
-     * text into mBody, and optionally the language code into mLanguage
+     * Parse WEA Handset Action Message(WHAM) a.k.a geo-fencing trigger message.
+     *
+     * WEA Handset Action Message(WHAM) is a cell broadcast service message broadcast by the network
+     * to direct devices to perform a geo-fencing check on selected alerts.
+     *
+     * WEA Handset Action Message(WHAM) requirements from ATIS-0700041 section 4
+     * 1. The Warning Message contents of a WHAM shall be in Cell Broadcast(CB) data format as
+     * defined in TS 23.041.
+     * 2. The Warning Message Contents of WHAM shall be limited to one CB page(max 20 referenced
+     * WEA messages).
+     * 3. The broadcast area for a WHAM shall be the union of the broadcast areas of the referenced
+     * WEA message.
+     * @param pdu cell broadcast pdu, including the header
+     * @return {@link GeoFencingTriggerMessage} instance
+     */
+    public static GeoFencingTriggerMessage createGeoFencingTriggerMessage(byte[] pdu) {
+        try {
+            // Header length + 1(number of page). ATIS-0700041 define the number of page of
+            // geo-fencing trigger message is 1.
+            int whamOffset = SmsCbHeader.PDU_HEADER_LENGTH + 1;
+
+            BitStreamReader bitReader = new BitStreamReader(pdu, whamOffset);
+            int type = bitReader.read(4);
+            int length = bitReader.read(7);
+            // Skip the remained 5 bits
+            bitReader.skip();
+
+            int messageIdentifierCount = (length - 2) * 8 / 32;
+            List<CellBroadcastIdentity> cbIdentifiers = new ArrayList<>();
+            for (int i = 0; i < messageIdentifierCount; i++) {
+                // Both messageIdentifier and serialNumber are 16 bits integers.
+                // ATIS-0700041 Section 5.1.6
+                int messageIdentifier = bitReader.read(16);
+                int serialNumber = bitReader.read(16);
+                cbIdentifiers.add(new CellBroadcastIdentity(messageIdentifier, serialNumber));
+            }
+            return new GeoFencingTriggerMessage(type, cbIdentifiers);
+        } catch (Exception ex) {
+            Slog.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString());
+            return null;
+        }
+    }
+
+    private static List<Geometry> parseWarningAreaCoordinates(byte[] pdu, int wacOffset) {
+        // little-endian
+        int wacDataLength = (pdu[wacOffset + 1] << 8) | pdu[wacOffset];
+        int offset = wacOffset + 2;
+
+        if (offset + wacDataLength > pdu.length) {
+            throw new IllegalArgumentException("Invalid wac data, expected the length of pdu at"
+                    + "least " + offset + wacDataLength + ", actual is " + pdu.length);
+        }
+
+        BitStreamReader bitReader = new BitStreamReader(pdu, offset);
+
+        List<Geometry> geo = new ArrayList<>();
+        int remainedBytes = wacDataLength;
+        while (remainedBytes > 0) {
+            int type = bitReader.read(4);
+            int length = bitReader.read(10);
+            remainedBytes -= length;
+            // Skip the 2 remained bits
+            bitReader.skip();
+
+            switch (type) {
+                case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME:
+                    // TODO: handle the maximum wait time in cell broadcast provider.
+                    int maximumWaitTimeSec = bitReader.read(8);
+                    break;
+                case CbGeoUtils.GEOMETRY_TYPE_POLYGON:
+                    List<LatLng> latLngs = new ArrayList<>();
+                    // Each coordinate is represented by 44 bits integer.
+                    // ATIS-0700041 5.2.4 Coordinate coding
+                    int n = (length - 2) * 8 / 44;
+                    for (int i = 0; i < n; i++) {
+                        latLngs.add(getLatLng(bitReader));
+                    }
+                    // Skip the padding bits
+                    bitReader.skip();
+                    geo.add(new Polygon(latLngs));
+                    break;
+                case CbGeoUtils.GEOMETRY_TYPE_CIRCLE:
+                    LatLng center = getLatLng(bitReader);
+                    // radius = (wacRadius / 2^6). The unit of wacRadius is km, we use meter as the
+                    // distance unit during geo-fencing.
+                    // ATIS-0700041 5.2.5 radius coding
+                    double radius = (bitReader.read(20) * 1.0 / (1 << 6)) * 1000.0;
+                    geo.add(new Circle(center, radius));
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported geoType = " + type);
+            }
+        }
+        return geo;
+    }
+
+    /**
+     * The coordinate is (latitude, longitude), represented by a 44 bits integer.
+     * The coding is defined in ATIS-0700041 5.2.4
+     * @param bitReader
+     * @return coordinate (latitude, longitude)
+     */
+    private static LatLng getLatLng(BitStreamReader bitReader) {
+        // wacLatitude = floor(((latitude + 90) / 180) * 2^22)
+        // wacLongitude = floor(((longitude + 180) / 360) * 2^22)
+        int wacLat = bitReader.read(22);
+        int wacLng = bitReader.read(22);
+
+        // latitude = wacLatitude * 180 / 2^22 - 90
+        // longitude = wacLongitude * 360 / 2^22 -180
+        return new LatLng((wacLat * 180.0 / (1 << 22)) - 90, (wacLng * 360.0 / (1 << 22) - 180));
+    }
+
+    /**
+     * Parse and unpack the UMTS body text according to the encoding in the data coding scheme.
      *
      * @param header the message header to use
      * @param pdu the PDU to decode
-     * @return a Pair of Strings containing the language and body of the message
+     * @return a pair of string containing the language and body of the message in order
      */
-    private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) {
-        int encoding;
-        String language = null;
-        boolean hasLanguageIndicator = false;
-        int dataCodingScheme = header.getDataCodingScheme();
+    private static Pair<String, String> parseUmtsBody(SmsCbHeader header, byte[] pdu) {
+        // Payload may contain multiple pages
+        int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
+        String language = header.getDataCodingSchemeStructedData().language;
 
-        // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
-        // section 5.
-        switch ((dataCodingScheme & 0xf0) >> 4) {
-            case 0x00:
-                encoding = SmsConstants.ENCODING_7BIT;
-                language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
-                break;
-
-            case 0x01:
-                hasLanguageIndicator = true;
-                if ((dataCodingScheme & 0x0f) == 0x01) {
-                    encoding = SmsConstants.ENCODING_16BIT;
-                } else {
-                    encoding = SmsConstants.ENCODING_7BIT;
-                }
-                break;
-
-            case 0x02:
-                encoding = SmsConstants.ENCODING_7BIT;
-                language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
-                break;
-
-            case 0x03:
-                encoding = SmsConstants.ENCODING_7BIT;
-                break;
-
-            case 0x04:
-            case 0x05:
-                switch ((dataCodingScheme & 0x0c) >> 2) {
-                    case 0x01:
-                        encoding = SmsConstants.ENCODING_8BIT;
-                        break;
-
-                    case 0x02:
-                        encoding = SmsConstants.ENCODING_16BIT;
-                        break;
-
-                    case 0x00:
-                    default:
-                        encoding = SmsConstants.ENCODING_7BIT;
-                        break;
-                }
-                break;
-
-            case 0x06:
-            case 0x07:
-                // Compression not supported
-            case 0x09:
-                // UDH structure not supported
-            case 0x0e:
-                // Defined by the WAP forum not supported
-                throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
-                        + dataCodingScheme);
-
-            case 0x0f:
-                if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
-                    encoding = SmsConstants.ENCODING_8BIT;
-                } else {
-                    encoding = SmsConstants.ENCODING_7BIT;
-                }
-                break;
-
-            default:
-                // Reserved values are to be treated as 7-bit
-                encoding = SmsConstants.ENCODING_7BIT;
-                break;
+        if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
+                * nrPages) {
+            throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
+                    + nrPages + " pages");
         }
 
-        if (header.isUmtsFormat()) {
-            // Payload may contain multiple pages
-            int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
+        StringBuilder sb = new StringBuilder();
 
-            if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
-                    * nrPages) {
-                throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
-                        + nrPages + " pages");
+        for (int i = 0; i < nrPages; i++) {
+            // Each page is 82 bytes followed by a length octet indicating
+            // the number of useful octets within those 82
+            int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
+            int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
+
+            if (length > PDU_BODY_PAGE_LENGTH) {
+                throw new IllegalArgumentException("Page length " + length
+                        + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
             }
 
-            StringBuilder sb = new StringBuilder();
-
-            for (int i = 0; i < nrPages; i++) {
-                // Each page is 82 bytes followed by a length octet indicating
-                // the number of useful octets within those 82
-                int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
-                int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
-
-                if (length > PDU_BODY_PAGE_LENGTH) {
-                    throw new IllegalArgumentException("Page length " + length
-                            + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
-                }
-
-                Pair<String, String> p = unpackBody(pdu, encoding, offset, length,
-                        hasLanguageIndicator, language);
-                language = p.first;
-                sb.append(p.second);
-            }
-            return new Pair<String, String>(language, sb.toString());
-        } else {
-            // Payload is one single page
-            int offset = SmsCbHeader.PDU_HEADER_LENGTH;
-            int length = pdu.length - offset;
-
-            return unpackBody(pdu, encoding, offset, length, hasLanguageIndicator, language);
+            Pair<String, String> p = unpackBody(pdu, offset, length,
+                    header.getDataCodingSchemeStructedData());
+            language = p.first;
+            sb.append(p.second);
         }
+        return new Pair(language, sb.toString());
+
     }
 
     /**
-     * Unpack body text from the pdu using the given encoding, position and
-     * length within the pdu
+     * Parse and unpack the GSM body text according to the encoding in the data coding scheme.
+     * @param header the message header to use
+     * @param pdu the PDU to decode
+     * @return a pair of string containing the language and body of the message in order
+     */
+    private static Pair<String, String> parseGsmBody(SmsCbHeader header, byte[] pdu) {
+        // Payload is one single page
+        int offset = SmsCbHeader.PDU_HEADER_LENGTH;
+        int length = pdu.length - offset;
+        return unpackBody(pdu, offset, length, header.getDataCodingSchemeStructedData());
+    }
+
+    /**
+     * Unpack body text from the pdu using the given encoding, position and length within the pdu.
      *
      * @param pdu The pdu
-     * @param encoding The encoding, as derived from the DCS
      * @param offset Position of the first byte to unpack
      * @param length Number of bytes to unpack
-     * @param hasLanguageIndicator true if the body text is preceded by a
-     *            language indicator. If so, this method will as a side-effect
-     *            assign the extracted language code into mLanguage
-     * @param language the language to return if hasLanguageIndicator is false
+     * @param dcs data coding scheme
      * @return a Pair of Strings containing the language and body of the message
      */
-    private static Pair<String, String> unpackBody(byte[] pdu, int encoding, int offset, int length,
-            boolean hasLanguageIndicator, String language) {
+    private static Pair<String, String> unpackBody(byte[] pdu, int offset, int length,
+            DataCodingScheme dcs) {
         String body = null;
 
-        switch (encoding) {
+        String language = dcs.language;
+        switch (dcs.encoding) {
             case SmsConstants.ENCODING_7BIT:
                 body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
 
-                if (hasLanguageIndicator && body != null && body.length() > 2) {
+                if (dcs.hasLanguageIndicator && body != null && body.length() > 2) {
                     // Language is two GSM characters followed by a CR.
                     // The actual body text is offset by 3 characters.
                     language = body.substring(0, 2);
@@ -296,7 +347,7 @@
                 break;
 
             case SmsConstants.ENCODING_16BIT:
-                if (hasLanguageIndicator && pdu.length >= offset + 2) {
+                if (dcs.hasLanguageIndicator && pdu.length >= offset + 2) {
                     // Language is two GSM characters.
                     // The actual body text is offset by 2 bytes.
                     language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
@@ -330,4 +381,105 @@
 
         return new Pair<String, String>(language, body);
     }
+
+    /** A class use to facilitate the processing of bits stream data. */
+    private static final class BitStreamReader {
+        /** The bits stream represent by a bytes array. */
+        private final byte[] mData;
+
+        /** The offset of the current byte. */
+        private int mCurrentOffset;
+
+        /**
+         * The remained bits of the current byte which have not been read. The most significant
+         * will be read first, so the remained bits are always the least significant bits.
+         */
+        private int mRemainedBit;
+
+        /**
+         * Constructor
+         * @param data bit stream data represent by byte array.
+         * @param offset the offset of the first byte.
+         */
+        BitStreamReader(byte[] data, int offset) {
+            mData = data;
+            mCurrentOffset = offset;
+            mRemainedBit = 8;
+        }
+
+        /**
+         * Read the first {@code count} bits.
+         * @param count the number of bits need to read
+         * @return {@code bits} represent by an 32-bits integer, therefore {@code count} must be no
+         * greater than 32.
+         */
+        public int read(int count) throws IndexOutOfBoundsException {
+            int val = 0;
+            while (count > 0) {
+                if (count >= mRemainedBit) {
+                    val <<= mRemainedBit;
+                    val |= mData[mCurrentOffset] & ((1 << mRemainedBit) - 1);
+                    count -= mRemainedBit;
+                    mRemainedBit = 8;
+                    ++mCurrentOffset;
+                } else {
+                    val <<= count;
+                    val |= (mData[mCurrentOffset] & ((1 << mRemainedBit) - 1))
+                            >> (mRemainedBit - count);
+                    mRemainedBit -= count;
+                    count = 0;
+                }
+            }
+            return val;
+        }
+
+        /**
+         * Skip the current bytes if the remained bits is less than 8. This is useful when
+         * processing the padding or reserved bits.
+         */
+        public void skip() {
+            if (mRemainedBit < 8) {
+                mRemainedBit = 8;
+                ++mCurrentOffset;
+            }
+        }
+    }
+
+    static final class GeoFencingTriggerMessage {
+        /**
+         * Indicate the list of active alerts share their warning area coordinates which means the
+         * broadcast area is the union of the broadcast areas of the active alerts in this list.
+         */
+        public static final int TYPE_ACTIVE_ALERT_SHARE_WAC = 2;
+
+        public final int type;
+        public final List<CellBroadcastIdentity> cbIdentifiers;
+
+        GeoFencingTriggerMessage(int type, @NonNull List<CellBroadcastIdentity> cbIdentifiers) {
+            this.type = type;
+            this.cbIdentifiers = cbIdentifiers;
+        }
+
+        boolean shouldShareBroadcastArea() {
+            return type == TYPE_ACTIVE_ALERT_SHARE_WAC;
+        }
+
+        static final class CellBroadcastIdentity {
+            public final int messageIdentifier;
+            public final int serialNumber;
+            CellBroadcastIdentity(int messageIdentifier, int serialNumber) {
+                this.messageIdentifier = messageIdentifier;
+                this.serialNumber = serialNumber;
+            }
+        }
+
+        @Override
+        public String toString() {
+            String identifiers = cbIdentifiers.stream()
+                    .map(cbIdentifier ->String.format("(msgId = %d, serial = %d)",
+                            cbIdentifier.messageIdentifier, cbIdentifier.serialNumber))
+                    .collect(Collectors.joining(","));
+            return "triggerType=" + type + " identifiers=" + identifiers;
+        }
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
index 541ca8d..5ad2b9d 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
@@ -215,9 +215,11 @@
     public static final int MESSAGE_ID_CMAS_ALERT_STATE_LOCAL_TEST_LANGUAGE
             = 0x112F; // 4399
 
-    /** End of CMAS Message Identifier range (including future extensions). */
-    public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER
-            = 0x112F; // 4399
+    /** CMAS Message Identifier for CMAS geo fencing trigger message. */
+    public static final int MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER = 0x1130; // 4440
+
+    /** End of CMAS Message Identifier range. */
+    public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER = MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER;
 
     /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
     public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
index 0dbc186..acdc838 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
@@ -19,7 +19,10 @@
 import android.telephony.SmsCbCmasInfo;
 import android.telephony.SmsCbEtwsInfo;
 
+import com.android.internal.telephony.SmsConstants;
+
 import java.util.Arrays;
+import java.util.Locale;
 
 /**
  * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by
@@ -32,6 +35,39 @@
  * The raw PDU is no longer sent to SMS CB applications.
  */
 public class SmsCbHeader {
+    /**
+     * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
+     */
+    private static final String[] LANGUAGE_CODES_GROUP_0 = {
+            Locale.GERMAN.getLanguage(),        // German
+            Locale.ENGLISH.getLanguage(),       // English
+            Locale.ITALIAN.getLanguage(),       // Italian
+            Locale.FRENCH.getLanguage(),        // French
+            new Locale("es").getLanguage(),     // Spanish
+            new Locale("nl").getLanguage(),     // Dutch
+            new Locale("sv").getLanguage(),     // Swedish
+            new Locale("da").getLanguage(),     // Danish
+            new Locale("pt").getLanguage(),     // Portuguese
+            new Locale("fi").getLanguage(),     // Finnish
+            new Locale("nb").getLanguage(),     // Norwegian
+            new Locale("el").getLanguage(),     // Greek
+            new Locale("tr").getLanguage(),     // Turkish
+            new Locale("hu").getLanguage(),     // Hungarian
+            new Locale("pl").getLanguage(),     // Polish
+            null
+    };
+
+    /**
+     * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
+     */
+    private static final String[] LANGUAGE_CODES_GROUP_2 = {
+            new Locale("cs").getLanguage(),     // Czech
+            new Locale("he").getLanguage(),     // Hebrew
+            new Locale("ar").getLanguage(),     // Arabic
+            new Locale("ru").getLanguage(),     // Russian
+            new Locale("is").getLanguage(),     // Icelandic
+            null, null, null, null, null, null, null, null, null, null, null
+    };
 
     /**
      * Length of SMS-CB header
@@ -84,6 +120,8 @@
 
     private final int mFormat;
 
+    private DataCodingScheme mDataCodingSchemeStructedData;
+
     /** ETWS warning notification info. */
     private final SmsCbEtwsInfo mEtwsInfo;
 
@@ -162,6 +200,10 @@
             mNrOfPages = 1;
         }
 
+        if (mDataCodingScheme != -1) {
+            mDataCodingSchemeStructedData = new DataCodingScheme(mDataCodingScheme);
+        }
+
         if (isEtwsMessage()) {
             boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
             boolean activatePopup = isEtwsPopupAlert();
@@ -199,6 +241,10 @@
         return mDataCodingScheme;
     }
 
+    DataCodingScheme getDataCodingSchemeStructedData() {
+        return mDataCodingSchemeStructedData;
+    }
+
     int getPageIndex() {
         return mPageIndex;
     }
@@ -448,4 +494,93 @@
                 + ", DCS=0x" + Integer.toHexString(mDataCodingScheme)
                 + ", page " + mPageIndex + " of " + mNrOfPages + '}';
     }
+
+    /**
+     * CBS Data Coding Scheme.
+     * Reference: 3GPP TS 23.038 version 15.0.0 section #5, CBS Data Coding Scheme
+     */
+    public static final class DataCodingScheme {
+        public final int encoding;
+        public final String language;
+        public final boolean hasLanguageIndicator;
+
+        public DataCodingScheme(int dataCodingScheme) {
+            int encoding = 0;
+            String language = null;
+            boolean hasLanguageIndicator = false;
+
+            // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
+            // section 5.
+            switch ((dataCodingScheme & 0xf0) >> 4) {
+                case 0x00:
+                    encoding = SmsConstants.ENCODING_7BIT;
+                    language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
+                    break;
+
+                case 0x01:
+                    hasLanguageIndicator = true;
+                    if ((dataCodingScheme & 0x0f) == 0x01) {
+                        encoding = SmsConstants.ENCODING_16BIT;
+                    } else {
+                        encoding = SmsConstants.ENCODING_7BIT;
+                    }
+                    break;
+
+                case 0x02:
+                    encoding = SmsConstants.ENCODING_7BIT;
+                    language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
+                    break;
+
+                case 0x03:
+                    encoding = SmsConstants.ENCODING_7BIT;
+                    break;
+
+                case 0x04:
+                case 0x05:
+                    switch ((dataCodingScheme & 0x0c) >> 2) {
+                        case 0x01:
+                            encoding = SmsConstants.ENCODING_8BIT;
+                            break;
+
+                        case 0x02:
+                            encoding = SmsConstants.ENCODING_16BIT;
+                            break;
+
+                        case 0x00:
+                        default:
+                            encoding = SmsConstants.ENCODING_7BIT;
+                            break;
+                    }
+                    break;
+
+                case 0x06:
+                case 0x07:
+                    // Compression not supported
+                case 0x09:
+                    // UDH structure not supported
+                case 0x0e:
+                    // Defined by the WAP forum not supported
+                    throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
+                            + dataCodingScheme);
+
+                case 0x0f:
+                    if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
+                        encoding = SmsConstants.ENCODING_8BIT;
+                    } else {
+                        encoding = SmsConstants.ENCODING_7BIT;
+                    }
+                    break;
+
+                default:
+                    // Reserved values are to be treated as 7-bit
+                    encoding = SmsConstants.ENCODING_7BIT;
+                    break;
+            }
+
+
+            this.encoding = encoding;
+            this.language = language;
+            this.hasLanguageIndicator = hasLanguageIndicator;
+        }
+    }
 }
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 1a6c7b5..17e3bac 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -17,7 +17,6 @@
 package com.android.internal.telephony.gsm;
 
 import android.telephony.PhoneNumberUtils;
-import android.text.format.Time;
 import android.telephony.Rlog;
 import android.content.res.Resources;
 import android.text.TextUtils;
@@ -33,6 +32,8 @@
 import java.io.ByteArrayOutputStream;
 import java.io.UnsupportedEncodingException;
 import java.text.ParseException;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 
 import static com.android.internal.telephony.SmsConstants.MessageClass;
 import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
@@ -722,19 +723,21 @@
             int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
 
             timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
-
-            Time time = new Time(Time.TIMEZONE_UTC);
+            // timezoneOffset is in quarter hours.
+            int timeZoneOffsetSeconds = timezoneOffset * 15 * 60;
 
             // It's 2006.  Should I really support years < 2000?
-            time.year = year >= 90 ? year + 1900 : year + 2000;
-            time.month = month - 1;
-            time.monthDay = day;
-            time.hour = hour;
-            time.minute = minute;
-            time.second = second;
-
-            // Timezone offset is in quarter hours.
-            return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
+            int fullYear = year >= 90 ? year + 1900 : year + 2000;
+            LocalDateTime localDateTime = LocalDateTime.of(
+                    fullYear,
+                    month /* 1-12 */,
+                    day,
+                    hour,
+                    minute,
+                    second);
+            long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds;
+            // Convert to milliseconds.
+            return epochSeconds * 1000;
         }
 
         /**
@@ -914,7 +917,7 @@
         CharSequence newMsgBody = null;
         Resources r = Resources.getSystem();
         if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
-            newMsgBody = Sms7BitEncodingTranslator.translate(msgBody, false);
+            newMsgBody = Sms7BitEncodingTranslator.translate(msgBody, false /* isCdmaFormat */);
         }
         if (TextUtils.isEmpty(newMsgBody)) {
             newMsgBody = msgBody;
@@ -1215,6 +1218,7 @@
 
         int encodingType = ENCODING_UNKNOWN;
 
+        Resources r = Resources.getSystem();
         // Look up the data encoding scheme
         if ((mDataCodingScheme & 0x80) == 0) {
             userDataCompressed = (0 != (mDataCodingScheme & 0x20));
@@ -1236,7 +1240,6 @@
                 case 1: // 8 bit data
                     //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
                     //that's stored in 8-bit unpacked format) characters.
-                    Resources r = Resources.getSystem();
                     if (r.getBoolean(com.android.internal.
                             R.bool.config_sms_decode_gsm_8bit_data)) {
                         encodingType = ENCODING_8BIT;
@@ -1246,7 +1249,8 @@
                 case 3: // reserved
                     Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
                             + (mDataCodingScheme & 0xff));
-                    encodingType = ENCODING_8BIT;
+                    encodingType = r.getInteger(
+                            com.android.internal.R.integer.default_reserved_data_coding_scheme);
                     break;
                 }
             }
@@ -1400,7 +1404,6 @@
         case ENCODING_8BIT:
             //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
             //that's stored in 8-bit unpacked format) characters.
-            Resources r = Resources.getSystem();
             if (r.getBoolean(com.android.internal.
                     R.bool.config_sms_decode_gsm_8bit_data)) {
                 mMessageBody = p.getUserDataGSM8bit(count);
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 8aa0aaf..69c296e 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -25,7 +25,7 @@
     srcs: ["src/**/*.java"],
 
     errorprone: {
-      javacflags: ["-Xep:DepAnn:ERROR"],
+        javacflags: ["-Xep:DepAnn:ERROR"],
     },
 
     hostdex: true,
@@ -96,3 +96,14 @@
     ],
 }
 
+// Make the current.txt available for use by the cts/tests/signature tests.
+// ========================================================================
+filegroup {
+    name: "android-test-base-current.txt",
+    visibility: [
+        "//cts/tests/signature/api",
+    ],
+    srcs: [
+        "api/current.txt",
+    ],
+}
diff --git a/test-base/Android.mk b/test-base/Android.mk
deleted file mode 100644
index a9d30cf..0000000
--- a/test-base/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(HOST_OS),linux)
-# Build the legacy-performance-test-hostdex library
-# =================================================
-# This contains the android.test.PerformanceTestCase class only
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := src/android/test/PerformanceTestCase.java
-LOCAL_MODULE := legacy-performance-test-hostdex
-
-include $(BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY)
-endif  # HOST_OS == linux
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index e1d6e01..34ac3dc 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -19,14 +19,25 @@
 java_sdk_library {
     name: "android.test.mock",
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        ":framework-srcs",
+    ],
 
     api_packages: [
         "android.test.mock",
     ],
-
-    srcs_lib: "framework",
-    srcs_lib_whitelist_dirs: ["core/java"],
-    srcs_lib_whitelist_pkgs: ["android"],
     compile_dex: true,
 }
+
+// Make the current.txt available for use by the cts/tests/signature tests.
+// ========================================================================
+filegroup {
+    name: "android-test-mock-current.txt",
+    visibility: [
+        "//cts/tests/signature/api",
+    ],
+    srcs: [
+        "api/current.txt",
+    ],
+}
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 3521202..75f5b5a 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -40,7 +40,7 @@
         "junit.textui",
     ],
 
-    compile_dex: true
+    compile_dex: true,
 }
 
 // Build the android.test.runner-minus-junit library
@@ -86,3 +86,14 @@
     java_version: "1.8",
 }
 
+// Make the current.txt available for use by the cts/tests/signature tests.
+// ========================================================================
+filegroup {
+    name: "android-test-runner-current.txt",
+    visibility: [
+        "//cts/tests/signature/api",
+    ],
+    srcs: [
+        "api/current.txt",
+    ],
+}
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/Android.bp b/tests/BackgroundDexOptServiceIntegrationTests/Android.bp
index 036f845..a85d129 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/Android.bp
+++ b/tests/BackgroundDexOptServiceIntegrationTests/Android.bp
@@ -17,7 +17,7 @@
 android_test {
     name: "BackgroundDexOptServiceIntegrationTests",
     srcs: ["src/**/*.java"],
-    static_libs: ["android-support-test"],
+    static_libs: ["androidx.test.rules"],
     platform_apis: true,
     test_suites: ["device-tests"],
     certificate: "platform",
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml b/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
index afae155..aec9f77 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
+++ b/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
@@ -34,7 +34,7 @@
     </application>
 
     <instrumentation
-        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.frameworks.bgdexopttest"
         android:label="Integration test for BackgroundDexOptService" />
 </manifest>
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/AndroidTest.xml b/tests/BackgroundDexOptServiceIntegrationTests/AndroidTest.xml
index 9bb1e28..a532422 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/AndroidTest.xml
+++ b/tests/BackgroundDexOptServiceIntegrationTests/AndroidTest.xml
@@ -50,6 +50,6 @@
     <option name="test-tag" value="BackgroundDexOptServiceIntegrationTests"/>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="com.android.frameworks.bgdexopttest"/>
-        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
     </test>
 </configuration>
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
index e247951..7d826f7 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
+++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
@@ -19,13 +19,14 @@
 import android.app.AlarmManager;
 import android.content.Context;
 import android.os.Environment;
+import android.os.ParcelFileDescriptor;
 import android.os.SystemProperties;
 import android.os.storage.StorageManager;
-import android.support.test.InstrumentationRegistry;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -34,6 +35,7 @@
 import org.junit.runners.JUnit4;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -141,27 +143,19 @@
     // Run the command and return the stdout.
     private static String runShellCommand(String cmd) throws IOException {
         Log.i(TAG, String.format("running command: '%s'", cmd));
-        long startTime = System.nanoTime();
-        Process p = Runtime.getRuntime().exec(cmd);
-        int res;
-        try {
-            res = p.waitFor();
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
+        ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .executeShellCommand(cmd);
+        byte[] buf = new byte[512];
+        int bytesRead;
+        FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+        StringBuilder stdout = new StringBuilder();
+        while ((bytesRead = fis.read(buf)) != -1) {
+            stdout.append(new String(buf, 0, bytesRead));
         }
-        String stdout = inputStreamToString(p.getInputStream());
-        String stderr = inputStreamToString(p.getErrorStream());
-        long elapsedTime = System.nanoTime() - startTime;
-        Log.i(TAG, String.format("ran command: '%s' in %d ms with return code %d", cmd,
-                TimeUnit.NANOSECONDS.toMillis(elapsedTime), res));
+        fis.close();
         Log.i(TAG, "stdout");
-        Log.i(TAG, stdout);
-        Log.i(TAG, "stderr");
-        Log.i(TAG, stderr);
-        if (res != 0) {
-            throw new RuntimeException(String.format("failed command: '%s'", cmd));
-        }
-        return stdout;
+        Log.i(TAG, stdout.toString());
+        return stdout.toString();
     }
 
     // Run the command and return the stdout split by lines.
@@ -209,7 +203,10 @@
 
     // TODO(aeubanks): figure out how to get scheduled bg-dexopt to run
     private static void runBackgroundDexOpt() throws IOException {
-        runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME);
+        String result = runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME);
+        if (!result.trim().equals("Success")) {
+            throw new IllegalStateException("Expected command success, received >" + result + "<");
+        }
     }
 
     // Set the time ahead of the last use time of the test app in days.
diff --git a/tests/CanvasCompare/res/drawable/sunset1.jpg b/tests/CanvasCompare/res/drawable/sunset1.jpg
index 92851f3..3b4e056 100644
--- a/tests/CanvasCompare/res/drawable/sunset1.jpg
+++ b/tests/CanvasCompare/res/drawable/sunset1.jpg
Binary files differ
diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rscript
similarity index 100%
rename from tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs
rename to tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rscript
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
index d8b3b20..460022e 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -28,6 +28,7 @@
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -108,6 +109,7 @@
     }
 
     @Test
+    @Ignore  // Should invoke shell command via UiAutomation: b/137574238
     public void testDexLoggerReconcileGeneratesEvents() throws Exception {
         int[] tagList = new int[] { SNET_TAG };
         List<EventLog.Event> events = new ArrayList<>();
diff --git a/tests/FrameworkPerf/res/drawable-161dpi/wallpaper_goldengate_scale.jpg b/tests/FrameworkPerf/res/drawable-161dpi/wallpaper_goldengate_scale.jpg
index 2271091..8b7c6db 100644
--- a/tests/FrameworkPerf/res/drawable-161dpi/wallpaper_goldengate_scale.jpg
+++ b/tests/FrameworkPerf/res/drawable-161dpi/wallpaper_goldengate_scale.jpg
Binary files differ
diff --git a/tests/FrameworkPerf/res/drawable-nodpi/wallpaper_goldengate.jpg b/tests/FrameworkPerf/res/drawable-nodpi/wallpaper_goldengate.jpg
index 2271091..8b7c6db 100644
--- a/tests/FrameworkPerf/res/drawable-nodpi/wallpaper_goldengate.jpg
+++ b/tests/FrameworkPerf/res/drawable-nodpi/wallpaper_goldengate.jpg
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg b/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg
index 92851f3..086c055 100644
--- a/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg
+++ b/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg b/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg
index 7f047b1..6e1a866 100644
--- a/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg
+++ b/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable/sunset1.jpg b/tests/HwAccelerationTest/res/drawable/sunset1.jpg
index 92851f3..3b4e056 100644
--- a/tests/HwAccelerationTest/res/drawable/sunset1.jpg
+++ b/tests/HwAccelerationTest/res/drawable/sunset1.jpg
Binary files differ
diff --git a/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg b/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg
index 755232d..14d6027 100644
--- a/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg
+++ b/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg
Binary files differ
diff --git a/tests/UiBench/res/drawable-nodpi/frantic.jpg b/tests/UiBench/res/drawable-nodpi/frantic.jpg
index 4c62333..856b419 100644
--- a/tests/UiBench/res/drawable-nodpi/frantic.jpg
+++ b/tests/UiBench/res/drawable-nodpi/frantic.jpg
Binary files differ
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 1fbb658..502aa97 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -3,22 +3,6 @@
 //########################################################################
 java_defaults {
     name: "FrameworksNetTests-jni-defaults",
-    static_libs: [
-        "FrameworksNetCommonTests",
-        "frameworks-base-testutils",
-        "frameworks-net-testutils",
-        "framework-protos",
-        "androidx.test.rules",
-        "mockito-target-minus-junit4",
-        "platform-test-annotations",
-        "services.core",
-        "services.net",
-    ],
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-        "android.test.mock",
-    ],
     jni_libs: [
         "ld-android",
         "libartbase",
@@ -44,20 +28,20 @@
         "libnativehelper",
         "libnetdbpf",
         "libnetdutils",
+        "libnetworkstatsfactorytestjni",
         "libpackagelistparser",
         "libpcre2",
         "libprocessgroup",
         "libselinux",
-        "libui",
-        "libutils",
-        "libvndksupport",
         "libtinyxml2",
+        "libui",
         "libunwindstack",
+        "libutils",
         "libutilscallstack",
+        "libvndksupport",
         "libziparchive",
         "libz",
-        "netd_aidl_interface-cpp",
-        "libnetworkstatsfactorytestjni",
+        "netd_aidl_interface-V2-cpp",
     ],
 }
 
@@ -68,4 +52,21 @@
     platform_apis: true,
     test_suites: ["device-tests"],
     certificate: "platform",
+    static_libs: [
+        "androidx.test.rules",
+        "FrameworksNetCommonTests",
+        "frameworks-base-testutils",
+        "frameworks-net-integration-testutils",
+        "framework-protos",
+        "mockito-target-minus-junit4",
+        "net-tests-utils",
+        "platform-test-annotations",
+        "services.core",
+        "services.net",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
 }
diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp
index db1ccb4..e44d460 100644
--- a/tests/net/common/Android.bp
+++ b/tests/net/common/Android.bp
@@ -21,12 +21,12 @@
     srcs: ["java/**/*.java", "java/**/*.kt"],
     static_libs: [
         "androidx.test.rules",
-        "frameworks-net-testutils",
         "junit",
         "mockito-target-minus-junit4",
+        "net-tests-utils",
         "platform-test-annotations",
     ],
     libs: [
         "android.test.base.stubs",
     ],
-}
\ No newline at end of file
+}
diff --git a/tests/net/common/java/android/net/IpPrefixTest.java b/tests/net/common/java/android/net/IpPrefixTest.java
index 719960d..985e10d 100644
--- a/tests/net/common/java/android/net/IpPrefixTest.java
+++ b/tests/net/common/java/android/net/IpPrefixTest.java
@@ -16,16 +16,18 @@
 
 package android.net;
 
+import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
 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.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.os.Parcel;
-
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -171,56 +173,46 @@
 
     }
 
-    private void assertAreEqual(Object o1, Object o2) {
-        assertTrue(o1.equals(o2));
-        assertTrue(o2.equals(o1));
-    }
-
-    private void assertAreNotEqual(Object o1, Object o2) {
-        assertFalse(o1.equals(o2));
-        assertFalse(o2.equals(o1));
-    }
-
     @Test
     public void testEquals() {
         IpPrefix p1, p2;
 
         p1 = new IpPrefix("192.0.2.251/23");
         p2 = new IpPrefix(new byte[]{(byte) 192, (byte) 0, (byte) 2, (byte) 251}, 23);
-        assertAreEqual(p1, p2);
+        assertEqualBothWays(p1, p2);
 
         p1 = new IpPrefix("192.0.2.5/23");
-        assertAreEqual(p1, p2);
+        assertEqualBothWays(p1, p2);
 
         p1 = new IpPrefix("192.0.2.5/24");
-        assertAreNotEqual(p1, p2);
+        assertNotEqualEitherWay(p1, p2);
 
         p1 = new IpPrefix("192.0.4.5/23");
-        assertAreNotEqual(p1, p2);
+        assertNotEqualEitherWay(p1, p2);
 
 
         p1 = new IpPrefix("2001:db8:dead:beef:f00::80/122");
         p2 = new IpPrefix(IPV6_BYTES, 122);
         assertEquals("2001:db8:dead:beef:f00::80/122", p2.toString());
-        assertAreEqual(p1, p2);
+        assertEqualBothWays(p1, p2);
 
         p1 = new IpPrefix("2001:db8:dead:beef:f00::bf/122");
-        assertAreEqual(p1, p2);
+        assertEqualBothWays(p1, p2);
 
         p1 = new IpPrefix("2001:db8:dead:beef:f00::8:0/123");
-        assertAreNotEqual(p1, p2);
+        assertNotEqualEitherWay(p1, p2);
 
         p1 = new IpPrefix("2001:db8:dead:beef::/122");
-        assertAreNotEqual(p1, p2);
+        assertNotEqualEitherWay(p1, p2);
 
         // 192.0.2.4/32 != c000:0204::/32.
         byte[] ipv6bytes = new byte[16];
         System.arraycopy(IPV4_BYTES, 0, ipv6bytes, 0, IPV4_BYTES.length);
         p1 = new IpPrefix(ipv6bytes, 32);
-        assertAreEqual(p1, new IpPrefix("c000:0204::/32"));
+        assertEqualBothWays(p1, new IpPrefix("c000:0204::/32"));
 
         p2 = new IpPrefix(IPV4_BYTES, 32);
-        assertAreNotEqual(p1, p2);
+        assertNotEqualEitherWay(p1, p2);
     }
 
     @Test
@@ -356,25 +348,6 @@
         assertEquals(InetAddress.parseNumericAddress("192.0.2.0"), p.getAddress());
     }
 
-    public IpPrefix passThroughParcel(IpPrefix p) {
-        Parcel parcel = Parcel.obtain();
-        IpPrefix p2 = null;
-        try {
-            p.writeToParcel(parcel, 0);
-            parcel.setDataPosition(0);
-            p2 = IpPrefix.CREATOR.createFromParcel(parcel);
-        } finally {
-            parcel.recycle();
-        }
-        assertNotNull(p2);
-        return p2;
-    }
-
-    public void assertParcelingIsLossless(IpPrefix p) {
-        IpPrefix p2 = passThroughParcel(p);
-        assertEquals(p, p2);
-    }
-
     @Test
     public void testParceling() {
         IpPrefix p;
@@ -386,5 +359,7 @@
         p = new IpPrefix("192.0.2.0/25");
         assertParcelingIsLossless(p);
         assertTrue(p.isIPv4());
+
+        assertFieldCountEquals(2, IpPrefix.class);
     }
 }
diff --git a/tests/net/common/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java
index d462441b..b2e573b 100644
--- a/tests/net/common/java/android/net/LinkAddressTest.java
+++ b/tests/net/common/java/android/net/LinkAddressTest.java
@@ -27,15 +27,17 @@
 import static android.system.OsConstants.RT_SCOPE_SITE;
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
+import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
+import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.os.Parcel;
-
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -217,67 +219,56 @@
                 l1.isSameAddressAs(l2));
     }
 
-    private void assertLinkAddressesEqual(LinkAddress l1, LinkAddress l2) {
-        assertTrue(l1 + " unexpectedly not equal to " + l2, l1.equals(l2));
-        assertTrue(l2 + " unexpectedly not equal to " + l1, l2.equals(l1));
-        assertEquals(l1.hashCode(), l2.hashCode());
-    }
-
-    private void assertLinkAddressesNotEqual(LinkAddress l1, LinkAddress l2) {
-        assertFalse(l1 + " unexpectedly equal to " + l2, l1.equals(l2));
-        assertFalse(l2 + " unexpectedly equal to " + l1, l2.equals(l1));
-    }
-
     @Test
     public void testEqualsAndSameAddressAs() {
         LinkAddress l1, l2, l3;
 
         l1 = new LinkAddress("2001:db8::1/64");
         l2 = new LinkAddress("2001:db8::1/64");
-        assertLinkAddressesEqual(l1, l2);
+        assertEqualBothWays(l1, l2);
         assertIsSameAddressAs(l1, l2);
 
         l2 = new LinkAddress("2001:db8::1/65");
-        assertLinkAddressesNotEqual(l1, l2);
+        assertNotEqualEitherWay(l1, l2);
         assertIsNotSameAddressAs(l1, l2);
 
         l2 = new LinkAddress("2001:db8::2/64");
-        assertLinkAddressesNotEqual(l1, l2);
+        assertNotEqualEitherWay(l1, l2);
         assertIsNotSameAddressAs(l1, l2);
 
 
         l1 = new LinkAddress("192.0.2.1/24");
         l2 = new LinkAddress("192.0.2.1/24");
-        assertLinkAddressesEqual(l1, l2);
+        assertEqualBothWays(l1, l2);
         assertIsSameAddressAs(l1, l2);
 
         l2 = new LinkAddress("192.0.2.1/23");
-        assertLinkAddressesNotEqual(l1, l2);
+        assertNotEqualEitherWay(l1, l2);
         assertIsNotSameAddressAs(l1, l2);
 
         l2 = new LinkAddress("192.0.2.2/24");
-        assertLinkAddressesNotEqual(l1, l2);
+        assertNotEqualEitherWay(l1, l2);
         assertIsNotSameAddressAs(l1, l2);
 
 
         // Check equals() and isSameAddressAs() on identical addresses with different flags.
         l1 = new LinkAddress(V6_ADDRESS, 64);
         l2 = new LinkAddress(V6_ADDRESS, 64, 0, RT_SCOPE_UNIVERSE);
-        assertLinkAddressesEqual(l1, l2);
+        assertEqualBothWays(l1, l2);
         assertIsSameAddressAs(l1, l2);
 
         l2 = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, RT_SCOPE_UNIVERSE);
-        assertLinkAddressesNotEqual(l1, l2);
+        assertNotEqualEitherWay(l1, l2);
         assertIsSameAddressAs(l1, l2);
 
         // Check equals() and isSameAddressAs() on identical addresses with different scope.
         l1 = new LinkAddress(V4_ADDRESS, 24);
         l2 = new LinkAddress(V4_ADDRESS, 24, 0, RT_SCOPE_UNIVERSE);
-        assertLinkAddressesEqual(l1, l2);
+        assertEqualBothWays(l1, l2);
         assertIsSameAddressAs(l1, l2);
 
         l2 = new LinkAddress(V4_ADDRESS, 24, 0, RT_SCOPE_HOST);
-        assertLinkAddressesNotEqual(l1, l2);
+        assertNotEqualEitherWay(l1, l2);
         assertIsSameAddressAs(l1, l2);
 
         // Addresses with the same start or end bytes aren't equal between families.
@@ -291,10 +282,10 @@
         assertTrue(Arrays.equals(ipv4Bytes, l2FirstIPv6Bytes));
         assertTrue(Arrays.equals(ipv4Bytes, l3LastIPv6Bytes));
 
-        assertLinkAddressesNotEqual(l1, l2);
+        assertNotEqualEitherWay(l1, l2);
         assertIsNotSameAddressAs(l1, l2);
 
-        assertLinkAddressesNotEqual(l1, l3);
+        assertNotEqualEitherWay(l1, l3);
         assertIsNotSameAddressAs(l1, l3);
 
         // Because we use InetAddress, an IPv4 address is equal to its IPv4-mapped address.
@@ -302,7 +293,7 @@
         String addressString = V4 + "/24";
         l1 = new LinkAddress(addressString);
         l2 = new LinkAddress("::ffff:" + addressString);
-        assertLinkAddressesEqual(l1, l2);
+        assertEqualBothWays(l1, l2);
         assertIsSameAddressAs(l1, l2);
     }
 
@@ -319,25 +310,6 @@
         assertNotEquals(l1.hashCode(), l2.hashCode());
     }
 
-    private LinkAddress passThroughParcel(LinkAddress l) {
-        Parcel p = Parcel.obtain();
-        LinkAddress l2 = null;
-        try {
-            l.writeToParcel(p, 0);
-            p.setDataPosition(0);
-            l2 = LinkAddress.CREATOR.createFromParcel(p);
-        } finally {
-            p.recycle();
-        }
-        assertNotNull(l2);
-        return l2;
-    }
-
-    private void assertParcelingIsLossless(LinkAddress l) {
-      LinkAddress l2 = passThroughParcel(l);
-      assertEquals(l, l2);
-    }
-
     @Test
     public void testParceling() {
         LinkAddress l;
@@ -346,7 +318,7 @@
         assertParcelingIsLossless(l);
 
         l = new LinkAddress(V4 + "/28", IFA_F_PERMANENT, RT_SCOPE_LINK);
-        assertParcelingIsLossless(l);
+        assertParcelSane(l, 4);
     }
 
     private void assertGlobalPreferred(LinkAddress l, String msg) {
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index e1c4238..b0464d9 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -31,8 +33,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.TestUtils;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -942,13 +942,13 @@
 
         source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
 
-        TestUtils.assertParcelingIsLossless(source);
+        assertParcelingIsLossless(source);
     }
 
     @Test
     public void testParcelUninitialized() throws Exception {
         LinkProperties empty = new LinkProperties();
-        TestUtils.assertParcelingIsLossless(empty);
+        assertParcelingIsLossless(empty);
     }
 
     @Test
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 6bc7c1b..2ca0d1a 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -38,6 +38,9 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
 
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -45,7 +48,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
@@ -267,9 +269,9 @@
             .setUids(uids)
             .addCapability(NET_CAPABILITY_EIMS)
             .addCapability(NET_CAPABILITY_NOT_METERED);
-        assertEqualsThroughMarshalling(netCap);
+        assertParcelingIsLossless(netCap);
         netCap.setSSID(TEST_SSID);
-        assertEqualsThroughMarshalling(netCap);
+        assertParcelSane(netCap, 11);
     }
 
     @Test
@@ -542,18 +544,6 @@
         nc1.combineCapabilities(nc3);
     }
 
-    private void assertEqualsThroughMarshalling(NetworkCapabilities netCap) {
-        Parcel p = Parcel.obtain();
-        netCap.writeToParcel(p, /* flags */ 0);
-        p.setDataPosition(0);
-        byte[] marshalledData = p.marshall();
-
-        p = Parcel.obtain();
-        p.unmarshall(marshalledData, 0, marshalledData.length);
-        p.setDataPosition(0);
-        assertEquals(NetworkCapabilities.CREATOR.createFromParcel(p), netCap);
-    }
-
     @Test
     public void testSet() {
         NetworkCapabilities nc1 = new NetworkCapabilities();
diff --git a/tests/net/common/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java
index 38bc744..11d44b8 100644
--- a/tests/net/common/java/android/net/NetworkTest.java
+++ b/tests/net/common/java/android/net/NetworkTest.java
@@ -18,13 +18,10 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.net.LocalServerSocket;
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-import android.net.Network;
 import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.filters.SmallTest;
@@ -40,7 +37,6 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.SocketException;
-import java.util.Objects;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -123,29 +119,29 @@
         Network three = new Network(3);
 
         // None of the hashcodes are zero.
-        assertNotEqual(0, one.hashCode());
-        assertNotEqual(0, two.hashCode());
-        assertNotEqual(0, three.hashCode());
+        assertNotEquals(0, one.hashCode());
+        assertNotEquals(0, two.hashCode());
+        assertNotEquals(0, three.hashCode());
 
         // All the hashcodes are distinct.
-        assertNotEqual(one.hashCode(), two.hashCode());
-        assertNotEqual(one.hashCode(), three.hashCode());
-        assertNotEqual(two.hashCode(), three.hashCode());
+        assertNotEquals(one.hashCode(), two.hashCode());
+        assertNotEquals(one.hashCode(), three.hashCode());
+        assertNotEquals(two.hashCode(), three.hashCode());
 
         // None of the handles are zero.
-        assertNotEqual(0, one.getNetworkHandle());
-        assertNotEqual(0, two.getNetworkHandle());
-        assertNotEqual(0, three.getNetworkHandle());
+        assertNotEquals(0, one.getNetworkHandle());
+        assertNotEquals(0, two.getNetworkHandle());
+        assertNotEquals(0, three.getNetworkHandle());
 
         // All the handles are distinct.
-        assertNotEqual(one.getNetworkHandle(), two.getNetworkHandle());
-        assertNotEqual(one.getNetworkHandle(), three.getNetworkHandle());
-        assertNotEqual(two.getNetworkHandle(), three.getNetworkHandle());
+        assertNotEquals(one.getNetworkHandle(), two.getNetworkHandle());
+        assertNotEquals(one.getNetworkHandle(), three.getNetworkHandle());
+        assertNotEquals(two.getNetworkHandle(), three.getNetworkHandle());
 
         // The handles are not equal to the hashcodes.
-        assertNotEqual(one.hashCode(), one.getNetworkHandle());
-        assertNotEqual(two.hashCode(), two.getNetworkHandle());
-        assertNotEqual(three.hashCode(), three.getNetworkHandle());
+        assertNotEquals(one.hashCode(), one.getNetworkHandle());
+        assertNotEquals(two.hashCode(), two.getNetworkHandle());
+        assertNotEquals(three.hashCode(), three.getNetworkHandle());
 
         // Adjust as necessary to test an implementation's specific constants.
         // When running with runtest, "adb logcat -s TestRunner" can be useful.
@@ -154,15 +150,11 @@
         assertEquals(16290598925L, three.getNetworkHandle());
     }
 
-    private static <T> void assertNotEqual(T t1, T t2) {
-        assertFalse(Objects.equals(t1, t2));
-    }
-
     @Test
     public void testGetPrivateDnsBypassingCopy() {
         final Network copy = mNetwork.getPrivateDnsBypassingCopy();
         assertEquals(mNetwork.netId, copy.netId);
-        assertNotEqual(copy.netId, copy.getNetIdForResolv());
-        assertNotEqual(mNetwork.getNetIdForResolv(), copy.getNetIdForResolv());
+        assertNotEquals(copy.netId, copy.getNetIdForResolv());
+        assertNotEquals(mNetwork.getNetIdForResolv(), copy.getNetIdForResolv());
     }
 }
diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java
index 2edbd40..5ce8436 100644
--- a/tests/net/common/java/android/net/RouteInfoTest.java
+++ b/tests/net/common/java/android/net/RouteInfoTest.java
@@ -18,7 +18,11 @@
 
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
-import android.os.Parcel;
+import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
+import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
 import android.test.suitebuilder.annotation.SmallTest;
 
 import junit.framework.TestCase;
@@ -109,47 +113,37 @@
         assertFalse(ipv4Default.matches(Address("2001:db8::f00")));
     }
 
-    private void assertAreEqual(Object o1, Object o2) {
-        assertTrue(o1.equals(o2));
-        assertTrue(o2.equals(o1));
-    }
-
-    private void assertAreNotEqual(Object o1, Object o2) {
-        assertFalse(o1.equals(o2));
-        assertFalse(o2.equals(o1));
-    }
-
     public void testEquals() {
         // IPv4
         RouteInfo r1 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0");
         RouteInfo r2 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0");
-        assertAreEqual(r1, r2);
+        assertEqualBothWays(r1, r2);
 
         RouteInfo r3 = new RouteInfo(Prefix("2001:db8:ace::/49"), Address("2001:db8::1"), "wlan0");
         RouteInfo r4 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::2"), "wlan0");
         RouteInfo r5 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "rmnet0");
-        assertAreNotEqual(r1, r3);
-        assertAreNotEqual(r1, r4);
-        assertAreNotEqual(r1, r5);
+        assertNotEqualEitherWay(r1, r3);
+        assertNotEqualEitherWay(r1, r4);
+        assertNotEqualEitherWay(r1, r5);
 
         // IPv6
         r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0");
         r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0");
-        assertAreEqual(r1, r2);
+        assertEqualBothWays(r1, r2);
 
         r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0");
         r4 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.2"), "wlan0");
         r5 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "rmnet0");
-        assertAreNotEqual(r1, r3);
-        assertAreNotEqual(r1, r4);
-        assertAreNotEqual(r1, r5);
+        assertNotEqualEitherWay(r1, r3);
+        assertNotEqualEitherWay(r1, r4);
+        assertNotEqualEitherWay(r1, r5);
 
         // Interfaces (but not destinations or gateways) can be null.
         r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null);
         r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null);
         r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0");
-        assertAreEqual(r1, r2);
-        assertAreNotEqual(r1, r3);
+        assertEqualBothWays(r1, r2);
+        assertNotEqualEitherWay(r1, r3);
     }
 
     public void testHostAndDefaultRoutes() {
@@ -257,25 +251,6 @@
       // No exceptions? Good.
     }
 
-    public RouteInfo passThroughParcel(RouteInfo r) {
-        Parcel p = Parcel.obtain();
-        RouteInfo r2 = null;
-        try {
-            r.writeToParcel(p, 0);
-            p.setDataPosition(0);
-            r2 = RouteInfo.CREATOR.createFromParcel(p);
-        } finally {
-            p.recycle();
-        }
-        assertNotNull(r2);
-        return r2;
-    }
-
-    public void assertParcelingIsLossless(RouteInfo r) {
-      RouteInfo r2 = passThroughParcel(r);
-      assertEquals(r, r2);
-    }
-
     public void testParceling() {
         RouteInfo r;
 
@@ -283,6 +258,6 @@
         assertParcelingIsLossless(r);
 
         r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
-        assertParcelingIsLossless(r);
+        assertParcelSane(r, 6);
     }
 }
diff --git a/tests/net/common/java/android/net/StaticIpConfigurationTest.java b/tests/net/common/java/android/net/StaticIpConfigurationTest.java
index 5096be2..b5f23bf 100644
--- a/tests/net/common/java/android/net/StaticIpConfigurationTest.java
+++ b/tests/net/common/java/android/net/StaticIpConfigurationTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -34,7 +35,6 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Objects;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -61,10 +61,6 @@
         assertEquals(0, s.dnsServers.size());
     }
 
-    private static <T> void assertNotEquals(T t1, T t2) {
-        assertFalse(Objects.equals(t1, t2));
-    }
-
     private StaticIpConfiguration makeTestObject() {
         StaticIpConfiguration s = new StaticIpConfiguration();
         s.ipAddress = ADDR;
diff --git a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
index 0ce7c91..f4f804a 100644
--- a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
@@ -16,6 +16,8 @@
 
 package android.net.apf;
 
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -24,9 +26,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.ParcelableTestUtil;
-import com.android.internal.util.TestUtils;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,9 +39,7 @@
         assertEquals(456, caps.maximumApfProgramSize);
         assertEquals(789, caps.apfPacketFormat);
 
-        ParcelableTestUtil.assertFieldCountEquals(3, ApfCapabilities.class);
-
-        TestUtils.assertParcelingIsLossless(caps);
+        assertParcelSane(caps, 3);
     }
 
     @Test
diff --git a/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt b/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt
index 8d055c9..0b7b740 100644
--- a/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt
+++ b/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt
@@ -16,11 +16,9 @@
 
 package android.net.metrics;
 
-import android.os.Parcelable
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.util.ParcelableTestUtil
-import com.android.internal.util.TestUtils
+import com.android.testutils.assertParcelSane
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -30,11 +28,6 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class ApfProgramEventTest {
-    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
-        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
-        TestUtils.assertParcelingIsLossless(obj)
-    }
-
     private infix fun Int.hasFlag(flag: Int) = (this and (1 shl flag)) != 0
 
     @Test
@@ -55,7 +48,7 @@
         assertEquals(5, apfProgramEvent.programLength)
         assertEquals(ApfProgramEvent.flagsFor(true, true), apfProgramEvent.flags)
 
-        testParcel(apfProgramEvent, 6)
+        assertParcelSane(apfProgramEvent, 6)
     }
 
     @Test
diff --git a/tests/net/common/java/android/net/metrics/ApfStatsTest.kt b/tests/net/common/java/android/net/metrics/ApfStatsTest.kt
index f8eb40c..46a8c8e 100644
--- a/tests/net/common/java/android/net/metrics/ApfStatsTest.kt
+++ b/tests/net/common/java/android/net/metrics/ApfStatsTest.kt
@@ -16,11 +16,9 @@
 
 package android.net.metrics
 
-import android.os.Parcelable
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.util.ParcelableTestUtil
-import com.android.internal.util.TestUtils
+import com.android.testutils.assertParcelSane
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -28,11 +26,6 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class ApfStatsTest {
-    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
-        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
-        TestUtils.assertParcelingIsLossless(obj)
-    }
-
     @Test
     fun testBuilderAndParcel() {
         val apfStats = ApfStats.Builder()
@@ -59,6 +52,6 @@
         assertEquals(8, apfStats.programUpdatesAllowingMulticast)
         assertEquals(9, apfStats.maxProgramSize)
 
-        testParcel(apfStats, 10)
+        assertParcelSane(apfStats, 10)
     }
 }
diff --git a/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt
index 36e9f8c..8d7a9c4 100644
--- a/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt
+++ b/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt
@@ -16,11 +16,9 @@
 
 package android.net.metrics
 
-import android.os.Parcelable
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.util.ParcelableTestUtil
-import com.android.internal.util.TestUtils
+import com.android.testutils.assertParcelSane
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -30,11 +28,6 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class DhcpClientEventTest {
-    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
-        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
-        TestUtils.assertParcelingIsLossless(obj)
-    }
-
     @Test
     fun testBuilderAndParcel() {
         val dhcpClientEvent = DhcpClientEvent.Builder()
@@ -45,6 +38,6 @@
         assertEquals(FAKE_MESSAGE, dhcpClientEvent.msg)
         assertEquals(Integer.MAX_VALUE, dhcpClientEvent.durationMs)
 
-        testParcel(dhcpClientEvent, 2)
+        assertParcelSane(dhcpClientEvent, 2)
     }
 }
diff --git a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
index e9d5e6d..236f72e 100644
--- a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
+++ b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
@@ -1,10 +1,10 @@
 package android.net.metrics
 
-import android.net.metrics.DhcpErrorEvent.errorCodeWithOption
 import android.net.metrics.DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH
+import android.net.metrics.DhcpErrorEvent.errorCodeWithOption
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.util.TestUtils.parcelingRoundTrip
+import com.android.testutils.parcelingRoundTrip
 import java.lang.reflect.Modifier
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
@@ -62,4 +62,4 @@
     fun testToString_InvalidErrorCode() {
         assertNotNull(DhcpErrorEvent(TEST_ERROR_CODE).toString())
     }
-}
\ No newline at end of file
+}
diff --git a/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt b/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt
index 5144ca5..64be508 100644
--- a/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt
+++ b/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt
@@ -16,11 +16,9 @@
 
 package android.net.metrics
 
-import android.os.Parcelable
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.util.ParcelableTestUtil
-import com.android.internal.util.TestUtils
+import com.android.testutils.assertParcelSane
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -28,11 +26,6 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class IpManagerEventTest {
-    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
-        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
-        TestUtils.assertParcelingIsLossless(obj)
-    }
-
     @Test
     fun testConstructorAndParcel() {
         (IpManagerEvent.PROVISIONING_OK..IpManagerEvent.ERROR_INTERFACE_NOT_FOUND).forEach {
@@ -40,7 +33,7 @@
             assertEquals(it, ipManagerEvent.eventType)
             assertEquals(Long.MAX_VALUE, ipManagerEvent.durationMs)
 
-            testParcel(ipManagerEvent, 2)
+            assertParcelSane(ipManagerEvent, 2)
         }
     }
 }
diff --git a/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt b/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt
index d76ebf6..55b5e49 100644
--- a/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt
+++ b/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt
@@ -16,11 +16,9 @@
 
 package android.net.metrics
 
-import android.os.Parcelable
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.util.ParcelableTestUtil
-import com.android.internal.util.TestUtils
+import com.android.testutils.assertParcelSane
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -28,18 +26,13 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class IpReachabilityEventTest {
-    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
-        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
-        TestUtils.assertParcelingIsLossless(obj)
-    }
-
     @Test
     fun testConstructorAndParcel() {
         (IpReachabilityEvent.PROBE..IpReachabilityEvent.PROVISIONING_LOST_ORGANIC).forEach {
             val ipReachabilityEvent = IpReachabilityEvent(it)
             assertEquals(it, ipReachabilityEvent.eventType)
 
-            testParcel(ipReachabilityEvent, 1)
+            assertParcelSane(ipReachabilityEvent, 1)
         }
     }
 }
diff --git a/tests/net/common/java/android/net/metrics/NetworkEventTest.kt b/tests/net/common/java/android/net/metrics/NetworkEventTest.kt
index 8b52e81..41430b0 100644
--- a/tests/net/common/java/android/net/metrics/NetworkEventTest.kt
+++ b/tests/net/common/java/android/net/metrics/NetworkEventTest.kt
@@ -16,11 +16,9 @@
 
 package android.net.metrics
 
-import android.os.Parcelable
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.util.ParcelableTestUtil
-import com.android.internal.util.TestUtils
+import com.android.testutils.assertParcelSane
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -28,11 +26,6 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class NetworkEventTest {
-    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
-        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
-        TestUtils.assertParcelingIsLossless(obj)
-    }
-
     @Test
     fun testConstructorAndParcel() {
         (NetworkEvent.NETWORK_CONNECTED..NetworkEvent.NETWORK_PARTIAL_CONNECTIVITY).forEach {
@@ -44,7 +37,7 @@
             assertEquals(it, networkEvent.eventType)
             assertEquals(Long.MAX_VALUE, networkEvent.durationMs)
 
-            testParcel(networkEvent, 2)
+            assertParcelSane(networkEvent, 2)
         }
     }
 }
diff --git a/tests/net/common/java/android/net/metrics/RaEventTest.kt b/tests/net/common/java/android/net/metrics/RaEventTest.kt
index f38d328..d9b7203 100644
--- a/tests/net/common/java/android/net/metrics/RaEventTest.kt
+++ b/tests/net/common/java/android/net/metrics/RaEventTest.kt
@@ -16,11 +16,9 @@
 
 package android.net.metrics
 
-import android.os.Parcelable
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.util.ParcelableTestUtil
-import com.android.internal.util.TestUtils
+import com.android.testutils.assertParcelSane
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -30,11 +28,6 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class RaEventTest {
-    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
-        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
-        TestUtils.assertParcelingIsLossless(obj)
-    }
-
     @Test
     fun testConstructorAndParcel() {
         var raEvent = RaEvent.Builder().build()
@@ -74,6 +67,6 @@
         assertEquals(5, raEvent.rdnssLifetime)
         assertEquals(6, raEvent.dnsslLifetime)
 
-        testParcel(raEvent, 6)
+        assertParcelSane(raEvent, 6)
     }
 }
diff --git a/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt b/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt
index c0cef8f..51c0d41 100644
--- a/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt
+++ b/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt
@@ -16,11 +16,9 @@
 
 package android.net.metrics
 
-import android.os.Parcelable
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.util.ParcelableTestUtil
-import com.android.internal.util.TestUtils
+import com.android.testutils.assertParcelSane
 import java.lang.reflect.Modifier
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
@@ -33,11 +31,6 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class ValidationProbeEventTest {
-    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
-        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
-        TestUtils.assertParcelingIsLossless(obj)
-    }
-
     private infix fun Int.hasType(type: Int) = (type and this) == type
 
     @Test
@@ -58,7 +51,7 @@
         assertTrue(validationProbeEvent.probeType hasType FIRST_VALIDATION)
         assertEquals(ValidationProbeEvent.DNS_SUCCESS, validationProbeEvent.returnCode)
 
-        testParcel(validationProbeEvent, 3)
+        assertParcelSane(validationProbeEvent, 3)
     }
 
     @Test
diff --git a/tests/net/common/java/android/net/util/SocketUtilsTest.kt b/tests/net/common/java/android/net/util/SocketUtilsTest.kt
new file mode 100644
index 0000000..9c7cfb0
--- /dev/null
+++ b/tests/net/common/java/android/net/util/SocketUtilsTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.system.NetlinkSocketAddress
+import android.system.Os
+import android.system.OsConstants.AF_INET
+import android.system.OsConstants.ETH_P_ALL
+import android.system.OsConstants.IPPROTO_UDP
+import android.system.OsConstants.RTMGRP_NEIGH
+import android.system.OsConstants.SOCK_DGRAM
+import android.system.PacketSocketAddress
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_INDEX = 123
+private const val TEST_PORT = 555
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SocketUtilsTest {
+    @Test
+    fun testMakeNetlinkSocketAddress() {
+        val nlAddress = SocketUtils.makeNetlinkSocketAddress(TEST_PORT, RTMGRP_NEIGH)
+        if (nlAddress is NetlinkSocketAddress) {
+            assertEquals(TEST_PORT, nlAddress.getPortId())
+            assertEquals(RTMGRP_NEIGH, nlAddress.getGroupsMask())
+        } else {
+            fail("Not NetlinkSocketAddress object")
+        }
+    }
+
+    @Test
+    fun testMakePacketSocketAddress() {
+        val pkAddress = SocketUtils.makePacketSocketAddress(ETH_P_ALL, TEST_INDEX)
+        assertTrue("Not PacketSocketAddress object", pkAddress is PacketSocketAddress)
+
+        val ff = 0xff.toByte()
+        val pkAddress2 = SocketUtils.makePacketSocketAddress(TEST_INDEX,
+                byteArrayOf(ff, ff, ff, ff, ff, ff))
+        assertTrue("Not PacketSocketAddress object", pkAddress2 is PacketSocketAddress)
+    }
+
+    @Test
+    fun testCloseSocket() {
+        // Expect no exception happening with null object.
+        SocketUtils.closeSocket(null)
+
+        val fd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
+        assertTrue(fd.valid())
+        SocketUtils.closeSocket(fd)
+        assertFalse(fd.valid())
+        // Expecting socket should be still invalid even closed socket again.
+        SocketUtils.closeSocket(fd)
+        assertFalse(fd.valid())
+    }
+}
diff --git a/tests/net/util/Android.bp b/tests/net/deflake/Android.bp
similarity index 70%
copy from tests/net/util/Android.bp
copy to tests/net/deflake/Android.bp
index d8c502d..1c48c74 100644
--- a/tests/net/util/Android.bp
+++ b/tests/net/deflake/Android.bp
@@ -14,17 +14,16 @@
 // limitations under the License.
 //
 
-// Common utilities for network tests.
-java_library {
-    name: "frameworks-net-testutils",
-    srcs: ["java/**/*.java"],
-    // test_current to be also appropriate for CTS tests
-    sdk_version: "test_current",
-    static_libs: [
-        "androidx.annotation_annotation",
-        "junit",
-    ],
+java_test_host {
+    name: "FrameworksNetDeflakeTest",
+    srcs: ["src/**/*.kt"],
     libs: [
-        "android.test.base.stubs",
+        "junit",
+        "tradefed",
     ],
+    static_libs: [
+        "kotlin-test",
+        "net-host-tests-utils",
+    ],
+    data: [":FrameworksNetTests"],
 }
\ No newline at end of file
diff --git a/tests/net/deflake/src/com/android/server/net/FrameworksNetDeflakeTest.kt b/tests/net/deflake/src/com/android/server/net/FrameworksNetDeflakeTest.kt
new file mode 100644
index 0000000..6285525
--- /dev/null
+++ b/tests/net/deflake/src/com/android/server/net/FrameworksNetDeflakeTest.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.net
+
+import com.android.testutils.host.DeflakeHostTestBase
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class FrameworksNetDeflakeTest: DeflakeHostTestBase() {
+    override val runCount = 20
+    override val testApkFilename = "FrameworksNetTests.apk"
+    override val testClasses = listOf("com.android.server.ConnectivityServiceTest")
+}
\ No newline at end of file
diff --git a/tests/net/util/Android.bp b/tests/net/integration/Android.bp
similarity index 72%
rename from tests/net/util/Android.bp
rename to tests/net/integration/Android.bp
index d8c502d..16a68d7 100644
--- a/tests/net/util/Android.bp
+++ b/tests/net/integration/Android.bp
@@ -14,17 +14,18 @@
 // limitations under the License.
 //
 
-// Common utilities for network tests.
+// Utilities for testing framework code both in integration and unit tests.
 java_library {
-    name: "frameworks-net-testutils",
-    srcs: ["java/**/*.java"],
-    // test_current to be also appropriate for CTS tests
-    sdk_version: "test_current",
+    name: "frameworks-net-integration-testutils",
+    srcs: ["util/**/*.java", "util/**/*.kt"],
     static_libs: [
         "androidx.annotation_annotation",
+        "androidx.test.rules",
         "junit",
+        "net-tests-utils",
     ],
     libs: [
-        "android.test.base.stubs",
+        "services.core",
+        "services.net",
     ],
-}
\ No newline at end of file
+}
diff --git a/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt b/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt
new file mode 100644
index 0000000..fa2b99c
--- /dev/null
+++ b/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server
+
+import android.net.ConnectivityManager.TYPE_BLUETOOTH
+import android.net.ConnectivityManager.TYPE_ETHERNET
+import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_NONE
+import android.net.ConnectivityManager.TYPE_TEST
+import android.net.ConnectivityManager.TYPE_VPN
+import android.net.ConnectivityManager.TYPE_WIFI
+import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+
+fun transportToLegacyType(transport: Int) = when (transport) {
+    TRANSPORT_BLUETOOTH -> TYPE_BLUETOOTH
+    TRANSPORT_CELLULAR -> TYPE_MOBILE
+    TRANSPORT_ETHERNET -> TYPE_ETHERNET
+    TRANSPORT_TEST -> TYPE_TEST
+    TRANSPORT_VPN -> TYPE_VPN
+    TRANSPORT_WIFI -> TYPE_WIFI
+    else -> TYPE_NONE
+}
\ No newline at end of file
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
new file mode 100644
index 0000000..1e8d83c
--- /dev/null
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+
+import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkFactory;
+import android.net.NetworkInfo;
+import android.net.NetworkMisc;
+import android.net.NetworkSpecifier;
+import android.net.SocketKeepalive;
+import android.net.UidRange;
+import android.os.ConditionVariable;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.server.connectivity.ConnectivityConstants;
+import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.TestableNetworkCallback;
+
+import java.util.Set;
+
+public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
+    private final NetworkInfo mNetworkInfo;
+    private final NetworkCapabilities mNetworkCapabilities;
+    private final HandlerThread mHandlerThread;
+    private final Context mContext;
+    private final String mLogTag;
+
+    private final ConditionVariable mDisconnected = new ConditionVariable();
+    private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
+    private int mScore;
+    private NetworkAgent mNetworkAgent;
+    private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED;
+    private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
+    private Integer mExpectedKeepaliveSlot = null;
+
+    public NetworkAgentWrapper(int transport, LinkProperties linkProperties, Context context)
+            throws Exception {
+        final int type = transportToLegacyType(transport);
+        final String typeName = ConnectivityManager.getNetworkTypeName(type);
+        mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
+        mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities.addTransportType(transport);
+        switch (transport) {
+            case TRANSPORT_ETHERNET:
+                mScore = 70;
+                break;
+            case TRANSPORT_WIFI:
+                mScore = 60;
+                break;
+            case TRANSPORT_CELLULAR:
+                mScore = 50;
+                break;
+            case TRANSPORT_WIFI_AWARE:
+                mScore = 20;
+                break;
+            case TRANSPORT_VPN:
+                mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
+                mScore = ConnectivityConstants.VPN_DEFAULT_SCORE;
+                break;
+            default:
+                throw new UnsupportedOperationException("unimplemented network type");
+        }
+        mContext = context;
+        mLogTag = "Mock-" + typeName;
+        mHandlerThread = new HandlerThread(mLogTag);
+        mHandlerThread.start();
+
+        mNetworkAgent = makeNetworkAgent(linkProperties);
+    }
+
+    protected InstrumentedNetworkAgent makeNetworkAgent(LinkProperties linkProperties)
+            throws Exception {
+        return new InstrumentedNetworkAgent(this, linkProperties);
+    }
+
+    public static class InstrumentedNetworkAgent extends NetworkAgent {
+        private final NetworkAgentWrapper mWrapper;
+
+        public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp) {
+            super(wrapper.mHandlerThread.getLooper(), wrapper.mContext, wrapper.mLogTag,
+                    wrapper.mNetworkInfo, wrapper.mNetworkCapabilities, lp, wrapper.mScore,
+                    new NetworkMisc(), NetworkFactory.SerialNumber.NONE);
+            mWrapper = wrapper;
+        }
+
+        @Override
+        public void unwanted() {
+            mWrapper.mDisconnected.open();
+        }
+
+        @Override
+        public void startSocketKeepalive(Message msg) {
+            int slot = msg.arg1;
+            if (mWrapper.mExpectedKeepaliveSlot != null) {
+                assertEquals((int) mWrapper.mExpectedKeepaliveSlot, slot);
+            }
+            onSocketKeepaliveEvent(slot, mWrapper.mStartKeepaliveError);
+        }
+
+        @Override
+        public void stopSocketKeepalive(Message msg) {
+            onSocketKeepaliveEvent(msg.arg1, mWrapper.mStopKeepaliveError);
+        }
+
+        @Override
+        protected void preventAutomaticReconnect() {
+            mWrapper.mPreventReconnectReceived.open();
+        }
+
+        @Override
+        protected void addKeepalivePacketFilter(Message msg) {
+            Log.i(mWrapper.mLogTag, "Add keepalive packet filter.");
+        }
+
+        @Override
+        protected void removeKeepalivePacketFilter(Message msg) {
+            Log.i(mWrapper.mLogTag, "Remove keepalive packet filter.");
+        }
+    }
+
+    public void adjustScore(int change) {
+        mScore += change;
+        mNetworkAgent.sendNetworkScore(mScore);
+    }
+
+    public int getScore() {
+        return mScore;
+    }
+
+    public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
+        mNetworkAgent.explicitlySelected(explicitlySelected, acceptUnvalidated);
+    }
+
+    public void addCapability(int capability) {
+        mNetworkCapabilities.addCapability(capability);
+        mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+    }
+
+    public void removeCapability(int capability) {
+        mNetworkCapabilities.removeCapability(capability);
+        mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+    }
+
+    public void setUids(Set<UidRange> uids) {
+        mNetworkCapabilities.setUids(uids);
+        mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+    }
+
+    public void setSignalStrength(int signalStrength) {
+        mNetworkCapabilities.setSignalStrength(signalStrength);
+        mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+    }
+
+    public void setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
+        mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
+        mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+    }
+
+    public void setNetworkCapabilities(NetworkCapabilities nc, boolean sendToConnectivityService) {
+        mNetworkCapabilities.set(nc);
+        if (sendToConnectivityService) {
+            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+        }
+    }
+
+    public void connect() {
+        assertNotEquals("MockNetworkAgents can only be connected once",
+                getNetworkInfo().getDetailedState(), NetworkInfo.DetailedState.CONNECTED);
+        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
+        mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+    }
+
+    public void suspend() {
+        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, null, null);
+        mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+    }
+
+    public void resume() {
+        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
+        mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+    }
+
+    public void disconnect() {
+        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
+        mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+    }
+
+    @Override
+    public Network getNetwork() {
+        return new Network(mNetworkAgent.netId);
+    }
+
+    public void expectPreventReconnectReceived(long timeoutMs) {
+        assertTrue(mPreventReconnectReceived.block(timeoutMs));
+    }
+
+    public void expectDisconnected(long timeoutMs) {
+        assertTrue(mDisconnected.block(timeoutMs));
+    }
+
+    public void sendLinkProperties(LinkProperties lp) {
+        mNetworkAgent.sendLinkProperties(lp);
+    }
+
+    public void setStartKeepaliveEvent(int reason) {
+        mStartKeepaliveError = reason;
+    }
+
+    public void setStopKeepaliveEvent(int reason) {
+        mStopKeepaliveError = reason;
+    }
+
+    public void setExpectedKeepaliveSlot(Integer slot) {
+        mExpectedKeepaliveSlot = slot;
+    }
+
+    public NetworkAgent getNetworkAgent() {
+        return mNetworkAgent;
+    }
+
+    public NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
+    }
+
+    public NetworkCapabilities getNetworkCapabilities() {
+        return mNetworkCapabilities;
+    }
+
+    public void waitForIdle(long timeoutMs) {
+        HandlerUtilsKt.waitForIdle(mHandlerThread, timeoutMs);
+    }
+}
diff --git a/tests/net/integration/util/com/android/server/TestNetIdManager.kt b/tests/net/integration/util/com/android/server/TestNetIdManager.kt
new file mode 100644
index 0000000..eb290dc
--- /dev/null
+++ b/tests/net/integration/util/com/android/server/TestNetIdManager.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server
+
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * A [NetIdManager] that generates ID starting from [NetIdManager.MAX_NET_ID] and decreasing, rather
+ * than starting from [NetIdManager.MIN_NET_ID] and increasing.
+ *
+ * Useful for testing ConnectivityService, to minimize the risk of test ConnectivityService netIDs
+ * overlapping with netIDs used by the real ConnectivityService on the device.
+ *
+ * IDs may still overlap if many networks have been used on the device (so the "real" netIDs
+ * are close to MAX_NET_ID), but this is typically not the case when running unit tests. Also, there
+ * is no way to fully solve the overlap issue without altering ID allocation in non-test code, as
+ * the real ConnectivityService could start using netIds that have been used by the test in the
+ * past.
+ */
+class TestNetIdManager : NetIdManager() {
+    private val nextId = AtomicInteger(MAX_NET_ID)
+    override fun reserveNetId() = nextId.decrementAndGet()
+    override fun releaseNetId(id: Int) = Unit
+}
diff --git a/tests/net/java/android/app/usage/NetworkStatsManagerTest.java b/tests/net/java/android/app/usage/NetworkStatsManagerTest.java
index fd555c1..899295a 100644
--- a/tests/net/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/net/java/android/app/usage/NetworkStatsManagerTest.java
@@ -202,8 +202,7 @@
         assertFalse(stats.hasNextBucket());
     }
 
-    private void assertBucketMatches(Entry expected,
-            NetworkStats.Bucket actual) {
+    private void assertBucketMatches(Entry expected, NetworkStats.Bucket actual) {
         assertEquals(expected.uid, actual.getUid());
         assertEquals(expected.rxBytes, actual.getRxBytes());
         assertEquals(expected.rxPackets, actual.getRxPackets());
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
index 6e69b34..b81ca36 100644
--- a/tests/net/java/android/net/IpMemoryStoreTest.java
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -321,4 +321,11 @@
                 eq(TEST_OTHER_DATA_NAME), any());
         assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
     }
+
+    @Test
+    public void testFactoryReset() throws RemoteException {
+        startIpMemoryStore(true /* supplyService */);
+        mStore.factoryReset();
+        verify(mMockService, times(1)).factoryReset();
+    }
 }
diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java
index 215506c..c9888b2 100644
--- a/tests/net/java/android/net/IpSecConfigTest.java
+++ b/tests/net/java/android/net/IpSecConfigTest.java
@@ -16,12 +16,12 @@
 
 package android.net;
 
-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 com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
 
-import android.os.Parcel;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
 
 import androidx.test.filters.SmallTest;
 
@@ -89,23 +89,15 @@
         IpSecConfig original = getSampleConfig();
         IpSecConfig copy = new IpSecConfig(original);
 
-        assertTrue(IpSecConfig.equals(original, copy));
-        assertFalse(original == copy);
+        assertEquals(original, copy);
+        assertNotSame(original, copy);
     }
 
     @Test
-    public void testParcelUnparcel() throws Exception {
+    public void testParcelUnparcel() {
         assertParcelingIsLossless(new IpSecConfig());
 
         IpSecConfig c = getSampleConfig();
-        assertParcelingIsLossless(c);
-    }
-
-    private void assertParcelingIsLossless(IpSecConfig ci) throws Exception {
-        Parcel p = Parcel.obtain();
-        ci.writeToParcel(p, 0);
-        p.setDataPosition(0);
-        IpSecConfig co = IpSecConfig.CREATOR.createFromParcel(p);
-        assertTrue(IpSecConfig.equals(co, ci));
+        assertParcelSane(c, 15);
     }
 }
diff --git a/tests/net/java/android/net/IpSecTransformTest.java b/tests/net/java/android/net/IpSecTransformTest.java
index 2308a3c..424f23d 100644
--- a/tests/net/java/android/net/IpSecTransformTest.java
+++ b/tests/net/java/android/net/IpSecTransformTest.java
@@ -16,8 +16,8 @@
 
 package android.net;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 
 import androidx.test.filters.SmallTest;
 
@@ -43,7 +43,7 @@
         config.setSpiResourceId(1985);
         IpSecTransform postModification = new IpSecTransform(null, config);
 
-        assertFalse(IpSecTransform.equals(preModification, postModification));
+        assertNotEquals(preModification, postModification);
     }
 
     @Test
@@ -57,6 +57,6 @@
         IpSecTransform config1 = new IpSecTransform(null, config);
         IpSecTransform config2 = new IpSecTransform(null, config);
 
-        assertTrue(IpSecTransform.equals(config1, config2));
+        assertEquals(config1, config2);
     }
 }
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
index 583d3fd..5cb0d7e 100644
--- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -16,14 +16,14 @@
 
 package android.net;
 
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import android.net.SocketKeepalive.InvalidPacketException;
 
-import com.android.internal.util.TestUtils;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -79,7 +79,7 @@
         assertEquals(testInfo.tos, resultData.ipTos);
         assertEquals(testInfo.ttl, resultData.ipTtl);
 
-        TestUtils.assertParcelingIsLossless(resultData);
+        assertParcelingIsLossless(resultData);
 
         final byte[] packet = resultData.getPacket();
         // IP version and IHL
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
index 2d2bccb..cf7587a 100644
--- a/tests/net/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -16,8 +16,6 @@
 
 package android.net.nsd;
 
-import static com.android.internal.util.TestUtils.waitForIdleHandler;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
@@ -40,6 +38,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.AsyncChannel;
+import com.android.testutils.HandlerUtilsKt;
 
 import org.junit.After;
 import org.junit.Before;
@@ -74,7 +73,7 @@
 
     @After
     public void tearDown() throws Exception {
-        mServiceHandler.waitForIdle(mTimeoutMs);
+        HandlerUtilsKt.waitForIdle(mServiceHandler, mTimeoutMs);
         mServiceHandler.chan.disconnect();
         mServiceHandler.stop();
         if (mManager != null) {
@@ -334,7 +333,7 @@
     }
 
     int verifyRequest(int expectedMessageType) {
-        mServiceHandler.waitForIdle(mTimeoutMs);
+        HandlerUtilsKt.waitForIdle(mServiceHandler, mTimeoutMs);
         verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
         reset(mServiceHandler);
         Message received = mServiceHandler.getLastMessage();
@@ -366,10 +365,6 @@
             lastMessage.copyFrom(msg);
         }
 
-        void waitForIdle(long timeoutMs) {
-            waitForIdleHandler(this, timeoutMs);
-        }
-
         @Override
         public void handleMessage(Message msg) {
             setLastMessage(msg);
diff --git a/tests/net/java/android/net/shared/InitialConfigurationTest.java b/tests/net/java/android/net/shared/InitialConfigurationTest.java
index 2fb8b19..17f8324 100644
--- a/tests/net/java/android/net/shared/InitialConfigurationTest.java
+++ b/tests/net/java/android/net/shared/InitialConfigurationTest.java
@@ -18,7 +18,7 @@
 
 import static android.net.InetAddresses.parseNumericAddress;
 
-import static com.android.internal.util.ParcelableTestUtil.assertFieldCountEquals;
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
diff --git a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
index f9dbdc7..f987389 100644
--- a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
+++ b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
@@ -20,7 +20,7 @@
 import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
 import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
 
-import static com.android.internal.util.ParcelableTestUtil.assertFieldCountEquals;
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
 
diff --git a/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java b/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java
index 382afe0..7079a28 100644
--- a/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java
+++ b/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java
@@ -19,7 +19,7 @@
 import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.shared.ProvisioningConfiguration.fromStableParcelable;
 
-import static com.android.internal.util.ParcelableTestUtil.assertFieldCountEquals;
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
diff --git a/tests/net/java/android/net/util/DnsUtilsTest.java b/tests/net/java/android/net/util/DnsUtilsTest.java
new file mode 100644
index 0000000..b626db8
--- /dev/null
+++ b/tests/net/java/android/net/util/DnsUtilsTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_GLOBAL;
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_LINKLOCAL;
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_SITELOCAL;
+
+import static org.junit.Assert.assertEquals;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsUtilsTest {
+    private InetAddress stringToAddress(@NonNull String addr) {
+        return InetAddresses.parseNumericAddress(addr);
+    }
+
+    private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr) {
+        return makeSortableAddress(addr, null);
+    }
+
+    private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr,
+            @Nullable String srcAddr) {
+        return new DnsUtils.SortableAddress(stringToAddress(addr),
+                srcAddr != null ? stringToAddress(srcAddr) : null);
+    }
+
+    @Test
+    public void testRfc6724Comparator() {
+        final List<DnsUtils.SortableAddress> test = Arrays.asList(
+                // Ipv4
+                makeSortableAddress("216.58.200.36", "192.168.1.1"),
+                // global with different scope src
+                makeSortableAddress("2404:6800:4008:801::2004", "fe80::1111:2222"),
+                // global without src addr
+                makeSortableAddress("2404:6800:cafe:801::1"),
+                // loop back
+                makeSortableAddress("::1", "::1"),
+                // link local
+                makeSortableAddress("fe80::c46f:1cff:fe04:39b4", "fe80::1"),
+                // teredo tunneling
+                makeSortableAddress("2001::47c1", "2001::2"),
+                // 6bone without src addr
+                makeSortableAddress("3ffe::1234:5678"),
+                // IPv4-compatible
+                makeSortableAddress("::216.58.200.36", "::216.58.200.9"),
+                // 6bone
+                makeSortableAddress("3ffe::1234:5678", "3ffe::1234:1"),
+                // IPv4-mapped IPv6
+                makeSortableAddress("::ffff:192.168.95.7", "::ffff:192.168.95.1"));
+
+        final List<InetAddress> expected = Arrays.asList(
+                stringToAddress("::1"),                       // loop back
+                stringToAddress("fe80::c46f:1cff:fe04:39b4"), // link local
+                stringToAddress("216.58.200.36"),             // Ipv4
+                stringToAddress("::ffff:192.168.95.7"),       // IPv4-mapped IPv6
+                stringToAddress("2001::47c1"),                // teredo tunneling
+                stringToAddress("::216.58.200.36"),           // IPv4-compatible
+                stringToAddress("3ffe::1234:5678"),           // 6bone
+                stringToAddress("2404:6800:4008:801::2004"),  // global with different scope src
+                stringToAddress("2404:6800:cafe:801::1"),     // global without src addr
+                stringToAddress("3ffe::1234:5678"));          // 6bone without src addr
+
+        Collections.sort(test, new DnsUtils.Rfc6724Comparator());
+
+        for (int i = 0; i < test.size(); ++i) {
+            assertEquals(test.get(i).address, expected.get(i));
+        }
+
+        // TODO: add more combinations
+    }
+
+    @Test
+    public void testV4SortableAddress() {
+        // Test V4 address
+        DnsUtils.SortableAddress test = makeSortableAddress("216.58.200.36");
+        assertEquals(test.hasSrcAddr, 0);
+        assertEquals(test.prefixMatchLen, 0);
+        assertEquals(test.address, stringToAddress("216.58.200.36"));
+        assertEquals(test.labelMatch, 0);
+        assertEquals(test.scopeMatch, 0);
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 4);
+        assertEquals(test.precedence, 35);
+
+        // Test V4 loopback address with the same source address
+        test = makeSortableAddress("127.1.2.3", "127.1.2.3");
+        assertEquals(test.hasSrcAddr, 1);
+        assertEquals(test.prefixMatchLen, 0);
+        assertEquals(test.address, stringToAddress("127.1.2.3"));
+        assertEquals(test.labelMatch, 1);
+        assertEquals(test.scopeMatch, 1);
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+        assertEquals(test.label, 4);
+        assertEquals(test.precedence, 35);
+    }
+
+    @Test
+    public void testV6SortableAddress() {
+        // Test global address
+        DnsUtils.SortableAddress test = makeSortableAddress("2404:6800:4008:801::2004");
+        assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004"));
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 1);
+        assertEquals(test.precedence, 40);
+
+        // Test global address with global source address
+        test = makeSortableAddress("2404:6800:4008:801::2004",
+                "2401:fa00:fc:fd00:6d6c:7199:b8e7:41d6");
+        assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004"));
+        assertEquals(test.hasSrcAddr, 1);
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.labelMatch, 1);
+        assertEquals(test.scopeMatch, 1);
+        assertEquals(test.label, 1);
+        assertEquals(test.precedence, 40);
+        assertEquals(test.prefixMatchLen, 13);
+
+        // Test global address with linklocal source address
+        test = makeSortableAddress("2404:6800:4008:801::2004", "fe80::c46f:1cff:fe04:39b4");
+        assertEquals(test.hasSrcAddr, 1);
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.labelMatch, 1);
+        assertEquals(test.scopeMatch, 0);
+        assertEquals(test.label, 1);
+        assertEquals(test.precedence, 40);
+        assertEquals(test.prefixMatchLen, 0);
+
+        // Test loopback address with the same source address
+        test = makeSortableAddress("::1", "::1");
+        assertEquals(test.hasSrcAddr, 1);
+        assertEquals(test.prefixMatchLen, 16 * 8);
+        assertEquals(test.labelMatch, 1);
+        assertEquals(test.scopeMatch, 1);
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+        assertEquals(test.label, 0);
+        assertEquals(test.precedence, 50);
+
+        // Test linklocal address
+        test = makeSortableAddress("fe80::c46f:1cff:fe04:39b4");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+        assertEquals(test.label, 1);
+        assertEquals(test.precedence, 40);
+
+        // Test linklocal address
+        test = makeSortableAddress("fe80::");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+        assertEquals(test.label, 1);
+        assertEquals(test.precedence, 40);
+
+        // Test 6to4 address
+        test = makeSortableAddress("2002:c000:0204::");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 2);
+        assertEquals(test.precedence, 30);
+
+        // Test unique local address
+        test = makeSortableAddress("fc00::c000:13ab");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 13);
+        assertEquals(test.precedence, 3);
+
+        // Test teredo tunneling address
+        test = makeSortableAddress("2001::47c1");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 5);
+        assertEquals(test.precedence, 5);
+
+        // Test IPv4-compatible addresses
+        test = makeSortableAddress("::216.58.200.36");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 3);
+        assertEquals(test.precedence, 1);
+
+        // Test site-local address
+        test = makeSortableAddress("fec0::cafe:3ab2");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_SITELOCAL);
+        assertEquals(test.label, 11);
+        assertEquals(test.precedence, 1);
+
+        // Test 6bone address
+        test = makeSortableAddress("3ffe::1234:5678");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 12);
+        assertEquals(test.precedence, 1);
+    }
+}
diff --git a/tests/net/java/android/net/util/KeepaliveUtilsTest.kt b/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
index 814e06e..8ea226d 100644
--- a/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
@@ -78,7 +78,6 @@
         assertRunWithException(arrayOf("5"))
 
         // Check resource with invalid slots value.
-        assertRunWithException(arrayOf("2,2"))
         assertRunWithException(arrayOf("3,-1"))
 
         // Check resource with invalid transport type.
diff --git a/tests/net/java/com/android/internal/util/RingBufferTest.java b/tests/net/java/com/android/internal/util/RingBufferTest.java
index eff334f..d06095a 100644
--- a/tests/net/java/com/android/internal/util/RingBufferTest.java
+++ b/tests/net/java/com/android/internal/util/RingBufferTest.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
@@ -25,9 +26,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Arrays;
-import java.util.Objects;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class RingBufferTest {
@@ -36,7 +34,7 @@
     public void testEmptyRingBuffer() {
         RingBuffer<String> buffer = new RingBuffer<>(String.class, 100);
 
-        assertArraysEqual(new String[0], buffer.toArray());
+        assertArrayEquals(new String[0], buffer.toArray());
     }
 
     @Test
@@ -65,7 +63,7 @@
         buffer.append("e");
 
         String[] expected = {"a", "b", "c", "d", "e"};
-        assertArraysEqual(expected, buffer.toArray());
+        assertArrayEquals(expected, buffer.toArray());
     }
 
     @Test
@@ -73,19 +71,19 @@
         RingBuffer<String> buffer = new RingBuffer<>(String.class, 1);
 
         buffer.append("a");
-        assertArraysEqual(new String[]{"a"}, buffer.toArray());
+        assertArrayEquals(new String[]{"a"}, buffer.toArray());
 
         buffer.append("b");
-        assertArraysEqual(new String[]{"b"}, buffer.toArray());
+        assertArrayEquals(new String[]{"b"}, buffer.toArray());
 
         buffer.append("c");
-        assertArraysEqual(new String[]{"c"}, buffer.toArray());
+        assertArrayEquals(new String[]{"c"}, buffer.toArray());
 
         buffer.append("d");
-        assertArraysEqual(new String[]{"d"}, buffer.toArray());
+        assertArrayEquals(new String[]{"d"}, buffer.toArray());
 
         buffer.append("e");
-        assertArraysEqual(new String[]{"e"}, buffer.toArray());
+        assertArrayEquals(new String[]{"e"}, buffer.toArray());
     }
 
     @Test
@@ -100,7 +98,7 @@
         buffer.append("e");
 
         String[] expected1 = {"a", "b", "c", "d", "e"};
-        assertArraysEqual(expected1, buffer.toArray());
+        assertArrayEquals(expected1, buffer.toArray());
 
         String[] expected2 = new String[capacity];
         int firstIndex = 0;
@@ -111,22 +109,22 @@
             buffer.append("x");
             expected2[i] = "x";
         }
-        assertArraysEqual(expected2, buffer.toArray());
+        assertArrayEquals(expected2, buffer.toArray());
 
         buffer.append("x");
         expected2[firstIndex] = "x";
-        assertArraysEqual(expected2, buffer.toArray());
+        assertArrayEquals(expected2, buffer.toArray());
 
         for (int i = 0; i < 10; i++) {
             for (String s : expected2) {
                 buffer.append(s);
             }
         }
-        assertArraysEqual(expected2, buffer.toArray());
+        assertArrayEquals(expected2, buffer.toArray());
 
         buffer.append("a");
         expected2[lastIndex] = "a";
-        assertArraysEqual(expected2, buffer.toArray());
+        assertArrayEquals(expected2, buffer.toArray());
     }
 
     @Test
@@ -143,7 +141,7 @@
             expected[i] = new DummyClass1();
             expected[i].x = capacity * i;
         }
-        assertArraysEqual(expected, buffer.toArray());
+        assertArrayEquals(expected, buffer.toArray());
 
         for (int i = 0; i < capacity; ++i) {
             if (actual[i] != buffer.getNextSlot()) {
@@ -177,18 +175,4 @@
     }
 
     private static final class DummyClass3 {}
-
-    static <T> void assertArraysEqual(T[] expected, T[] got) {
-        if (expected.length != got.length) {
-            fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
-                    + " did not have the same length");
-        }
-
-        for (int i = 0; i < expected.length; i++) {
-            if (!Objects.equals(expected[i], got[i])) {
-                fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
-                        + " were not equal");
-            }
-        }
-    }
 }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 23cfbd4..c63bf42 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.NETID_UNSET;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
@@ -27,12 +28,13 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
 import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
-import static android.net.ConnectivityManager.TYPE_NONE;
-import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -65,11 +67,17 @@
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
-import static com.android.internal.util.TestUtils.waitForIdleHandler;
-import static com.android.internal.util.TestUtils.waitForIdleLooper;
-import static com.android.internal.util.TestUtils.waitForIdleSerialExecutor;
+import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType;
+import static com.android.testutils.ConcurrentUtilsKt.await;
+import static com.android.testutils.ConcurrentUtilsKt.durationOf;
+import static com.android.testutils.ExceptionUtils.ignoreExceptions;
+import static com.android.testutils.HandlerUtilsKt.waitForIdleSerialExecutor;
+import static com.android.testutils.MiscAssertsKt.assertContainsExactly;
+import static com.android.testutils.MiscAssertsKt.assertEmpty;
+import static com.android.testutils.MiscAssertsKt.assertLength;
+import static com.android.testutils.MiscAssertsKt.assertRunsInAtMost;
+import static com.android.testutils.MiscAssertsKt.assertThrows;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -77,13 +85,15 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -96,6 +106,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.app.AlarmManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -116,6 +127,7 @@
 import android.net.ConnectivityManager.TooManyRequestsException;
 import android.net.ConnectivityThread;
 import android.net.IDnsResolver;
+import android.net.IIpConnectivityMetrics;
 import android.net.INetd;
 import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
@@ -130,14 +142,11 @@
 import android.net.LinkProperties;
 import android.net.MatchAllNetworkSpecifier;
 import android.net.Network;
-import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkFactory;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
 import android.net.NetworkStackClient;
 import android.net.NetworkState;
 import android.net.NetworkUtils;
@@ -150,13 +159,14 @@
 import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
+import android.os.BadParcelableException;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.INetworkManagementService;
 import android.os.Looper;
-import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
@@ -174,6 +184,7 @@
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -188,11 +199,16 @@
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.NetworkPinner;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.testutils.ExceptionUtils;
+import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.RecorderCallback.CallbackRecord;
+import com.android.testutils.TestableNetworkCallback;
 
 import org.junit.After;
 import org.junit.Before;
@@ -213,14 +229,12 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
-import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -229,7 +243,8 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Predicate;
+
+import kotlin.reflect.KClass;
 
 /**
  * Tests for {@link ConnectivityService}.
@@ -249,10 +264,12 @@
     // timeout. For this, our assertions should run fast enough to leave less than
     // (mService.mLingerDelayMs - TEST_CALLBACK_TIMEOUT_MS) between the time callbacks are
     // supposedly fired, and the time we call expectCallback.
-    private final static int TEST_CALLBACK_TIMEOUT_MS = 200;
+    private static final int TEST_CALLBACK_TIMEOUT_MS = 200;
     // Chosen to be less than TEST_CALLBACK_TIMEOUT_MS. This ensures that requests have time to
     // complete before callbacks are verified.
-    private final static int TEST_REQUEST_TIMEOUT_MS = 150;
+    private static final int TEST_REQUEST_TIMEOUT_MS = 150;
+
+    private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000;
 
     private static final String CLAT_PREFIX = "v4-";
     private static final String MOBILE_IFNAME = "test_rmnet_data0";
@@ -260,15 +277,19 @@
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
     private MockContext mServiceContext;
-    private WrappedConnectivityService mService;
+    private HandlerThread mCsHandlerThread;
+    private ConnectivityService mService;
     private WrappedConnectivityManager mCm;
-    private MockNetworkAgent mWiFiNetworkAgent;
-    private MockNetworkAgent mCellNetworkAgent;
-    private MockNetworkAgent mEthernetNetworkAgent;
+    private TestNetworkAgentWrapper mWiFiNetworkAgent;
+    private TestNetworkAgentWrapper mCellNetworkAgent;
+    private TestNetworkAgentWrapper mEthernetNetworkAgent;
     private MockVpn mMockVpn;
     private Context mContext;
     private INetworkPolicyListener mPolicyListener;
+    private WrappedMultinetworkPolicyTracker mPolicyTracker;
+    private HandlerThread mAlarmManagerThread;
 
+    @Mock IIpConnectivityMetrics mIpConnectivityMetrics;
     @Mock IpConnectivityMetrics.Logger mMetricsService;
     @Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
     @Mock INetworkManagementService mNetworkManagementService;
@@ -279,6 +300,8 @@
     @Mock NetworkStackClient mNetworkStack;
     @Mock PackageManager mPackageManager;
     @Mock UserManager mUserManager;
+    @Mock NotificationManager mNotificationManager;
+    @Mock AlarmManager mAlarmManager;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
             ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -348,9 +371,10 @@
         @Override
         public Object getSystemService(String name) {
             if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
-            if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
+            if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager;
             if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
             if (Context.USER_SERVICE.equals(name)) return mUserManager;
+            if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager;
             return super.getSystemService(name);
         }
 
@@ -368,31 +392,36 @@
         public PackageManager getPackageManager() {
             return mPackageManager;
         }
-   }
 
-    public void waitForIdle(int timeoutMsAsInt) {
-        long timeoutMs = timeoutMsAsInt;
-        waitForIdleHandler(mService.mHandlerThread, timeoutMs);
-        waitForIdle(mCellNetworkAgent, timeoutMs);
-        waitForIdle(mWiFiNetworkAgent, timeoutMs);
-        waitForIdle(mEthernetNetworkAgent, timeoutMs);
-        waitForIdleHandler(mService.mHandlerThread, timeoutMs);
-        waitForIdleLooper(ConnectivityThread.getInstanceLooper(), timeoutMs);
-    }
-
-    public void waitForIdle(MockNetworkAgent agent, long timeoutMs) {
-        if (agent == null) {
-            return;
+        @Override
+        public void enforceCallingOrSelfPermission(String permission, String message) {
+            // The mainline permission can only be held if signed with the network stack certificate
+            // Skip testing for this permission.
+            if (NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK.equals(permission)) return;
+            // All other permissions should be held by the test or unnecessary: check as normal to
+            // make sure the code does not rely on unexpected permissions.
+            super.enforceCallingOrSelfPermission(permission, message);
         }
-        waitForIdleHandler(agent.mHandlerThread, timeoutMs);
     }
 
     private void waitForIdle() {
-        waitForIdle(TIMEOUT_MS);
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        waitForIdle(mCellNetworkAgent, TIMEOUT_MS);
+        waitForIdle(mWiFiNetworkAgent, TIMEOUT_MS);
+        waitForIdle(mEthernetNetworkAgent, TIMEOUT_MS);
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtilsKt.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
+    }
+
+    private void waitForIdle(TestNetworkAgentWrapper agent, long timeoutMs) {
+        if (agent == null) {
+            return;
+        }
+        agent.waitForIdle(timeoutMs);
     }
 
     @Test
-    public void testWaitForIdle() {
+    public void testWaitForIdle() throws Exception {
         final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
 
         // Tests that waitForIdle returns immediately if the service is already idle.
@@ -402,7 +431,7 @@
 
         // Bring up a network that we can use to send messages to ConnectivityService.
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         waitFor(cv);
         Network n = mWiFiNetworkAgent.getNetwork();
@@ -419,10 +448,10 @@
     // This test has an inherent race condition in it, and cannot be enabled for continuous testing
     // or presubmit tests. It is kept for manual runs and documentation purposes.
     @Ignore
-    public void verifyThatNotWaitingForIdleCausesRaceConditions() {
+    public void verifyThatNotWaitingForIdleCausesRaceConditions() throws Exception {
         // Bring up a network that we can use to send messages to ConnectivityService.
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         waitFor(cv);
         Network n = mWiFiNetworkAgent.getNetwork();
@@ -442,93 +471,53 @@
         fail("expected race condition at least once in " + attempts + " attempts");
     }
 
-    private class MockNetworkAgent {
-        private final INetworkMonitor mNetworkMonitor;
-        private final NetworkInfo mNetworkInfo;
-        private final NetworkCapabilities mNetworkCapabilities;
-        private final HandlerThread mHandlerThread;
-        private final ConditionVariable mDisconnected = new ConditionVariable();
+    private class TestNetworkAgentWrapper extends NetworkAgentWrapper {
+        private static final int VALIDATION_RESULT_BASE = NETWORK_VALIDATION_PROBE_DNS
+                | NETWORK_VALIDATION_PROBE_HTTP
+                | NETWORK_VALIDATION_PROBE_HTTPS;
+        private static final int VALIDATION_RESULT_VALID = VALIDATION_RESULT_BASE
+                | NETWORK_VALIDATION_RESULT_VALID;
+        private static final int VALIDATION_RESULT_PARTIAL = VALIDATION_RESULT_BASE
+                | NETWORK_VALIDATION_PROBE_FALLBACK
+                | NETWORK_VALIDATION_RESULT_PARTIAL;
+        private static final int VALIDATION_RESULT_INVALID = 0;
+
+        private INetworkMonitor mNetworkMonitor;
+        private INetworkMonitorCallbacks mNmCallbacks;
+        private int mNmValidationResult = VALIDATION_RESULT_BASE;
+        private String mNmValidationRedirectUrl = null;
+        private boolean mNmProvNotificationRequested = false;
+
         private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
-        private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
-        private int mScore;
-        private NetworkAgent mNetworkAgent;
-        private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED;
-        private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
-        private Integer mExpectedKeepaliveSlot = null;
         // Contains the redirectUrl from networkStatus(). Before reading, wait for
         // mNetworkStatusReceived.
         private String mRedirectUrl;
 
-        private INetworkMonitorCallbacks mNmCallbacks;
-        private int mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
-        private String mNmValidationRedirectUrl = null;
-        private boolean mNmProvNotificationRequested = false;
-
-        void setNetworkValid() {
-            mNmValidationResult = NETWORK_TEST_RESULT_VALID;
-            mNmValidationRedirectUrl = null;
-        }
-
-        void setNetworkInvalid() {
-            mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
-            mNmValidationRedirectUrl = null;
-        }
-
-        void setNetworkPortal(String redirectUrl) {
-            setNetworkInvalid();
-            mNmValidationRedirectUrl = redirectUrl;
-        }
-
-        void setNetworkPartial() {
-            mNmValidationResult = NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
-            mNmValidationRedirectUrl = null;
-        }
-
-        MockNetworkAgent(int transport) {
+        TestNetworkAgentWrapper(int transport) throws Exception {
             this(transport, new LinkProperties());
         }
 
-        MockNetworkAgent(int transport, LinkProperties linkProperties) {
-            final int type = transportToLegacyType(transport);
-            final String typeName = ConnectivityManager.getNetworkTypeName(type);
-            mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
-            mNetworkCapabilities = new NetworkCapabilities();
-            mNetworkCapabilities.addTransportType(transport);
-            switch (transport) {
-                case TRANSPORT_ETHERNET:
-                    mScore = 70;
-                    break;
-                case TRANSPORT_WIFI:
-                    mScore = 60;
-                    break;
-                case TRANSPORT_CELLULAR:
-                    mScore = 50;
-                    break;
-                case TRANSPORT_WIFI_AWARE:
-                    mScore = 20;
-                    break;
-                case TRANSPORT_VPN:
-                    mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
-                    mScore = ConnectivityConstants.VPN_DEFAULT_SCORE;
-                    break;
-                default:
-                    throw new UnsupportedOperationException("unimplemented network type");
-            }
-            mHandlerThread = new HandlerThread("Mock-" + typeName);
-            mHandlerThread.start();
+        TestNetworkAgentWrapper(int transport, LinkProperties linkProperties)
+                throws Exception {
+            super(transport, linkProperties, mServiceContext);
 
+            // Waits for the NetworkAgent to be registered, which includes the creation of the
+            // NetworkMonitor.
+            waitForIdle(TIMEOUT_MS);
+        }
+
+        @Override
+        protected InstrumentedNetworkAgent makeNetworkAgent(LinkProperties linkProperties)
+                throws Exception {
             mNetworkMonitor = mock(INetworkMonitor.class);
+
             final Answer validateAnswer = inv -> {
-                new Thread(this::onValidationRequested).start();
+                new Thread(ignoreExceptions(this::onValidationRequested)).start();
                 return null;
             };
 
-            try {
-                doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(any(), any());
-                doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt());
-            } catch (RemoteException e) {
-                fail(e.getMessage());
-            }
+            doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(any(), any());
+            doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt());
 
             final ArgumentCaptor<Network> nmNetworkCaptor = ArgumentCaptor.forClass(Network.class);
             final ArgumentCaptor<INetworkMonitorCallbacks> nmCbCaptor =
@@ -538,132 +527,44 @@
                     any() /* name */,
                     nmCbCaptor.capture());
 
-            mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
-                    "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
-                    linkProperties, mScore, new NetworkMisc(), NetworkFactory.SerialNumber.NONE) {
-                @Override
-                public void unwanted() { mDisconnected.open(); }
-
-                @Override
-                public void startSocketKeepalive(Message msg) {
-                    int slot = msg.arg1;
-                    if (mExpectedKeepaliveSlot != null) {
-                        assertEquals((int) mExpectedKeepaliveSlot, slot);
-                    }
-                    onSocketKeepaliveEvent(slot, mStartKeepaliveError);
-                }
-
-                @Override
-                public void stopSocketKeepalive(Message msg) {
-                    onSocketKeepaliveEvent(msg.arg1, mStopKeepaliveError);
-                }
-
+            final InstrumentedNetworkAgent na = new InstrumentedNetworkAgent(this, linkProperties) {
                 @Override
                 public void networkStatus(int status, String redirectUrl) {
                     mRedirectUrl = redirectUrl;
                     mNetworkStatusReceived.open();
                 }
-
-                @Override
-                protected void preventAutomaticReconnect() {
-                    mPreventReconnectReceived.open();
-                }
-
-                @Override
-                protected void addKeepalivePacketFilter(Message msg) {
-                    Log.i(TAG, "Add keepalive packet filter.");
-                }
-
-                @Override
-                protected void removeKeepalivePacketFilter(Message msg) {
-                    Log.i(TAG, "Remove keepalive packet filter.");
-                }
             };
 
-            assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId);
+            assertEquals(na.netId, nmNetworkCaptor.getValue().netId);
             mNmCallbacks = nmCbCaptor.getValue();
 
-            try {
-                mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);
-            } catch (RemoteException e) {
-                fail(e.getMessage());
-            }
+            mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);
 
-            // Waits for the NetworkAgent to be registered, which includes the creation of the
-            // NetworkMonitor.
-            waitForIdle();
+            return na;
         }
 
-        private void onValidationRequested() {
-            try {
-                if (mNmProvNotificationRequested
-                        && mNmValidationResult == NETWORK_TEST_RESULT_VALID) {
-                    mNmCallbacks.hideProvisioningNotification();
-                    mNmProvNotificationRequested = false;
-                }
+        private void onValidationRequested() throws Exception {
+            if (mNmProvNotificationRequested
+                    && ((mNmValidationResult & NETWORK_VALIDATION_RESULT_VALID) != 0)) {
+                mNmCallbacks.hideProvisioningNotification();
+                mNmProvNotificationRequested = false;
+            }
 
-                mNmCallbacks.notifyNetworkTested(
-                        mNmValidationResult, mNmValidationRedirectUrl);
+            mNmCallbacks.notifyNetworkTested(
+                    mNmValidationResult, mNmValidationRedirectUrl);
 
-                if (mNmValidationRedirectUrl != null) {
-                    mNmCallbacks.showProvisioningNotification(
-                            "test_provisioning_notif_action", "com.android.test.package");
-                    mNmProvNotificationRequested = true;
-                }
-            } catch (RemoteException e) {
-                fail(e.getMessage());
+            if (mNmValidationRedirectUrl != null) {
+                mNmCallbacks.showProvisioningNotification(
+                        "test_provisioning_notif_action", "com.android.test.package");
+                mNmProvNotificationRequested = true;
             }
         }
 
-        public void adjustScore(int change) {
-            mScore += change;
-            mNetworkAgent.sendNetworkScore(mScore);
-        }
-
-        public int getScore() {
-            return mScore;
-        }
-
-        public void explicitlySelected(boolean acceptUnvalidated) {
-            mNetworkAgent.explicitlySelected(acceptUnvalidated);
-        }
-
-        public void addCapability(int capability) {
-            mNetworkCapabilities.addCapability(capability);
-            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
-        }
-
-        public void removeCapability(int capability) {
-            mNetworkCapabilities.removeCapability(capability);
-            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
-        }
-
-        public void setUids(Set<UidRange> uids) {
-            mNetworkCapabilities.setUids(uids);
-            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
-        }
-
-        public void setSignalStrength(int signalStrength) {
-            mNetworkCapabilities.setSignalStrength(signalStrength);
-            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
-        }
-
-        public void setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
-            mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
-            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
-        }
-
-        public void setNetworkCapabilities(NetworkCapabilities nc,
-                boolean sendToConnectivityService) {
-            mNetworkCapabilities.set(nc);
-            if (sendToConnectivityService) {
-                mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
-            }
-        }
-
+        /**
+         * Connect without adding any internet capability.
+         */
         public void connectWithoutInternet() {
-            mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
-            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+            super.connect();
         }
 
         /**
@@ -680,23 +581,21 @@
          * @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
          */
         public void connect(boolean validated, boolean hasInternet) {
-            assertEquals("MockNetworkAgents can only be connected once",
-                    mNetworkInfo.getDetailedState(), DetailedState.IDLE);
-            assertFalse(mNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET));
+            assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET));
 
-            NetworkCallback callback = null;
+            ConnectivityManager.NetworkCallback callback = null;
             final ConditionVariable validatedCv = new ConditionVariable();
             if (validated) {
                 setNetworkValid();
                 NetworkRequest request = new NetworkRequest.Builder()
-                        .addTransportType(mNetworkCapabilities.getTransportTypes()[0])
+                        .addTransportType(getNetworkCapabilities().getTransportTypes()[0])
                         .clearCapabilities()
                         .build();
-                callback = new NetworkCallback() {
+                callback = new ConnectivityManager.NetworkCallback() {
                     public void onCapabilitiesChanged(Network network,
                             NetworkCapabilities networkCapabilities) {
                         if (network.equals(getNetwork()) &&
-                            networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
+                                networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
                             validatedCv.open();
                         }
                     }
@@ -728,47 +627,34 @@
             connect(false);
         }
 
-        public void suspend() {
-            mNetworkInfo.setDetailedState(DetailedState.SUSPENDED, null, null);
-            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+        public void connectWithPartialValidConnectivity() {
+            setNetworkPartialValid();
+            connect(false);
         }
 
-        public void resume() {
-            mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
-            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+        void setNetworkValid() {
+            mNmValidationResult = VALIDATION_RESULT_VALID;
+            mNmValidationRedirectUrl = null;
         }
 
-        public void disconnect() {
-            mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
-            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+        void setNetworkInvalid() {
+            mNmValidationResult = VALIDATION_RESULT_INVALID;
+            mNmValidationRedirectUrl = null;
         }
 
-        public Network getNetwork() {
-            return new Network(mNetworkAgent.netId);
+        void setNetworkPortal(String redirectUrl) {
+            setNetworkInvalid();
+            mNmValidationRedirectUrl = redirectUrl;
         }
 
-        public ConditionVariable getPreventReconnectReceived() {
-            return mPreventReconnectReceived;
+        void setNetworkPartial() {
+            mNmValidationResult = VALIDATION_RESULT_PARTIAL;
+            mNmValidationRedirectUrl = null;
         }
 
-        public ConditionVariable getDisconnectedCV() {
-            return mDisconnected;
-        }
-
-        public void sendLinkProperties(LinkProperties lp) {
-            mNetworkAgent.sendLinkProperties(lp);
-        }
-
-        public void setStartKeepaliveError(int error) {
-            mStartKeepaliveError = error;
-        }
-
-        public void setStopKeepaliveError(int error) {
-            mStopKeepaliveError = error;
-        }
-
-        public void setExpectedKeepaliveSlot(Integer slot) {
-            mExpectedKeepaliveSlot = slot;
+        void setNetworkPartialValid() {
+            mNmValidationResult = VALIDATION_RESULT_PARTIAL | VALIDATION_RESULT_VALID;
+            mNmValidationRedirectUrl = null;
         }
 
         public String waitForRedirectUrl() {
@@ -776,12 +662,12 @@
             return mRedirectUrl;
         }
 
-        public NetworkAgent getNetworkAgent() {
-            return mNetworkAgent;
+        public void expectDisconnected() {
+            expectDisconnected(TIMEOUT_MS);
         }
 
-        public NetworkCapabilities getNetworkCapabilities() {
-            return mNetworkCapabilities;
+        public void expectPreventReconnectReceived() {
+            expectPreventReconnectReceived(TIMEOUT_MS);
         }
     }
 
@@ -960,15 +846,15 @@
         private boolean mConnected = false;
         // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does
         // not inherit from NetworkAgent.
-        private MockNetworkAgent mMockNetworkAgent;
+        private TestNetworkAgentWrapper mMockNetworkAgent;
 
         public MockVpn(int userId) {
             super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
                     userId);
         }
 
-        public void setNetworkAgent(MockNetworkAgent agent) {
-            waitForIdle(agent, TIMEOUT_MS);
+        public void setNetworkAgent(TestNetworkAgentWrapper agent) {
+            agent.waitForIdle(TIMEOUT_MS);
             mMockNetworkAgent = agent;
             mNetworkAgent = agent.getNetworkAgent();
             mNetworkCapabilities.set(agent.getNetworkCapabilities());
@@ -1035,192 +921,45 @@
         }
     }
 
-    private class FakeWakeupMessage extends WakeupMessage {
-        private static final int UNREASONABLY_LONG_WAIT = 1000;
-
-        public FakeWakeupMessage(Context context, Handler handler, String cmdName, int cmd) {
-            super(context, handler, cmdName, cmd);
-        }
-
-        public FakeWakeupMessage(Context context, Handler handler, String cmdName, int cmd,
-                int arg1, int arg2, Object obj) {
-            super(context, handler, cmdName, cmd, arg1, arg2, obj);
-        }
-
-        @Override
-        public void schedule(long when) {
-            long delayMs = when - SystemClock.elapsedRealtime();
-            if (delayMs < 0) delayMs = 0;
-            if (delayMs > UNREASONABLY_LONG_WAIT) {
-                fail("Attempting to send msg more than " + UNREASONABLY_LONG_WAIT +
-                        "ms into the future: " + delayMs);
-            }
-            Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
-            mHandler.sendMessageDelayed(msg, delayMs);
-        }
-
-        @Override
-        public void cancel() {
-            mHandler.removeMessages(mCmd, mObj);
-        }
-
-        @Override
-        public void onAlarm() {
-            throw new AssertionError("Should never happen. Update this fake.");
+    private void mockVpn(int uid) {
+        synchronized (mService.mVpns) {
+            int userId = UserHandle.getUserId(uid);
+            mMockVpn = new MockVpn(userId);
+            // This has no effect unless the VPN is actually connected, because things like
+            // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN
+            // netId, and check if that network is actually connected.
+            mService.mVpns.put(userId, mMockVpn);
         }
     }
 
-    private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
-        public volatile boolean configRestrictsAvoidBadWifi;
-        public volatile int configMeteredMultipathPreference;
+    private void setUidRulesChanged(int uidRules) throws RemoteException {
+        mPolicyListener.onUidRulesChanged(Process.myUid(), uidRules);
+    }
 
-        public WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
+    private void setRestrictBackgroundChanged(boolean restrictBackground) throws RemoteException {
+        mPolicyListener.onRestrictBackgroundChanged(restrictBackground);
+    }
+
+    private Nat464Xlat getNat464Xlat(NetworkAgentWrapper mna) {
+        return mService.getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
+    }
+
+    private static class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
+        volatile boolean mConfigRestrictsAvoidBadWifi;
+        volatile int mConfigMeteredMultipathPreference;
+
+        WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
             super(c, h, r);
         }
 
         @Override
         public boolean configRestrictsAvoidBadWifi() {
-            return configRestrictsAvoidBadWifi;
+            return mConfigRestrictsAvoidBadWifi;
         }
 
         @Override
         public int configMeteredMultipathPreference() {
-            return configMeteredMultipathPreference;
-        }
-    }
-
-    private class WrappedConnectivityService extends ConnectivityService {
-        public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
-        private MockableSystemProperties mSystemProperties;
-
-        public WrappedConnectivityService(Context context, INetworkManagementService netManager,
-                INetworkStatsService statsService, INetworkPolicyManager policyManager,
-                IpConnectivityLog log, INetd netd, IDnsResolver dnsResolver) {
-            super(context, netManager, statsService, policyManager, dnsResolver, log, netd);
-            mNetd = netd;
-            mLingerDelayMs = TEST_LINGER_DELAY_MS;
-        }
-
-        @Override
-        protected MockableSystemProperties getSystemProperties() {
-            // Minimal approach to overriding system properties: let most calls fall through to real
-            // device values, and only override ones values that are important to this test.
-            mSystemProperties = spy(new MockableSystemProperties());
-            when(mSystemProperties.getInt("net.tcp.default_init_rwnd", 0)).thenReturn(0);
-            when(mSystemProperties.getBoolean("ro.radio.noril", false)).thenReturn(false);
-            return mSystemProperties;
-        }
-
-        @Override
-        protected Tethering makeTethering() {
-            return mock(Tethering.class);
-        }
-
-        @Override
-        protected ProxyTracker makeProxyTracker() {
-            return mock(ProxyTracker.class);
-        }
-
-        @Override
-        protected int reserveNetId() {
-            while (true) {
-                final int netId = super.reserveNetId();
-
-                // Don't overlap test NetIDs with real NetIDs as binding sockets to real networks
-                // can have odd side-effects, like network validations succeeding.
-                Context context = InstrumentationRegistry.getContext();
-                final Network[] networks = ConnectivityManager.from(context).getAllNetworks();
-                boolean overlaps = false;
-                for (Network network : networks) {
-                    if (netId == network.netId) {
-                        overlaps = true;
-                        break;
-                    }
-                }
-                if (overlaps) continue;
-
-                return netId;
-            }
-        }
-
-        @Override
-        protected boolean queryUserAccess(int uid, int netId) {
-            return true;
-        }
-
-        public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
-            return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
-        }
-
-        @Override
-        public MultinetworkPolicyTracker createMultinetworkPolicyTracker(
-                Context c, Handler h, Runnable r) {
-            final WrappedMultinetworkPolicyTracker tracker = new WrappedMultinetworkPolicyTracker(c, h, r);
-            return tracker;
-        }
-
-        public WrappedMultinetworkPolicyTracker getMultinetworkPolicyTracker() {
-            return (WrappedMultinetworkPolicyTracker) mMultinetworkPolicyTracker;
-        }
-
-        @Override
-        protected NetworkStackClient getNetworkStack() {
-            return mNetworkStack;
-        }
-
-        @Override
-        public WakeupMessage makeWakeupMessage(
-                Context context, Handler handler, String cmdName, int cmd, Object obj) {
-            return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj);
-        }
-
-        @Override
-        public boolean hasService(String name) {
-            // Currenty, the only relevant service that ConnectivityService checks for is
-            // ETHERNET_SERVICE.
-            return Context.ETHERNET_SERVICE.equals(name);
-        }
-
-        @Override
-        protected IpConnectivityMetrics.Logger metricsLogger() {
-            return mMetricsService;
-        }
-
-        @Override
-        protected void registerNetdEventCallback() {
-        }
-
-        public void mockVpn(int uid) {
-            synchronized (mVpns) {
-                int userId = UserHandle.getUserId(uid);
-                mMockVpn = new MockVpn(userId);
-                // This has no effect unless the VPN is actually connected, because things like
-                // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN
-                // netId, and check if that network is actually connected.
-                mVpns.put(userId, mMockVpn);
-            }
-        }
-
-        public void waitForIdle(int timeoutMs) {
-            waitForIdleHandler(mHandlerThread, timeoutMs);
-        }
-
-        public void waitForIdle() {
-            waitForIdle(TIMEOUT_MS);
-        }
-
-        public void setUidRulesChanged(int uidRules) {
-            try {
-                mPolicyListener.onUidRulesChanged(Process.myUid(), uidRules);
-            } catch (RemoteException ignored) {
-            }
-        }
-
-        public void setRestrictBackgroundChanged(boolean restrictBackground) {
-            try {
-                mPolicyListener.onRestrictBackgroundChanged(restrictBackground);
-            } catch (RemoteException ignored) {
-            }
+            return mConfigMeteredMultipathPreference;
         }
     }
 
@@ -1266,13 +1005,22 @@
         LocalServices.addService(
                 NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class));
 
-        mService = new WrappedConnectivityService(mServiceContext,
+        mAlarmManagerThread = new HandlerThread("TestAlarmManager");
+        mAlarmManagerThread.start();
+        initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler());
+
+        mCsHandlerThread = new HandlerThread("TestConnectivityService");
+        final ConnectivityService.Dependencies deps = makeDependencies();
+        mService = new ConnectivityService(mServiceContext,
                 mNetworkManagementService,
                 mStatsService,
                 mNpm,
+                mMockDnsResolver,
                 mock(IpConnectivityLog.class),
                 mMockNetd,
-                mMockDnsResolver);
+                deps);
+        mService.mLingerDelayMs = TEST_LINGER_DELAY_MS;
+        verify(deps).makeMultinetworkPolicyTracker(any(), any(), any());
 
         final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor =
                 ArgumentCaptor.forClass(INetworkPolicyListener.class);
@@ -1283,7 +1031,7 @@
         // getSystemService() correctly.
         mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService);
         mService.systemReady();
-        mService.mockVpn(Process.myUid());
+        mockVpn(Process.myUid());
         mCm.bindProcessToNetwork(null);
 
         // Ensure that the default setting for Captive Portals is used for most tests
@@ -1292,6 +1040,57 @@
         setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
     }
 
+    private ConnectivityService.Dependencies makeDependencies() {
+        final MockableSystemProperties systemProperties = spy(new MockableSystemProperties());
+        when(systemProperties.getInt("net.tcp.default_init_rwnd", 0)).thenReturn(0);
+        when(systemProperties.getBoolean("ro.radio.noril", false)).thenReturn(false);
+
+        final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class);
+        doReturn(mCsHandlerThread).when(deps).makeHandlerThread();
+        doReturn(new TestNetIdManager()).when(deps).makeNetIdManager();
+        doReturn(mNetworkStack).when(deps).getNetworkStack();
+        doReturn(systemProperties).when(deps).getSystemProperties();
+        doReturn(mock(Tethering.class)).when(deps).makeTethering(any(), any(), any(), any(), any());
+        doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
+        doReturn(mMetricsService).when(deps).getMetricsLogger();
+        doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
+        doReturn(mIpConnectivityMetrics).when(deps).getIpConnectivityMetrics();
+        doReturn(true).when(deps).hasService(Context.ETHERNET_SERVICE);
+        doAnswer(inv -> {
+            mPolicyTracker = new WrappedMultinetworkPolicyTracker(
+                    inv.getArgument(0), inv.getArgument(1), inv.getArgument(2));
+            return mPolicyTracker;
+        }).when(deps).makeMultinetworkPolicyTracker(any(), any(), any());
+
+        return deps;
+    }
+
+    private static void initAlarmManager(final AlarmManager am, final Handler alarmHandler) {
+        doAnswer(inv -> {
+            final long when = inv.getArgument(1);
+            final WakeupMessage wakeupMsg = inv.getArgument(3);
+            final Handler handler = inv.getArgument(4);
+
+            long delayMs = when - SystemClock.elapsedRealtime();
+            if (delayMs < 0) delayMs = 0;
+            if (delayMs > UNREASONABLY_LONG_ALARM_WAIT_MS) {
+                fail("Attempting to send msg more than " + UNREASONABLY_LONG_ALARM_WAIT_MS
+                        + "ms into the future: " + delayMs);
+            }
+            alarmHandler.postDelayed(() -> handler.post(wakeupMsg::onAlarm), wakeupMsg /* token */,
+                    delayMs);
+
+            return null;
+        }).when(am).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), anyString(),
+                any(WakeupMessage.class), any());
+
+        doAnswer(inv -> {
+            final WakeupMessage wakeupMsg = inv.getArgument(0);
+            alarmHandler.removeCallbacksAndMessages(wakeupMsg /* token */);
+            return null;
+        }).when(am).cancel(any(WakeupMessage.class));
+    }
+
     @After
     public void tearDown() throws Exception {
         setAlwaysOnNetworks(false);
@@ -1308,6 +1107,9 @@
             mEthernetNetworkAgent = null;
         }
         FakeSettingsProvider.clearSettingsProvider();
+
+        mCsHandlerThread.quitSafely();
+        mAlarmManagerThread.quitSafely();
     }
 
     private void mockDefaultPackages() throws Exception {
@@ -1327,21 +1129,6 @@
                 }));
     }
 
-   private static int transportToLegacyType(int transport) {
-        switch (transport) {
-            case TRANSPORT_ETHERNET:
-                return TYPE_ETHERNET;
-            case TRANSPORT_WIFI:
-                return TYPE_WIFI;
-            case TRANSPORT_CELLULAR:
-                return TYPE_MOBILE;
-            case TRANSPORT_VPN:
-                return TYPE_VPN;
-            default:
-                return TYPE_NONE;
-        }
-    }
-
     private void verifyActiveNetwork(int transport) {
         // Test getActiveNetworkInfo()
         assertNotNull(mCm.getActiveNetworkInfo());
@@ -1419,8 +1206,8 @@
     @Test
     public void testLingering() throws Exception {
         verifyNoNetwork();
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         assertNull(mCm.getActiveNetworkInfo());
         assertNull(mCm.getActiveNetwork());
         // Test bringing up validated cellular.
@@ -1444,7 +1231,7 @@
         assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) ||
                 mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
         // Test cellular linger timeout.
-        waitFor(mCellNetworkAgent.getDisconnectedCV());
+        mCellNetworkAgent.expectDisconnected();
         waitForIdle();
         assertLength(1, mCm.getAllNetworks());
         verifyActiveNetwork(TRANSPORT_WIFI);
@@ -1460,13 +1247,13 @@
     @Test
     public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception {
         // Test bringing up unvalidated WiFi
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.connect(false);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up unvalidated cellular
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
         waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
@@ -1475,7 +1262,7 @@
         waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up validated cellular
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         cv = waitForConnectivityBroadcasts(2);
         mCellNetworkAgent.connect(true);
         waitFor(cv);
@@ -1495,13 +1282,13 @@
     @Test
     public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception {
         // Test bringing up unvalidated cellular.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent.connect(false);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test bringing up unvalidated WiFi.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent.connect(false);
         waitFor(cv);
@@ -1521,7 +1308,7 @@
     @Test
     public void testUnlingeringDoesNotValidate() throws Exception {
         // Test bringing up unvalidated WiFi.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.connect(false);
         waitFor(cv);
@@ -1529,7 +1316,7 @@
         assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         // Test bringing up validated cellular.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         cv = waitForConnectivityBroadcasts(2);
         mCellNetworkAgent.connect(true);
         waitFor(cv);
@@ -1549,13 +1336,13 @@
     @Test
     public void testCellularOutscoresWeakWifi() throws Exception {
         // Test bringing up validated cellular.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent.connect(true);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test bringing up validated WiFi.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent.connect(true);
         waitFor(cv);
@@ -1576,45 +1363,41 @@
     public void testReapingNetwork() throws Exception {
         // Test bringing up WiFi without NET_CAPABILITY_INTERNET.
         // Expect it to be torn down immediately because it satisfies no requests.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        ConditionVariable cv = mWiFiNetworkAgent.getDisconnectedCV();
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connectWithoutInternet();
-        waitFor(cv);
+        mWiFiNetworkAgent.expectDisconnected();
         // Test bringing up cellular without NET_CAPABILITY_INTERNET.
         // Expect it to be torn down immediately because it satisfies no requests.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        cv = mCellNetworkAgent.getDisconnectedCV();
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mCellNetworkAgent.connectWithoutInternet();
-        waitFor(cv);
+        mCellNetworkAgent.expectDisconnected();
         // Test bringing up validated WiFi.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        cv = waitForConnectivityBroadcasts(1);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        final ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.connect(true);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up unvalidated cellular.
         // Expect it to be torn down because it could never be the highest scoring network
         // satisfying the default request even if it validated.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
-        cv = mCellNetworkAgent.getDisconnectedCV();
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
-        waitFor(cv);
+        mCellNetworkAgent.expectDisconnected();
         verifyActiveNetwork(TRANSPORT_WIFI);
-        cv = mWiFiNetworkAgent.getDisconnectedCV();
         mWiFiNetworkAgent.disconnect();
-        waitFor(cv);
+        mWiFiNetworkAgent.expectDisconnected();
     }
 
     @Test
     public void testCellularFallback() throws Exception {
         // Test bringing up validated cellular.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent.connect(true);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test bringing up validated WiFi.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent.connect(true);
         waitFor(cv);
@@ -1646,13 +1429,13 @@
     @Test
     public void testWiFiFallback() throws Exception {
         // Test bringing up unvalidated WiFi.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.connect(false);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up validated cellular.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         cv = waitForConnectivityBroadcasts(2);
         mCellNetworkAgent.connect(true);
         waitFor(cv);
@@ -1675,274 +1458,33 @@
                 mCm.getDefaultRequest().networkCapabilities));
     }
 
-    enum CallbackState {
-        NONE,
-        AVAILABLE,
-        NETWORK_CAPABILITIES,
-        LINK_PROPERTIES,
-        SUSPENDED,
-        RESUMED,
-        LOSING,
-        LOST,
-        UNAVAILABLE,
-        BLOCKED_STATUS
-    }
-
-    private static class CallbackInfo {
-        public final CallbackState state;
-        public final Network network;
-        public final Object arg;
-        public CallbackInfo(CallbackState s, Network n, Object o) {
-            state = s; network = n; arg = o;
-        }
-        public String toString() {
-            return String.format("%s (%s) (%s)", state, network, arg);
-        }
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof CallbackInfo)) return false;
-            // Ignore timeMs, since it's unpredictable.
-            CallbackInfo other = (CallbackInfo) o;
-            return (state == other.state) && Objects.equals(network, other.network);
-        }
-        @Override
-        public int hashCode() {
-            return Objects.hash(state, network);
-        }
-    }
-
     /**
      * Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks
      * this class receives, by calling expectCallback() exactly once each time a callback is
      * received. assertNoCallback may be called at any time.
      */
-    private class TestNetworkCallback extends NetworkCallback {
-        private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
-        private Network mLastAvailableNetwork;
-
-        protected void setLastCallback(CallbackState state, Network network, Object o) {
-            mCallbacks.offer(new CallbackInfo(state, network, o));
+    private class TestNetworkCallback extends TestableNetworkCallback {
+        @Override
+        public void assertNoCallback() {
+            // TODO: better support this use case in TestableNetworkCallback
+            waitForIdle();
+            assertNoCallback(0 /* timeout */);
         }
 
         @Override
-        public void onAvailable(Network network) {
-            mLastAvailableNetwork = network;
-            setLastCallback(CallbackState.AVAILABLE, network, null);
-        }
-
-        @Override
-        public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) {
-            setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap);
-        }
-
-        @Override
-        public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) {
-            setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp);
-        }
-
-        @Override
-        public void onUnavailable() {
-            setLastCallback(CallbackState.UNAVAILABLE, null, null);
-        }
-
-        @Override
-        public void onNetworkSuspended(Network network) {
-            setLastCallback(CallbackState.SUSPENDED, network, null);
-        }
-
-        @Override
-        public void onNetworkResumed(Network network) {
-            setLastCallback(CallbackState.RESUMED, network, null);
-        }
-
-        @Override
-        public void onLosing(Network network, int maxMsToLive) {
-            setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */);
-        }
-
-        @Override
-        public void onLost(Network network) {
-            mLastAvailableNetwork = null;
-            setLastCallback(CallbackState.LOST, network, null);
-        }
-
-        @Override
-        public void onBlockedStatusChanged(Network network, boolean blocked) {
-            setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked);
-        }
-
-        public Network getLastAvailableNetwork() {
-            return mLastAvailableNetwork;
-        }
-
-        CallbackInfo nextCallback(int timeoutMs) {
-            CallbackInfo cb = null;
-            try {
-                cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-            }
-            if (cb == null) {
-                // LinkedBlockingQueue.poll() returns null if it timeouts.
-                fail("Did not receive callback after " + timeoutMs + "ms");
-            }
-            return cb;
-        }
-
-        CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent, int timeoutMs) {
-            final Network expectedNetwork = (agent != null) ? agent.getNetwork() : null;
-            CallbackInfo expected = new CallbackInfo(state, expectedNetwork, 0);
-            CallbackInfo actual = nextCallback(timeoutMs);
-            assertEquals("Unexpected callback:", expected, actual);
-
-            if (state == CallbackState.LOSING) {
+        public <T extends CallbackRecord> T expectCallback(final KClass<T> type, final HasNetwork n,
+                final long timeoutMs) {
+            final T callback = super.expectCallback(type, n, timeoutMs);
+            if (callback instanceof CallbackRecord.Losing) {
+                // TODO : move this to the specific test(s) needing this rather than here.
+                final CallbackRecord.Losing losing = (CallbackRecord.Losing) callback;
+                final int maxMsToLive = losing.getMaxMsToLive();
                 String msg = String.format(
                         "Invalid linger time value %d, must be between %d and %d",
-                        actual.arg, 0, mService.mLingerDelayMs);
-                int maxMsToLive = (Integer) actual.arg;
+                        maxMsToLive, 0, mService.mLingerDelayMs);
                 assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= mService.mLingerDelayMs);
             }
-
-            return actual;
-        }
-
-        CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent) {
-            return expectCallback(state, agent, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        CallbackInfo expectCallbackLike(Predicate<CallbackInfo> fn) {
-            return expectCallbackLike(fn, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        CallbackInfo expectCallbackLike(Predicate<CallbackInfo> fn, int timeoutMs) {
-            int timeLeft = timeoutMs;
-            while (timeLeft > 0) {
-                long start = SystemClock.elapsedRealtime();
-                CallbackInfo info = nextCallback(timeLeft);
-                if (fn.test(info)) {
-                    return info;
-                }
-                timeLeft -= (SystemClock.elapsedRealtime() - start);
-            }
-            fail("Did not receive expected callback after " + timeoutMs + "ms");
-            return null;
-        }
-
-        // Expects onAvailable and the callbacks that follow it. These are:
-        // - onSuspended, iff the network was suspended when the callbacks fire.
-        // - onCapabilitiesChanged.
-        // - onLinkPropertiesChanged.
-        // - onBlockedStatusChanged.
-        //
-        // @param agent the network to expect the callbacks on.
-        // @param expectSuspended whether to expect a SUSPENDED callback.
-        // @param expectValidated the expected value of the VALIDATED capability in the
-        //        onCapabilitiesChanged callback.
-        // @param timeoutMs how long to wait for the callbacks.
-        void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended,
-                boolean expectValidated, boolean expectBlocked, int timeoutMs) {
-            expectCallback(CallbackState.AVAILABLE, agent, timeoutMs);
-            if (expectSuspended) {
-                expectCallback(CallbackState.SUSPENDED, agent, timeoutMs);
-            }
-            if (expectValidated) {
-                expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent, timeoutMs);
-            } else {
-                expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent, timeoutMs);
-            }
-            expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs);
-            expectBlockedStatusCallback(expectBlocked, agent);
-        }
-
-        // Expects the available callbacks (validated), plus onSuspended.
-        void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated) {
-            expectAvailableCallbacks(agent, true, expectValidated, false, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        void expectAvailableCallbacksValidated(MockNetworkAgent agent) {
-            expectAvailableCallbacks(agent, false, true, false, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        void expectAvailableCallbacksValidatedAndBlocked(MockNetworkAgent agent) {
-            expectAvailableCallbacks(agent, false, true, true, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) {
-            expectAvailableCallbacks(agent, false, false, false, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        void expectAvailableCallbacksUnvalidatedAndBlocked(MockNetworkAgent agent) {
-            expectAvailableCallbacks(agent, false, false, true, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        // Expects the available callbacks (where the onCapabilitiesChanged must contain the
-        // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the
-        // one we just sent.
-        // TODO: this is likely a bug. Fix it and remove this method.
-        void expectAvailableDoubleValidatedCallbacks(MockNetworkAgent agent) {
-            expectCallback(CallbackState.AVAILABLE, agent, TEST_CALLBACK_TIMEOUT_MS);
-            NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
-            expectCallback(CallbackState.LINK_PROPERTIES, agent, TEST_CALLBACK_TIMEOUT_MS);
-            // Implicitly check the network is allowed to use.
-            // TODO: should we need to consider if network is in blocked status in this case?
-            expectBlockedStatusCallback(false, agent);
-            NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
-            assertEquals(nc1, nc2);
-        }
-
-        // Expects the available callbacks where the onCapabilitiesChanged must not have validated,
-        // then expects another onCapabilitiesChanged that has the validated bit set. This is used
-        // when a network connects and satisfies a callback, and then immediately validates.
-        void expectAvailableThenValidatedCallbacks(MockNetworkAgent agent) {
-            expectAvailableCallbacksUnvalidated(agent);
-            expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
-        }
-
-        NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
-            return expectCapabilitiesWith(capability, agent, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent,
-                int timeoutMs) {
-            CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
-            NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
-            assertTrue(nc.hasCapability(capability));
-            return nc;
-        }
-
-        NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
-            return expectCapabilitiesWithout(capability, agent, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent,
-                int timeoutMs) {
-            CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
-            NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
-            assertFalse(nc.hasCapability(capability));
-            return nc;
-        }
-
-        void expectCapabilitiesLike(Predicate<NetworkCapabilities> fn, MockNetworkAgent agent) {
-            CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
-            assertTrue("Received capabilities don't match expectations : " + cbi.arg,
-                    fn.test((NetworkCapabilities) cbi.arg));
-        }
-
-        void expectLinkPropertiesLike(Predicate<LinkProperties> fn, MockNetworkAgent agent) {
-            CallbackInfo cbi = expectCallback(CallbackState.LINK_PROPERTIES, agent);
-            assertTrue("Received LinkProperties don't match expectations : " + cbi.arg,
-                    fn.test((LinkProperties) cbi.arg));
-        }
-
-        void expectBlockedStatusCallback(boolean expectBlocked, MockNetworkAgent agent) {
-            CallbackInfo cbi = expectCallback(CallbackState.BLOCKED_STATUS, agent);
-            boolean actualBlocked = (boolean) cbi.arg;
-            assertEquals(expectBlocked, actualBlocked);
-        }
-
-        void assertNoCallback() {
-            waitForIdle();
-            CallbackInfo c = mCallbacks.peek();
-            assertNull("Unexpected callback: " + c, c);
+            return callback;
         }
     }
 
@@ -1971,7 +1513,7 @@
 
         // Test unvalidated networks
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
         genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
@@ -1986,7 +1528,7 @@
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         cv = waitForConnectivityBroadcasts(2);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
@@ -1996,21 +1538,21 @@
 
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent.disconnect();
-        genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        wifiNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         cellNetworkCallback.assertNoCallback();
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
         cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent.disconnect();
-        genericNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
         // Test validated networks
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
@@ -2023,29 +1565,29 @@
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
         mWiFiNetworkAgent.disconnect();
-        genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        wifiNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
         mCellNetworkAgent.disconnect();
-        genericNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
     }
 
     @Test
-    public void testMultipleLingering() {
+    public void testMultipleLingering() throws Exception {
         // This test would be flaky with the default 120ms timer: that is short enough that
         // lingered networks are torn down before assertions can be run. We don't want to mock the
         // lingering timer to keep the WakeupMessage logic realistic: this has already proven useful
@@ -2061,9 +1603,9 @@
         TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
 
         mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
@@ -2080,7 +1622,7 @@
         // We then get LOSING when wifi validates and cell is outscored.
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -2089,20 +1631,20 @@
         mEthernetNetworkAgent.connect(true);
         callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mWiFiNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         mEthernetNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
         defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         for (int i = 0; i < 4; i++) {
-            MockNetworkAgent oldNetwork, newNetwork;
+            TestNetworkAgentWrapper oldNetwork, newNetwork;
             if (i % 2 == 0) {
                 mWiFiNetworkAgent.adjustScore(-15);
                 oldNetwork = mWiFiNetworkAgent;
@@ -2113,7 +1655,7 @@
                 newNetwork = mWiFiNetworkAgent;
 
             }
-            callback.expectCallback(CallbackState.LOSING, oldNetwork);
+            callback.expectCallback(CallbackRecord.LOSING, oldNetwork);
             // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no
             // longer lingering?
             defaultCallback.expectAvailableCallbacksValidated(newNetwork);
@@ -2127,7 +1669,7 @@
         // We expect a notification about the capabilities change, and nothing else.
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
         defaultCallback.assertNoCallback();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Wifi no longer satisfies our listen, which is for an unmetered network.
@@ -2136,11 +1678,11 @@
 
         // Disconnect our test networks.
         mWiFiNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
         mCellNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
@@ -2154,7 +1696,7 @@
 
         mCm.registerNetworkCallback(request, callback);
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);   // Score: 10
         callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
@@ -2163,7 +1705,7 @@
 
         // Bring up wifi with a score of 20.
         // Cell stays up because it would satisfy the default request if it validated.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);   // Score: 20
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
@@ -2171,65 +1713,65 @@
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 70.
         // Cell is lingered because it would not satisfy any request, even if it validated.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.adjustScore(50);
         mWiFiNetworkAgent.connect(false);   // Score: 70
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Tear down wifi.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
         // it's arguably correct to linger it, since it was the default network before it validated.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
         // If a network is lingering, and we add and remove a request from it, resume lingering.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
@@ -2240,13 +1782,13 @@
         // TODO: should this cause an AVAILABLE callback, to indicate that the network is no longer
         // lingering?
         mCm.unregisterNetworkCallback(noopCallback);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
 
         // Similar to the above: lingering can start even after the lingered request is removed.
         // Disconnect wifi and switch to cell.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
@@ -2255,7 +1797,7 @@
         mCm.requestNetwork(cellRequest, noopCallback);
 
         // Now connect wifi, and expect it to become the default network.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
@@ -2265,34 +1807,34 @@
         callback.assertNoCallback();
         // Now unregister cellRequest and expect cell to start lingering.
         mCm.unregisterNetworkCallback(noopCallback);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
 
         // Let linger run its course.
         callback.assertNoCallback();
         final int lingerTimeoutMs = mService.mLingerDelayMs + mService.mLingerDelayMs / 4;
-        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
+        callback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent, lingerTimeoutMs);
 
         // Register a TRACK_DEFAULT request and check that it does not affect lingering.
         TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(trackDefaultCallback);
         trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
         mEthernetNetworkAgent.connect(true);
         callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mWiFiNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
         trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Let linger run its course.
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
 
         // Clean up.
         mEthernetNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
-        trackDefaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+        trackDefaultCallback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
 
         mCm.unregisterNetworkCallback(callback);
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -2300,7 +1842,7 @@
     }
 
     @Test
-    public void testNetworkGoesIntoBackgroundAfterLinger() {
+    public void testNetworkGoesIntoBackgroundAfterLinger() throws Exception {
         setAlwaysOnNetworks(true);
         NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities()
@@ -2311,8 +1853,8 @@
         TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
 
         mCellNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
@@ -2322,7 +1864,7 @@
         mWiFiNetworkAgent.connect(true);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
 
         // File a request for cellular, then release it.
@@ -2331,7 +1873,7 @@
         NetworkCallback noopCallback = new NetworkCallback();
         mCm.requestNetwork(cellRequest, noopCallback);
         mCm.unregisterNetworkCallback(noopCallback);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
 
         // Let linger run its course.
         callback.assertNoCallback();
@@ -2345,7 +1887,7 @@
     }
 
     @Test
-    public void testExplicitlySelected() {
+    public void testExplicitlySelected() throws Exception {
         NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
                 .build();
@@ -2353,13 +1895,13 @@
         mCm.registerNetworkCallback(request, callback);
 
         // Bring up validated cell.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
 
         // Bring up unvalidated wifi with explicitlySelected=true.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(false);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(true, false);
         mWiFiNetworkAgent.connect(false);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
 
@@ -2375,47 +1917,69 @@
         // If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to
         // wifi even though it's unvalidated.
         mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Disconnect wifi, and then reconnect, again with explicitlySelected=true.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(false);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(true, false);
         mWiFiNetworkAgent.connect(false);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
 
         // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
         // network to disconnect.
         mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false);
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Reconnect, again with explicitlySelected=true, but this time validate.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(false);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(true, false);
         mWiFiNetworkAgent.connect(true);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // BUG: the network will no longer linger, even though it's validated and outscored.
         // TODO: fix this.
-        mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
         mEthernetNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         callback.assertNoCallback();
 
+        // Disconnect wifi, and then reconnect as if the user had selected "yes, don't ask again"
+        // (i.e., with explicitlySelected=true and acceptUnvalidated=true). Expect to switch to
+        // wifi immediately.
+        mWiFiNetworkAgent.disconnect();
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(true, true);
+        mWiFiNetworkAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mEthernetNetworkAgent);
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        mEthernetNetworkAgent.disconnect();
+        callback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+
+        // Disconnect and reconnect with explicitlySelected=false and acceptUnvalidated=true.
+        // Check that the network is not scored specially and that the device prefers cell data.
+        mWiFiNetworkAgent.disconnect();
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(false, true);
+        mWiFiNetworkAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
         // Clean up.
         mWiFiNetworkAgent.disconnect();
         mCellNetworkAgent.disconnect();
-        mEthernetNetworkAgent.disconnect();
 
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
     }
 
     private int[] makeIntArray(final int size, final int value) {
@@ -2466,7 +2030,7 @@
         assertTrue(testFactory.getMyStartRequested());
 
         // Now bring in a higher scored network.
-        MockNetworkAgent testAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        TestNetworkAgentWrapper testAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         // Rather than create a validated network which complicates things by registering it's
         // own NetworkRequest during startup, just bump up the score to cancel out the
         // unvalidated penalty.
@@ -2551,27 +2115,26 @@
                 .build();
 
         Class<IllegalArgumentException> expected = IllegalArgumentException.class;
-        assertException(() -> { mCm.requestNetwork(request1, new NetworkCallback()); }, expected);
-        assertException(() -> { mCm.requestNetwork(request1, pendingIntent); }, expected);
-        assertException(() -> { mCm.requestNetwork(request2, new NetworkCallback()); }, expected);
-        assertException(() -> { mCm.requestNetwork(request2, pendingIntent); }, expected);
+        assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback()));
+        assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent));
+        assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback()));
+        assertThrows(expected, () -> mCm.requestNetwork(request2, pendingIntent));
     }
 
     @Test
     public void testMMSonWiFi() throws Exception {
         // Test bringing up cellular without MMS NetworkRequest gets reaped
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
-        ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV();
         mCellNetworkAgent.connectWithoutInternet();
-        waitFor(cv);
+        mCellNetworkAgent.expectDisconnected();
         waitForIdle();
         assertEmpty(mCm.getAllNetworks());
         verifyNoNetwork();
 
         // Test bringing up validated WiFi.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        cv = waitForConnectivityBroadcasts(1);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        final ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.connect(true);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
@@ -2583,23 +2146,22 @@
         mCm.requestNetwork(builder.build(), networkCallback);
 
         // Test bringing up unvalidated cellular with MMS
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mCellNetworkAgent.connectWithoutInternet();
         networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         verifyActiveNetwork(TRANSPORT_WIFI);
 
         // Test releasing NetworkRequest disconnects cellular with MMS
-        cv = mCellNetworkAgent.getDisconnectedCV();
         mCm.unregisterNetworkCallback(networkCallback);
-        waitFor(cv);
+        mCellNetworkAgent.expectDisconnected();
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
     @Test
     public void testMMSonCell() throws Exception {
         // Test bringing up cellular without MMS
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent.connect(false);
         waitFor(cv);
@@ -2612,21 +2174,22 @@
         mCm.requestNetwork(builder.build(), networkCallback);
 
         // Test bringing up MMS cellular network
-        MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        TestNetworkAgentWrapper
+                mmsNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mmsNetworkAgent.connectWithoutInternet();
         networkCallback.expectAvailableCallbacksUnvalidated(mmsNetworkAgent);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
 
         // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
-        cv = mmsNetworkAgent.getDisconnectedCV();
         mCm.unregisterNetworkCallback(networkCallback);
-        waitFor(cv);
+        mmsNetworkAgent.expectDisconnected();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
     }
 
     @Test
-    public void testPartialConnectivity() {
+    @FlakyTest(bugId = 140306320)
+    public void testPartialConnectivity() throws Exception {
         // Register network callback.
         NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
@@ -2635,12 +2198,12 @@
         mCm.registerNetworkCallback(request, callback);
 
         // Bring up validated mobile data.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
 
         // Bring up wifi with partial connectivity.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connectWithPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
@@ -2651,7 +2214,7 @@
 
         // With HTTPS probe disabled, NetworkMonitor should pass the network validation with http
         // probe.
-        mWiFiNetworkAgent.setNetworkValid();
+        mWiFiNetworkAgent.setNetworkPartialValid();
         // If the user chooses yes to use this partial connectivity wifi, switch the default
         // network to wifi and check if wifi becomes valid or not.
         mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
@@ -2659,15 +2222,12 @@
         // If user accepts partial connectivity network,
         // NetworkMonitor#setAcceptPartialConnectivity() should be called too.
         waitForIdle();
-        try {
-            verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
-        } catch (RemoteException e) {
-            fail(e.getMessage());
-        }
+        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
+
         // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
         // validated.
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         NetworkCapabilities nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED,
                 mWiFiNetworkAgent);
         assertTrue(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
@@ -2675,8 +2235,8 @@
 
         // Disconnect and reconnect wifi with partial connectivity again.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connectWithPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
@@ -2687,69 +2247,79 @@
         // If the user chooses no, disconnect wifi immediately.
         mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false/* accept */,
                 false /* always */);
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // If user accepted partial connectivity before, and device reconnects to that network
         // again, but now the network has full connectivity. The network shouldn't contain
         // NET_CAPABILITY_PARTIAL_CONNECTIVITY.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         // acceptUnvalidated is also used as setting for accepting partial networks.
-        mWiFiNetworkAgent.explicitlySelected(true /* acceptUnvalidated */);
+        mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */,
+                true /* acceptUnvalidated */);
         mWiFiNetworkAgent.connect(true);
+
         // If user accepted partial connectivity network before,
         // NetworkMonitor#setAcceptPartialConnectivity() will be called in
         // ConnectivityService#updateNetworkInfo().
         waitForIdle();
-        try {
-            verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
-        } catch (RemoteException e) {
-            fail(e.getMessage());
-        }
+        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
+
         // Wifi should be the default network.
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
-        // If user accepted partial connectivity before, and now the device reconnects to the
-        // partial connectivity network. The network should be valid and contain
-        // NET_CAPABILITY_PARTIAL_CONNECTIVITY.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(true /* acceptUnvalidated */);
-        // Current design cannot send multi-testResult from NetworkMonitor to ConnectivityService.
-        // So, if user accepts partial connectivity, NetworkMonitor will send PARTIAL_CONNECTIVITY
-        // to ConnectivityService first then send VALID. Once NetworkMonitor support
-        // multi-testResult, this test case also need to be changed to meet the new design.
+        // The user accepted partial connectivity and selected "don't ask again". Now the user
+        // reconnects to the partial connectivity network. Switch to wifi as soon as partial
+        // connectivity is detected.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */,
+                true /* acceptUnvalidated */);
         mWiFiNetworkAgent.connectWithPartialConnectivity();
         // If user accepted partial connectivity network before,
         // NetworkMonitor#setAcceptPartialConnectivity() will be called in
         // ConnectivityService#updateNetworkInfo().
         waitForIdle();
-        try {
-            verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
-        } catch (RemoteException e) {
-            fail(e.getMessage());
-        }
+        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        // TODO: If the user accepted partial connectivity, we shouldn't switch to wifi until
-        // NetworkMonitor detects partial connectivity
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
         mWiFiNetworkAgent.setNetworkValid();
+
         // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
         // validated.
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+
+        // If the user accepted partial connectivity, and the device auto-reconnects to the partial
+        // connectivity network, it should contain both PARTIAL_CONNECTIVITY and VALIDATED.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(false /* explicitlySelected */,
+                true /* acceptUnvalidated */);
+
+        // NetworkMonitor will immediately (once the HTTPS probe fails...) report the network as
+        // valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls
+        // notifyNetworkConnected.
+        mWiFiNetworkAgent.connectWithPartialValidConnectivity();
+        waitForIdle();
+        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
+        callback.expectCapabilitiesWith(
+                NET_CAPABILITY_PARTIAL_CONNECTIVITY | NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        mWiFiNetworkAgent.disconnect();
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
     }
 
     @Test
-    public void testCaptivePortal() {
+    public void testCaptivePortalOnPartialConnectivity() throws Exception {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
@@ -2762,7 +2332,56 @@
 
         // Bring up a network with a captive portal.
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        String redirectUrl = "http://android.com/path";
+        mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl);
+
+        // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
+        mCm.startCaptivePortalApp(mWiFiNetworkAgent.getNetwork());
+        verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
+                .launchCaptivePortalApp();
+
+        // Report that the captive portal is dismissed with partial connectivity, and check that
+        // callbacks are fired.
+        mWiFiNetworkAgent.setNetworkPartial();
+        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+        waitForIdle();
+        captivePortalCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+                mWiFiNetworkAgent);
+
+        // Report partial connectivity is accepted.
+        mWiFiNetworkAgent.setNetworkPartialValid();
+        mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
+                false /* always */);
+        waitForIdle();
+        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+        captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+        NetworkCapabilities nc =
+                validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+                mWiFiNetworkAgent);
+
+        mCm.unregisterNetworkCallback(captivePortalCallback);
+        mCm.unregisterNetworkCallback(validatedCallback);
+    }
+
+    @Test
+    public void testCaptivePortal() throws Exception {
+        final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+        final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+        mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+        final TestNetworkCallback validatedCallback = new TestNetworkCallback();
+        final NetworkRequest validatedRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_VALIDATED).build();
+        mCm.registerNetworkCallback(validatedRequest, validatedCallback);
+
+        // Bring up a network with a captive portal.
+        // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         String firstRedirectUrl = "http://example.com/firstPath";
         mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
         captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
@@ -2771,11 +2390,11 @@
         // Take down network.
         // Expect onLost callback.
         mWiFiNetworkAgent.disconnect();
-        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Bring up a network with a captive portal.
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         String secondRedirectUrl = "http://example.com/secondPath";
         mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
         captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
@@ -2785,20 +2404,23 @@
         // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
         mWiFiNetworkAgent.setNetworkValid();
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
         validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        // Expect no notification to be shown when captive portal disappears by itself
+        verify(mNotificationManager, never()).notifyAsUser(
+                anyString(), eq(NotificationType.LOGGED_IN.eventId), any(), any());
 
         // Break network connectivity.
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
         mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
-        validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        validatedCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
     }
 
     @Test
-    public void testCaptivePortalApp() throws RemoteException {
+    public void testCaptivePortalApp() throws Exception {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
@@ -2810,7 +2432,7 @@
         mCm.registerNetworkCallback(validatedRequest, validatedCallback);
 
         // Bring up wifi.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
@@ -2818,31 +2440,45 @@
         // Check that calling startCaptivePortalApp does nothing.
         final int fastTimeoutMs = 100;
         mCm.startCaptivePortalApp(wifiNetwork);
+        waitForIdle();
+        verify(mWiFiNetworkAgent.mNetworkMonitor, never()).launchCaptivePortalApp();
         mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
 
         // Turn into a captive portal.
         mWiFiNetworkAgent.setNetworkPortal("http://example.com");
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        validatedCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
         mCm.startCaptivePortalApp(wifiNetwork);
-        verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
-                .launchCaptivePortalApp();
+        waitForIdle();
+        verify(mWiFiNetworkAgent.mNetworkMonitor).launchCaptivePortalApp();
+
+        // NetworkMonitor uses startCaptivePortal(Network, Bundle) (startCaptivePortalAppInternal)
+        final Bundle testBundle = new Bundle();
+        final String testKey = "testkey";
+        final String testValue = "testvalue";
+        testBundle.putString(testKey, testValue);
+        mCm.startCaptivePortalApp(wifiNetwork, testBundle);
+        final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
+        assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction());
+        assertEquals(testValue, signInIntent.getStringExtra(testKey));
 
         // Report that the captive portal is dismissed, and check that callbacks are fired
         mWiFiNetworkAgent.setNetworkValid();
         mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
         validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        verify(mNotificationManager, times(1)).notifyAsUser(anyString(),
+                eq(NotificationType.LOGGED_IN.eventId), any(), eq(UserHandle.ALL));
 
         mCm.unregisterNetworkCallback(validatedCallback);
         mCm.unregisterNetworkCallback(captivePortalCallback);
     }
 
     @Test
-    public void testAvoidOrIgnoreCaptivePortals() {
+    public void testAvoidOrIgnoreCaptivePortals() throws Exception {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
@@ -2856,14 +2492,12 @@
         setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_AVOID);
         // Bring up a network with a captive portal.
         // Expect it to fail to connect and not result in any callbacks.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         String firstRedirectUrl = "http://example.com/firstPath";
 
-        ConditionVariable disconnectCv = mWiFiNetworkAgent.getDisconnectedCV();
-        ConditionVariable avoidCv = mWiFiNetworkAgent.getPreventReconnectReceived();
         mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
-        waitFor(disconnectCv);
-        waitFor(avoidCv);
+        mWiFiNetworkAgent.expectDisconnected();
+        mWiFiNetworkAgent.expectPreventReconnectReceived();
 
         assertNoCallbacks(captivePortalCallback, validatedCallback);
     }
@@ -2882,7 +2516,7 @@
      * does work.
      */
     @Test
-    public void testNetworkSpecifier() {
+    public void testNetworkSpecifier() throws Exception {
         // A NetworkSpecifier subclass that matches all networks but must not be visible to apps.
         class ConfidentialMatchAllNetworkSpecifier extends NetworkSpecifier implements
                 Parcelable {
@@ -2962,7 +2596,7 @@
         LocalStringNetworkSpecifier nsFoo = new LocalStringNetworkSpecifier("foo");
         LocalStringNetworkSpecifier nsBar = new LocalStringNetworkSpecifier("bar");
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         cEmpty1.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         cEmpty2.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
@@ -2973,24 +2607,24 @@
         mWiFiNetworkAgent.setNetworkSpecifier(nsFoo);
         cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
-            c.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsFoo),
-                    mWiFiNetworkAgent);
+            c.expectCapabilitiesThat(mWiFiNetworkAgent,
+                    (caps) -> caps.getNetworkSpecifier().equals(nsFoo));
         }
-        cFoo.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsFoo),
-                mWiFiNetworkAgent);
+        cFoo.expectCapabilitiesThat(mWiFiNetworkAgent,
+                (caps) -> caps.getNetworkSpecifier().equals(nsFoo));
         assertEquals(nsFoo,
                 mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
         cFoo.assertNoCallback();
 
         mWiFiNetworkAgent.setNetworkSpecifier(nsBar);
-        cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        cFoo.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
-            c.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsBar),
-                    mWiFiNetworkAgent);
+            c.expectCapabilitiesThat(mWiFiNetworkAgent,
+                    (caps) -> caps.getNetworkSpecifier().equals(nsBar));
         }
-        cBar.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsBar),
-                mWiFiNetworkAgent);
+        cBar.expectCapabilitiesThat(mWiFiNetworkAgent,
+                (caps) -> caps.getNetworkSpecifier().equals(nsBar));
         assertEquals(nsBar,
                 mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
         cBar.assertNoCallback();
@@ -2998,23 +2632,23 @@
         mWiFiNetworkAgent.setNetworkSpecifier(new ConfidentialMatchAllNetworkSpecifier());
         cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         for (TestNetworkCallback c : emptyCallbacks) {
-            c.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier() == null,
-                    mWiFiNetworkAgent);
+            c.expectCapabilitiesThat(mWiFiNetworkAgent,
+                    (caps) -> caps.getNetworkSpecifier() == null);
         }
-        cFoo.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier() == null,
-                mWiFiNetworkAgent);
-        cBar.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier() == null,
-                mWiFiNetworkAgent);
+        cFoo.expectCapabilitiesThat(mWiFiNetworkAgent,
+                (caps) -> caps.getNetworkSpecifier() == null);
+        cBar.expectCapabilitiesThat(mWiFiNetworkAgent,
+                (caps) -> caps.getNetworkSpecifier() == null);
         assertNull(
                 mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
         cFoo.assertNoCallback();
         cBar.assertNoCallback();
 
         mWiFiNetworkAgent.setNetworkSpecifier(null);
-        cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        cBar.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        cFoo.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        cBar.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
-            c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+            c.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent);
         }
 
         assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar);
@@ -3022,24 +2656,18 @@
 
     @Test
     public void testInvalidNetworkSpecifier() {
-        try {
+        assertThrows(IllegalArgumentException.class, () -> {
             NetworkRequest.Builder builder = new NetworkRequest.Builder();
             builder.setNetworkSpecifier(new MatchAllNetworkSpecifier());
-            fail("NetworkRequest builder with MatchAllNetworkSpecifier");
-        } catch (IllegalArgumentException expected) {
-            // expected
-        }
+        });
 
-        try {
+        assertThrows(IllegalArgumentException.class, () -> {
             NetworkCapabilities networkCapabilities = new NetworkCapabilities();
             networkCapabilities.addTransportType(TRANSPORT_WIFI)
                     .setNetworkSpecifier(new MatchAllNetworkSpecifier());
             mService.requestNetwork(networkCapabilities, null, 0, null,
                     ConnectivityManager.TYPE_WIFI);
-            fail("ConnectivityService requestNetwork with MatchAllNetworkSpecifier");
-        } catch (IllegalArgumentException expected) {
-            // expected
-        }
+        });
 
         class NonParcelableSpecifier extends NetworkSpecifier {
             public boolean satisfiedBy(NetworkSpecifier other) { return false; }
@@ -3048,24 +2676,22 @@
             @Override public int describeContents() { return 0; }
             @Override public void writeToParcel(Parcel p, int flags) {}
         }
-        NetworkRequest.Builder builder;
 
-        builder = new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET);
-        try {
+        final NetworkRequest.Builder builder =
+                new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET);
+        assertThrows(ClassCastException.class, () -> {
             builder.setNetworkSpecifier(new NonParcelableSpecifier());
             Parcel parcelW = Parcel.obtain();
             builder.build().writeToParcel(parcelW, 0);
-            fail("Parceling a non-parcelable specifier did not throw an exception");
-        } catch (Exception e) {
-            // expected
-        }
+        });
 
-        builder = new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET);
-        builder.setNetworkSpecifier(new ParcelableSpecifier());
-        NetworkRequest nr = builder.build();
+        final NetworkRequest nr =
+                new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET)
+                .setNetworkSpecifier(new ParcelableSpecifier())
+                .build();
         assertNotNull(nr);
 
-        try {
+        assertThrows(BadParcelableException.class, () -> {
             Parcel parcelW = Parcel.obtain();
             nr.writeToParcel(parcelW, 0);
             byte[] bytes = parcelW.marshall();
@@ -3075,14 +2701,11 @@
             parcelR.unmarshall(bytes, 0, bytes.length);
             parcelR.setDataPosition(0);
             NetworkRequest rereadNr = NetworkRequest.CREATOR.createFromParcel(parcelR);
-            fail("Unparceling a non-framework NetworkSpecifier did not throw an exception");
-        } catch (Exception e) {
-            // expected
-        }
+        });
     }
 
     @Test
-    public void testNetworkSpecifierUidSpoofSecurityException() {
+    public void testNetworkSpecifierUidSpoofSecurityException() throws Exception {
         class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
             @Override
             public boolean satisfiedBy(NetworkSpecifier other) {
@@ -3100,19 +2723,16 @@
             public void writeToParcel(Parcel dest, int flags) {}
         }
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
 
         UidAwareNetworkSpecifier networkSpecifier = new UidAwareNetworkSpecifier();
         NetworkRequest networkRequest = newWifiRequestBuilder().setNetworkSpecifier(
                 networkSpecifier).build();
         TestNetworkCallback networkCallback = new TestNetworkCallback();
-        try {
+        assertThrows(SecurityException.class, () -> {
             mCm.requestNetwork(networkRequest, networkCallback);
-            fail("Network request with spoofed UID did not throw a SecurityException");
-        } catch (SecurityException e) {
-            // expected
-        }
+        });
     }
 
     @Test
@@ -3124,36 +2744,20 @@
                 .build();
         // Registering a NetworkCallback with signal strength but w/o NETWORK_SIGNAL_STRENGTH_WAKEUP
         // permission should get SecurityException.
-        try {
-            mCm.registerNetworkCallback(r, new NetworkCallback());
-            fail("Expected SecurityException filing a callback with signal strength");
-        } catch (SecurityException expected) {
-            // expected
-        }
+        assertThrows(SecurityException.class, () ->
+                mCm.registerNetworkCallback(r, new NetworkCallback()));
 
-        try {
-            mCm.registerNetworkCallback(r, PendingIntent.getService(
-                    mServiceContext, 0, new Intent(), 0));
-            fail("Expected SecurityException filing a callback with signal strength");
-        } catch (SecurityException expected) {
-            // expected
-        }
+        assertThrows(SecurityException.class, () ->
+                mCm.registerNetworkCallback(r, PendingIntent.getService(
+                        mServiceContext, 0, new Intent(), 0)));
 
         // Requesting a Network with signal strength should get IllegalArgumentException.
-        try {
-            mCm.requestNetwork(r, new NetworkCallback());
-            fail("Expected IllegalArgumentException filing a request with signal strength");
-        } catch (IllegalArgumentException expected) {
-            // expected
-        }
+        assertThrows(IllegalArgumentException.class, () ->
+                mCm.requestNetwork(r, new NetworkCallback()));
 
-        try {
-            mCm.requestNetwork(r, PendingIntent.getService(
-                    mServiceContext, 0, new Intent(), 0));
-            fail("Expected IllegalArgumentException filing a request with signal strength");
-        } catch (IllegalArgumentException expected) {
-            // expected
-        }
+        assertThrows(IllegalArgumentException.class, () ->
+                mCm.requestNetwork(r, PendingIntent.getService(
+                        mServiceContext, 0, new Intent(), 0)));
     }
 
     @Test
@@ -3172,14 +2776,14 @@
         cellNetworkCallback.assertNoCallback();
 
         // Bring up cell and expect CALLBACK_AVAILABLE.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi and expect CALLBACK_AVAILABLE.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
@@ -3187,12 +2791,12 @@
 
         // Bring down cell. Expect no default network callback, since it wasn't the default.
         mCellNetworkAgent.disconnect();
-        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up cell. Expect no default network callback, since it won't be the default.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
@@ -3202,16 +2806,17 @@
         // followed by AVAILABLE cell.
         mWiFiNetworkAgent.disconnect();
         cellNetworkCallback.assertNoCallback();
-        defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        defaultNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
-        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+        defaultNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
         final int uid = Process.myUid();
-        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        final TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
@@ -3222,7 +2827,7 @@
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         vpnNetworkAgent.disconnect();
-        defaultNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        defaultNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
     }
@@ -3236,7 +2841,7 @@
         mCm.requestNetwork(cellRequest, cellNetworkCallback);
 
         // Bring up the mobile network.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
 
         // We should get onAvailable(), onCapabilitiesChanged(), and
@@ -3250,14 +2855,15 @@
         lp.setInterfaceName("foonet_data0");
         mCellNetworkAgent.sendLinkProperties(lp);
         // We should get onLinkPropertiesChanged().
-        cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
+                mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
         // Suspend the network.
         mCellNetworkAgent.suspend();
         cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_SUSPENDED,
                 mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.SUSPENDED, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.SUSPENDED, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
         // Register a garden variety default network request.
@@ -3272,7 +2878,7 @@
         mCellNetworkAgent.resume();
         cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_SUSPENDED,
                 mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.RESUMED, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.RESUMED, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
         dfltNetworkCallback = new TestNetworkCallback();
@@ -3305,7 +2911,7 @@
         waitForIdle();
     }
 
-    private boolean isForegroundNetwork(MockNetworkAgent network) {
+    private boolean isForegroundNetwork(TestNetworkAgentWrapper network) {
         NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork());
         assertNotNull(nc);
         return nc.hasCapability(NET_CAPABILITY_FOREGROUND);
@@ -3324,21 +2930,21 @@
         mCm.registerNetworkCallback(request, callback);
         mCm.registerNetworkCallback(fgRequest, fgCallback);
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
 
         // When wifi connects, cell lingers.
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        fgCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -3346,7 +2952,7 @@
         // When lingering is complete, cell is still there but is now in the background.
         waitForIdle();
         int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
-        fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
+        fgCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent, timeoutMs);
         // Expect a network capabilities update sans FOREGROUND.
         callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
         assertFalse(isForegroundNetwork(mCellNetworkAgent));
@@ -3372,7 +2978,7 @@
         // Release the request. The network immediately goes into the background, since it was not
         // lingering.
         mCm.unregisterNetworkCallback(cellCallback);
-        fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        fgCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         // Expect a network capabilities update sans FOREGROUND.
         callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
         assertFalse(isForegroundNetwork(mCellNetworkAgent));
@@ -3380,8 +2986,8 @@
 
         // Disconnect wifi and check that cell is foreground again.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        fgCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
@@ -3417,20 +3023,20 @@
             };
         }
 
-        assertTimeLimit("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> {
+        assertRunsInAtMost("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> {
             for (NetworkCallback cb : callbacks) {
                 mCm.registerNetworkCallback(request, cb);
             }
         });
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         // Don't request that the network validate, because otherwise connect() will block until
         // the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired,
         // and we won't actually measure anything.
         mCellNetworkAgent.connect(false);
 
         long onAvailableDispatchingDuration = durationOf(() -> {
-            awaitLatch(availableLatch, 10 * CONNECT_TIME_LIMIT_MS);
+            await(availableLatch, 10 * CONNECT_TIME_LIMIT_MS);
         });
         Log.d(TAG, String.format("Dispatched %d of %d onAvailable callbacks in %dms",
                 NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
@@ -3440,12 +3046,12 @@
                 onAvailableDispatchingDuration <= CONNECT_TIME_LIMIT_MS);
 
         // Give wifi a high enough score that we'll linger cell when wifi comes up.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.adjustScore(40);
         mWiFiNetworkAgent.connect(false);
 
         long onLostDispatchingDuration = durationOf(() -> {
-            awaitLatch(losingLatch, 10 * SWITCH_TIME_LIMIT_MS);
+            await(losingLatch, 10 * SWITCH_TIME_LIMIT_MS);
         });
         Log.d(TAG, String.format("Dispatched %d of %d onLosing callbacks in %dms",
                 NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, onLostDispatchingDuration));
@@ -3453,33 +3059,13 @@
                 NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS),
                 onLostDispatchingDuration <= SWITCH_TIME_LIMIT_MS);
 
-        assertTimeLimit("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
+        assertRunsInAtMost("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
             for (NetworkCallback cb : callbacks) {
                 mCm.unregisterNetworkCallback(cb);
             }
         });
     }
 
-    private long durationOf(Runnable fn) {
-        long startTime = SystemClock.elapsedRealtime();
-        fn.run();
-        return SystemClock.elapsedRealtime() - startTime;
-    }
-
-    private void assertTimeLimit(String descr, long timeLimit, Runnable fn) {
-        long timeTaken = durationOf(fn);
-        String msg = String.format("%s: took %dms, limit was %dms", descr, timeTaken, timeLimit);
-        Log.d(TAG, msg);
-        assertTrue(msg, timeTaken <= timeLimit);
-    }
-
-    private boolean awaitLatch(CountDownLatch l, long timeoutMs) {
-        try {
-            return l.await(timeoutMs, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {}
-        return false;
-    }
-
     @Test
     public void testMobileDataAlwaysOn() throws Exception {
         final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
@@ -3503,7 +3089,7 @@
         assertTrue(testFactory.getMyStartRequested());
 
         // Bring up wifi. The factory stops looking for a network.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         // Score 60 - 40 penalty for not validated yet, then 60 when it validates
         testFactory.expectAddRequestsWithScores(20, 60);
         mWiFiNetworkAgent.connect(true);
@@ -3520,7 +3106,7 @@
 
         // Bring up cell data and check that the factory stops looking.
         assertLength(1, mCm.getAllNetworks());
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         testFactory.expectAddRequestsWithScores(10, 50);  // Unvalidated, then validated
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
@@ -3538,7 +3124,7 @@
         testFactory.waitForNetworkRequests(1);
 
         // ...  and cell data to be torn down.
-        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         assertLength(1, mCm.getAllNetworks());
 
         testFactory.unregister();
@@ -3549,48 +3135,46 @@
     @Test
     public void testAvoidBadWifiSetting() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
-        final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
         final String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI;
 
-        tracker.configRestrictsAvoidBadWifi = false;
+        mPolicyTracker.mConfigRestrictsAvoidBadWifi = false;
         String[] values = new String[] {null, "0", "1"};
         for (int i = 0; i < values.length; i++) {
             Settings.Global.putInt(cr, settingName, 1);
-            tracker.reevaluate();
+            mPolicyTracker.reevaluate();
             waitForIdle();
             String msg = String.format("config=false, setting=%s", values[i]);
             assertTrue(mService.avoidBadWifi());
-            assertFalse(msg, tracker.shouldNotifyWifiUnvalidated());
+            assertFalse(msg, mPolicyTracker.shouldNotifyWifiUnvalidated());
         }
 
-        tracker.configRestrictsAvoidBadWifi = true;
+        mPolicyTracker.mConfigRestrictsAvoidBadWifi = true;
 
         Settings.Global.putInt(cr, settingName, 0);
-        tracker.reevaluate();
+        mPolicyTracker.reevaluate();
         waitForIdle();
         assertFalse(mService.avoidBadWifi());
-        assertFalse(tracker.shouldNotifyWifiUnvalidated());
+        assertFalse(mPolicyTracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putInt(cr, settingName, 1);
-        tracker.reevaluate();
+        mPolicyTracker.reevaluate();
         waitForIdle();
         assertTrue(mService.avoidBadWifi());
-        assertFalse(tracker.shouldNotifyWifiUnvalidated());
+        assertFalse(mPolicyTracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putString(cr, settingName, null);
-        tracker.reevaluate();
+        mPolicyTracker.reevaluate();
         waitForIdle();
         assertFalse(mService.avoidBadWifi());
-        assertTrue(tracker.shouldNotifyWifiUnvalidated());
+        assertTrue(mPolicyTracker.shouldNotifyWifiUnvalidated());
     }
 
     @Test
     public void testAvoidBadWifi() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
-        final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
 
         // Pretend we're on a carrier that restricts switching away from bad wifi.
-        tracker.configRestrictsAvoidBadWifi = true;
+        mPolicyTracker.mConfigRestrictsAvoidBadWifi = true;
 
         // File a request for cell to ensure it doesn't go down.
         final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
@@ -3609,17 +3193,17 @@
         mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback);
 
         Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0);
-        tracker.reevaluate();
+        mPolicyTracker.reevaluate();
 
         // Bring up validated cell.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         Network cellNetwork = mCellNetworkAgent.getNetwork();
 
         // Bring up validated wifi.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
@@ -3629,7 +3213,7 @@
         mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        validatedWifiCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Because avoid bad wifi is off, we don't switch to cellular.
         defaultCallback.assertNoCallback();
@@ -3641,14 +3225,14 @@
 
         // Simulate switching to a carrier that does not restrict avoiding bad wifi, and expect
         // that we switch back to cell.
-        tracker.configRestrictsAvoidBadWifi = false;
-        tracker.reevaluate();
+        mPolicyTracker.mConfigRestrictsAvoidBadWifi = false;
+        mPolicyTracker.reevaluate();
         defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // Switch back to a restrictive carrier.
-        tracker.configRestrictsAvoidBadWifi = true;
-        tracker.reevaluate();
+        mPolicyTracker.mConfigRestrictsAvoidBadWifi = true;
+        mPolicyTracker.reevaluate();
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
 
@@ -3663,7 +3247,7 @@
 
         // Disconnect and reconnect wifi to clear the one-time switch above.
         mWiFiNetworkAgent.disconnect();
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
@@ -3673,11 +3257,11 @@
         mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        validatedWifiCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Simulate the user selecting "switch" and checking the don't ask again checkbox.
         Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
-        tracker.reevaluate();
+        mPolicyTracker.reevaluate();
 
         // We now switch to cell.
         defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
@@ -3690,17 +3274,17 @@
         // Simulate the user turning the cellular fallback setting off and then on.
         // We switch to wifi and then to cell.
         Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
-        tracker.reevaluate();
+        mPolicyTracker.reevaluate();
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
         Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
-        tracker.reevaluate();
+        mPolicyTracker.reevaluate();
         defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // If cell goes down, we switch to wifi.
         mCellNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         validatedWifiCallback.assertNoCallback();
 
@@ -3712,14 +3296,13 @@
     @Test
     public void testMeteredMultipathPreferenceSetting() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
-        final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
         final String settingName = Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
 
         for (int config : Arrays.asList(0, 3, 2)) {
             for (String setting: Arrays.asList(null, "0", "2", "1")) {
-                tracker.configMeteredMultipathPreference = config;
+                mPolicyTracker.mConfigMeteredMultipathPreference = config;
                 Settings.Global.putString(cr, settingName, setting);
-                tracker.reevaluate();
+                mPolicyTracker.reevaluate();
                 waitForIdle();
 
                 final int expected = (setting != null) ? Integer.parseInt(setting) : config;
@@ -3734,13 +3317,13 @@
      * time-out period expires.
      */
     @Test
-    public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() {
+    public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() throws Exception {
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS);
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false,
                 TEST_CALLBACK_TIMEOUT_MS);
@@ -3754,18 +3337,18 @@
      * not trigger onUnavailable() once the time-out period expires.
      */
     @Test
-    public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() {
+    public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() throws Exception {
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS);
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false,
                 TEST_CALLBACK_TIMEOUT_MS);
         mWiFiNetworkAgent.disconnect();
-        networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Validate that UNAVAILABLE is not called
         networkCallback.assertNoCallback();
@@ -3777,7 +3360,7 @@
      * (somehow) satisfied - the callback isn't called later.
      */
     @Test
-    public void testTimedoutNetworkRequest() {
+    public void testTimedoutNetworkRequest() throws Exception {
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
@@ -3785,10 +3368,10 @@
         mCm.requestNetwork(nr, networkCallback, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is called
-        networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
+        networkCallback.expectCallback(CallbackRecord.UNAVAILABLE, null);
 
         // create a network satisfying request - validate that request not triggered
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         networkCallback.assertNoCallback();
     }
@@ -3798,7 +3381,7 @@
      * trigger the callback.
      */
     @Test
-    public void testNoCallbackAfterUnregisteredNetworkRequest() {
+    public void testNoCallbackAfterUnregisteredNetworkRequest() throws Exception {
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
@@ -3811,16 +3394,25 @@
         networkCallback.assertNoCallback();
 
         // create a network satisfying request - validate that request not triggered
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         networkCallback.assertNoCallback();
     }
 
+    @Test
+    public void testUnfulfillableNetworkRequest() throws Exception {
+        runUnfulfillableNetworkRequest(false);
+    }
+
+    @Test
+    public void testUnfulfillableNetworkRequestAfterUnregister() throws Exception {
+        runUnfulfillableNetworkRequest(true);
+    }
+
     /**
      * Validate the callback flow for a factory releasing a request as unfulfillable.
      */
-    @Test
-    public void testUnfulfillableNetworkRequest() throws Exception {
+    private void runUnfulfillableNetworkRequest(boolean preUnregister) throws Exception {
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
@@ -3855,14 +3447,25 @@
             }
         }
 
-        // Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
         testFactory.expectRemoveRequests(1);
-        testFactory.triggerUnfulfillable(requests.get(newRequestId));
-        networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
-        testFactory.waitForRequests();
+        if (preUnregister) {
+            mCm.unregisterNetworkCallback(networkCallback);
 
-        // unregister network callback - a no-op, but should not fail
-        mCm.unregisterNetworkCallback(networkCallback);
+            // Simulate the factory releasing the request as unfulfillable: no-op since
+            // the callback has already been unregistered (but a test that no exceptions are
+            // thrown).
+            testFactory.triggerUnfulfillable(requests.get(newRequestId));
+        } else {
+            // Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
+            testFactory.triggerUnfulfillable(requests.get(newRequestId));
+
+            networkCallback.expectCallback(CallbackRecord.UNAVAILABLE, null);
+            testFactory.waitForRequests();
+
+            // unregister network callback - a no-op (since already freed by the
+            // on-unavailable), but should not fail or throw exceptions.
+            mCm.unregisterNetworkCallback(networkCallback);
+        }
 
         testFactory.unregister();
         handlerThread.quit();
@@ -3870,7 +3473,7 @@
 
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
-        public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+        public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }
 
         private class CallbackValue {
             public CallbackType callbackType;
@@ -3918,25 +3521,19 @@
             mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
         }
 
-        private void expectCallback(CallbackValue callbackValue) {
-            try {
-                assertEquals(
-                        callbackValue,
-                        mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            } catch (InterruptedException e) {
-                fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
-            }
+        private void expectCallback(CallbackValue callbackValue) throws InterruptedException {
+            assertEquals(callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         }
 
-        public void expectStarted() {
+        public void expectStarted() throws Exception {
             expectCallback(new CallbackValue(CallbackType.ON_STARTED));
         }
 
-        public void expectStopped() {
+        public void expectStopped() throws Exception {
             expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
         }
 
-        public void expectError(int error) {
+        public void expectError(int error) throws Exception {
             expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
         }
     }
@@ -3997,25 +3594,20 @@
             mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
         }
 
-        private void expectCallback(CallbackValue callbackValue) {
-            try {
-                assertEquals(
-                        callbackValue,
-                        mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            } catch (InterruptedException e) {
-                fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
-            }
+        private void expectCallback(CallbackValue callbackValue) throws InterruptedException {
+            assertEquals(callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
         }
 
-        public void expectStarted() {
+        public void expectStarted() throws InterruptedException {
             expectCallback(new CallbackValue(CallbackType.ON_STARTED));
         }
 
-        public void expectStopped() {
+        public void expectStopped() throws InterruptedException {
             expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
         }
 
-        public void expectError(int error) {
+        public void expectError(int error) throws InterruptedException {
             expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
         }
 
@@ -4026,13 +3618,13 @@
         }
     }
 
-    private Network connectKeepaliveNetwork(LinkProperties lp) {
+    private Network connectKeepaliveNetwork(LinkProperties lp) throws Exception {
         // Ensure the network is disconnected before we do anything.
         if (mWiFiNetworkAgent != null) {
             assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()));
         }
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.connect(true);
         waitFor(cv);
@@ -4043,6 +3635,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 140305589)
     public void testPacketKeepalives() throws Exception {
         InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
         InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
@@ -4096,10 +3689,10 @@
         callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
 
         // Check that a started keepalive can be stopped.
-        mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS);
+        mWiFiNetworkAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS);
         ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4);
         callback.expectStarted();
-        mWiFiNetworkAgent.setStopKeepaliveError(PacketKeepalive.SUCCESS);
+        mWiFiNetworkAgent.setStopKeepaliveEvent(PacketKeepalive.SUCCESS);
         ka.stop();
         callback.expectStopped();
 
@@ -4117,7 +3710,7 @@
         ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4);
         callback.expectStarted();
         mWiFiNetworkAgent.disconnect();
-        waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+        mWiFiNetworkAgent.expectDisconnected();
         callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
 
         // ... and that stopping it after that has no adverse effects.
@@ -4128,7 +3721,7 @@
 
         // Reconnect.
         myNet = connectKeepaliveNetwork(lp);
-        mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS);
+        mWiFiNetworkAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS);
 
         // Check that keepalive slots start from 1 and increment. The first one gets slot 1.
         mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
@@ -4159,13 +3752,9 @@
         callback3.expectStopped();
     }
 
-    @FunctionalInterface
-    private interface ThrowingConsumer<T> {
-        void accept(T t) throws Exception;
-    }
-
     // Helper method to prepare the executor and run test
-    private void runTestWithSerialExecutors(ThrowingConsumer<Executor> functor) throws Exception {
+    private void runTestWithSerialExecutors(ExceptionUtils.ThrowingConsumer<Executor> functor)
+            throws Exception {
         final ExecutorService executorSingleThread = Executors.newSingleThreadExecutor();
         final Executor executorInline = (Runnable r) -> r.run();
         functor.accept(executorSingleThread);
@@ -4252,12 +3841,12 @@
         }
 
         // Check that a started keepalive can be stopped.
-        mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+        mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
         try (SocketKeepalive ka = mCm.createSocketKeepalive(
                 myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
             ka.start(validKaInterval);
             callback.expectStarted();
-            mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
+            mWiFiNetworkAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS);
             ka.stop();
             callback.expectStopped();
 
@@ -4297,7 +3886,7 @@
             ka.start(validKaInterval);
             callback.expectStarted();
             mWiFiNetworkAgent.disconnect();
-            waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+            mWiFiNetworkAgent.expectDisconnected();
             callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
 
             // ... and that stopping it after that has no adverse effects.
@@ -4310,7 +3899,7 @@
 
         // Reconnect.
         myNet = connectKeepaliveNetwork(lp);
-        mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+        mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
 
         // Check that keepalive slots start from 1 and increment. The first one gets slot 1.
         mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
@@ -4347,7 +3936,7 @@
         // assertFalse(isUdpPortInUse(srcPort2));
 
         mWiFiNetworkAgent.disconnect();
-        waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+        mWiFiNetworkAgent.expectDisconnected();
         mWiFiNetworkAgent = null;
     }
 
@@ -4423,7 +4012,7 @@
         testSocketV6.close();
 
         mWiFiNetworkAgent.disconnect();
-        waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+        mWiFiNetworkAgent.expectDisconnected();
         mWiFiNetworkAgent = null;
     }
 
@@ -4439,8 +4028,8 @@
         lp.addLinkAddress(new LinkAddress(myIPv4, 25));
         lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
         Network myNet = connectKeepaliveNetwork(lp);
-        mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
-        mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
+        mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
+        mWiFiNetworkAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS);
 
         TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor);
 
@@ -4476,14 +4065,14 @@
         // assertFalse(isUdpPortInUse(srcPort));
 
         mWiFiNetworkAgent.disconnect();
-        waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+        mWiFiNetworkAgent.expectDisconnected();
         mWiFiNetworkAgent = null;
     }
 
     private static boolean isUdpPortInUse(int port) {
         try (DatagramSocket ignored = new DatagramSocket(port)) {
             return false;
-        } catch (IOException ignored) {
+        } catch (IOException alreadyInUse) {
             return true;
         }
     }
@@ -4495,23 +4084,19 @@
     }
 
     private static class TestNetworkPinner extends NetworkPinner {
-        public static boolean awaitPin(int timeoutMs) {
+        public static boolean awaitPin(int timeoutMs) throws InterruptedException {
             synchronized(sLock) {
                 if (sNetwork == null) {
-                    try {
-                        sLock.wait(timeoutMs);
-                    } catch (InterruptedException e) {}
+                    sLock.wait(timeoutMs);
                 }
                 return sNetwork != null;
             }
         }
 
-        public static boolean awaitUnpin(int timeoutMs) {
+        public static boolean awaitUnpin(int timeoutMs) throws InterruptedException {
             synchronized(sLock) {
                 if (sNetwork != null) {
-                    try {
-                        sLock.wait(timeoutMs);
-                    } catch (InterruptedException e) {}
+                    sLock.wait(timeoutMs);
                 }
                 return sNetwork == null;
             }
@@ -4534,7 +4119,7 @@
     }
 
     @Test
-    public void testNetworkPinner() {
+    public void testNetworkPinner() throws Exception {
         NetworkRequest wifiRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI)
                 .build();
@@ -4543,9 +4128,9 @@
         TestNetworkPinner.pin(mServiceContext, wifiRequest);
         assertNull(mCm.getBoundNetworkForProcess());
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
 
         // When wi-fi connects, expect to be pinned.
@@ -4558,7 +4143,7 @@
         assertNotPinnedToWifi();
 
         // Reconnecting does not cause the pin to come back.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         assertFalse(TestNetworkPinner.awaitPin(100));
         assertNotPinnedToWifi();
@@ -4580,14 +4165,14 @@
 
         // Pinning takes effect even if the pinned network is the default when the pin is set...
         TestNetworkPinner.pin(mServiceContext, wifiRequest);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         assertTrue(TestNetworkPinner.awaitPin(100));
         assertPinnedToWifiWithWifiDefault();
 
         // ... and is maintained even when that network is no longer the default.
         cv = waitForConnectivityBroadcasts(1);
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mCellNetworkAgent.connect(true);
         waitFor(cv);
         assertPinnedToWifiWithCellDefault();
@@ -4629,25 +4214,20 @@
         }
 
         // Test that the limit is enforced when MAX_REQUESTS simultaneous requests are added.
-        try {
-            mCm.requestNetwork(networkRequest, new NetworkCallback());
-            fail("Registering " + MAX_REQUESTS + " network requests did not throw exception");
-        } catch (TooManyRequestsException expected) {}
-        try {
-            mCm.registerNetworkCallback(networkRequest, new NetworkCallback());
-            fail("Registering " + MAX_REQUESTS + " network callbacks did not throw exception");
-        } catch (TooManyRequestsException expected) {}
-        try {
-            mCm.requestNetwork(networkRequest,
-                PendingIntent.getBroadcast(mContext, 0, new Intent("c"), 0));
-            fail("Registering " + MAX_REQUESTS + " PendingIntent requests did not throw exception");
-        } catch (TooManyRequestsException expected) {}
-        try {
-            mCm.registerNetworkCallback(networkRequest,
-                PendingIntent.getBroadcast(mContext, 0, new Intent("d"), 0));
-            fail("Registering " + MAX_REQUESTS
-                    + " PendingIntent callbacks did not throw exception");
-        } catch (TooManyRequestsException expected) {}
+        assertThrows(TooManyRequestsException.class, () ->
+                mCm.requestNetwork(networkRequest, new NetworkCallback())
+        );
+        assertThrows(TooManyRequestsException.class, () ->
+                mCm.registerNetworkCallback(networkRequest, new NetworkCallback())
+        );
+        assertThrows(TooManyRequestsException.class, () ->
+                mCm.requestNetwork(networkRequest,
+                        PendingIntent.getBroadcast(mContext, 0, new Intent("c"), 0))
+        );
+        assertThrows(TooManyRequestsException.class, () ->
+                mCm.registerNetworkCallback(networkRequest,
+                        PendingIntent.getBroadcast(mContext, 0, new Intent("d"), 0))
+        );
 
         for (Object o : registered) {
             if (o instanceof NetworkCallback) {
@@ -4691,11 +4271,11 @@
     }
 
     @Test
-    public void testNetworkInfoOfTypeNone() {
+    public void testNetworkInfoOfTypeNone() throws Exception {
         ConditionVariable broadcastCV = waitForConnectivityBroadcasts(1);
 
         verifyNoNetwork();
-        MockNetworkAgent wifiAware = new MockNetworkAgent(TRANSPORT_WIFI_AWARE);
+        TestNetworkAgentWrapper wifiAware = new TestNetworkAgentWrapper(TRANSPORT_WIFI_AWARE);
         assertNull(mCm.getActiveNetworkInfo());
 
         Network[] allNetworks = mCm.getAllNetworks();
@@ -4721,7 +4301,7 @@
 
         // Disconnect wifi aware network.
         wifiAware.disconnect();
-        callback.expectCallbackLike((info) -> info.state == CallbackState.LOST, TIMEOUT_MS);
+        callback.expectCallbackThat(TIMEOUT_MS, (info) -> info instanceof CallbackRecord.Lost);
         mCm.unregisterNetworkCallback(callback);
 
         verifyNoNetwork();
@@ -4738,21 +4318,21 @@
         assertNull(mCm.getLinkProperties(TYPE_NONE));
         assertFalse(mCm.isNetworkSupported(TYPE_NONE));
 
-        assertException(() -> { mCm.networkCapabilitiesForType(TYPE_NONE); },
-                IllegalArgumentException.class);
+        assertThrows(IllegalArgumentException.class,
+                () -> { mCm.networkCapabilitiesForType(TYPE_NONE); });
 
         Class<UnsupportedOperationException> unsupported = UnsupportedOperationException.class;
-        assertException(() -> { mCm.startUsingNetworkFeature(TYPE_WIFI, ""); }, unsupported);
-        assertException(() -> { mCm.stopUsingNetworkFeature(TYPE_WIFI, ""); }, unsupported);
+        assertThrows(unsupported, () -> { mCm.startUsingNetworkFeature(TYPE_WIFI, ""); });
+        assertThrows(unsupported, () -> { mCm.stopUsingNetworkFeature(TYPE_WIFI, ""); });
         // TODO: let test context have configuration application target sdk version
         // and test that pre-M requesting for TYPE_NONE sends back APN_REQUEST_FAILED
-        assertException(() -> { mCm.startUsingNetworkFeature(TYPE_NONE, ""); }, unsupported);
-        assertException(() -> { mCm.stopUsingNetworkFeature(TYPE_NONE, ""); }, unsupported);
-        assertException(() -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); }, unsupported);
+        assertThrows(unsupported, () -> { mCm.startUsingNetworkFeature(TYPE_NONE, ""); });
+        assertThrows(unsupported, () -> { mCm.stopUsingNetworkFeature(TYPE_NONE, ""); });
+        assertThrows(unsupported, () -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); });
     }
 
     @Test
-    public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() {
+    public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() throws Exception {
         final NetworkRequest networkRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
@@ -4768,16 +4348,17 @@
 
         // Verify direct routes are added when network agent is first registered in
         // ConnectivityService.
-        MockNetworkAgent networkAgent = new MockNetworkAgent(TRANSPORT_WIFI, lp);
+        TestNetworkAgentWrapper networkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
         networkAgent.connect(true);
-        networkCallback.expectCallback(CallbackState.AVAILABLE, networkAgent);
-        networkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, networkAgent);
-        CallbackInfo cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        networkCallback.expectCallback(CallbackRecord.AVAILABLE, networkAgent);
+        networkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, networkAgent);
+        CallbackRecord.LinkPropertiesChanged cbi =
+                networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 networkAgent);
-        networkCallback.expectCallback(CallbackState.BLOCKED_STATUS, networkAgent);
+        networkCallback.expectCallback(CallbackRecord.BLOCKED_STATUS, networkAgent);
         networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent);
         networkCallback.assertNoCallback();
-        checkDirectlyConnectedRoutes(cbi.arg, Arrays.asList(myIpv4Address),
+        checkDirectlyConnectedRoutes(cbi.getLp(), Arrays.asList(myIpv4Address),
                 Arrays.asList(myIpv4DefaultRoute));
         checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()),
                 Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute));
@@ -4789,9 +4370,9 @@
         newLp.addLinkAddress(myIpv6Address1);
         newLp.addLinkAddress(myIpv6Address2);
         networkAgent.sendLinkProperties(newLp);
-        cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, networkAgent);
+        cbi = networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, networkAgent);
         networkCallback.assertNoCallback();
-        checkDirectlyConnectedRoutes(cbi.arg,
+        checkDirectlyConnectedRoutes(cbi.getLp(),
                 Arrays.asList(myIpv4Address, myIpv6Address1, myIpv6Address2),
                 Arrays.asList(myIpv4DefaultRoute));
         mCm.unregisterNetworkCallback(networkCallback);
@@ -4799,8 +4380,8 @@
 
     @Test
     public void testStatsIfacesChanged() throws Exception {
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
 
         Network[] onlyCell = new Network[] {mCellNetworkAgent.getNetwork()};
         Network[] onlyWifi = new Network[] {mWiFiNetworkAgent.getNetwork()};
@@ -4815,11 +4396,8 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         waitForIdle();
         verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(
-                        eq(onlyCell),
-                        eq(new VpnInfo[0]),
-                        any(NetworkState[].class),
-                        eq(MOBILE_IFNAME));
+                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
+                        eq(new VpnInfo[0]));
         reset(mStatsService);
 
         // Default network switch should update ifaces.
@@ -4828,65 +4406,47 @@
         waitForIdle();
         assertEquals(wifiLp, mService.getActiveLinkProperties());
         verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(
-                        eq(onlyWifi),
-                        eq(new VpnInfo[0]),
-                        any(NetworkState[].class),
-                        eq(WIFI_IFNAME));
+                .forceUpdateIfaces(eq(onlyWifi), any(NetworkState[].class), eq(WIFI_IFNAME),
+                        eq(new VpnInfo[0]));
         reset(mStatsService);
 
         // Disconnect should update ifaces.
         mWiFiNetworkAgent.disconnect();
         waitForIdle();
         verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(
-                        eq(onlyCell),
-                        eq(new VpnInfo[0]),
-                        any(NetworkState[].class),
-                        eq(MOBILE_IFNAME));
+                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class),
+                        eq(MOBILE_IFNAME), eq(new VpnInfo[0]));
         reset(mStatsService);
 
         // Metered change should update ifaces
         mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
         waitForIdle();
         verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(
-                        eq(onlyCell),
-                        eq(new VpnInfo[0]),
-                        any(NetworkState[].class),
-                        eq(MOBILE_IFNAME));
+                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
+                        eq(new VpnInfo[0]));
         reset(mStatsService);
 
         mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
         waitForIdle();
         verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(
-                        eq(onlyCell),
-                        eq(new VpnInfo[0]),
-                        any(NetworkState[].class),
-                        eq(MOBILE_IFNAME));
+                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
+                        eq(new VpnInfo[0]));
         reset(mStatsService);
 
         // Captive portal change shouldn't update ifaces
         mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
         waitForIdle();
         verify(mStatsService, never())
-                .forceUpdateIfaces(
-                        eq(onlyCell),
-                        eq(new VpnInfo[0]),
-                        any(NetworkState[].class),
-                        eq(MOBILE_IFNAME));
+                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
+                        eq(new VpnInfo[0]));
         reset(mStatsService);
 
         // Roaming change should update ifaces
         mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
         waitForIdle();
         verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(
-                        eq(onlyCell),
-                        eq(new VpnInfo[0]),
-                        any(NetworkState[].class),
-                        eq(MOBILE_IFNAME));
+                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
+                        eq(new VpnInfo[0]));
         reset(mStatsService);
     }
 
@@ -4897,7 +4457,7 @@
         // Clear any interactions that occur as a result of CS starting up.
         reset(mMockDnsResolver);
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         waitForIdle();
         verify(mMockDnsResolver, never()).setResolverConfiguration(any());
         verifyNoMoreInteractions(mMockDnsResolver);
@@ -4980,7 +4540,7 @@
                 .addTransportType(TRANSPORT_CELLULAR).build();
         mCm.requestNetwork(cellRequest, cellNetworkCallback);
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         waitForIdle();
         // CS tells netd about the empty DNS config for this network.
         verify(mMockDnsResolver, never()).setResolverConfiguration(any());
@@ -5010,21 +4570,21 @@
         ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue();
         assertEquals(2, resolvrParams.tlsServers.length);
         assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
-                new String[]{"2001:db8::1", "192.0.2.1"}));
+                new String[] { "2001:db8::1", "192.0.2.1" }));
         // Opportunistic mode.
         assertEquals(2, resolvrParams.tlsServers.length);
         assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
-                new String[]{"2001:db8::1", "192.0.2.1"}));
+                new String[] { "2001:db8::1", "192.0.2.1" }));
         reset(mMockDnsResolver);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES,
+        cellNetworkCallback.expectCallback(CallbackRecord.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED,
                 mCellNetworkAgent);
-        CallbackInfo cbi = cellNetworkCallback.expectCallback(
-                CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.BLOCKED_STATUS, mCellNetworkAgent);
+        CallbackRecord.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback(
+                CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.BLOCKED_STATUS, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+        assertFalse(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
 
         setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
         verify(mMockDnsResolver, times(1)).setResolverConfiguration(
@@ -5032,7 +4592,7 @@
         resolvrParams = mResolverParamsParcelCaptor.getValue();
         assertEquals(2, resolvrParams.servers.length);
         assertTrue(ArrayUtils.containsAll(resolvrParams.servers,
-                new String[]{"2001:db8::1", "192.0.2.1"}));
+                new String[] { "2001:db8::1", "192.0.2.1" }));
         reset(mMockDnsResolver);
         cellNetworkCallback.assertNoCallback();
 
@@ -5042,21 +4602,21 @@
         resolvrParams = mResolverParamsParcelCaptor.getValue();
         assertEquals(2, resolvrParams.servers.length);
         assertTrue(ArrayUtils.containsAll(resolvrParams.servers,
-                new String[]{"2001:db8::1", "192.0.2.1"}));
+                new String[] { "2001:db8::1", "192.0.2.1" }));
         assertEquals(2, resolvrParams.tlsServers.length);
         assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
-                new String[]{"2001:db8::1", "192.0.2.1"}));
+                new String[] { "2001:db8::1", "192.0.2.1" }));
         reset(mMockDnsResolver);
         cellNetworkCallback.assertNoCallback();
 
         setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com");
         // Can't test dns configuration for strict mode without properly mocking
         // out the DNS lookups, but can test that LinkProperties is updated.
-        cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertEquals("strict.example.com", ((LinkProperties)cbi.arg).getPrivateDnsServerName());
+        assertTrue(cbi.getLp().isPrivateDnsActive());
+        assertEquals("strict.example.com", cbi.getLp().getPrivateDnsServerName());
     }
 
     @Test
@@ -5069,23 +4629,23 @@
                 .addTransportType(TRANSPORT_CELLULAR).build();
         mCm.requestNetwork(cellRequest, cellNetworkCallback);
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         waitForIdle();
         LinkProperties lp = new LinkProperties();
         mCellNetworkAgent.sendLinkProperties(lp);
         mCellNetworkAgent.connect(false);
         waitForIdle();
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES,
+        cellNetworkCallback.expectCallback(CallbackRecord.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED,
                 mCellNetworkAgent);
-        CallbackInfo cbi = cellNetworkCallback.expectCallback(
-                CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.BLOCKED_STATUS, mCellNetworkAgent);
+        CallbackRecord.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback(
+                CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.BLOCKED_STATUS, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+        assertFalse(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
         Set<InetAddress> dnsServers = new HashSet<>();
-        checkDnsServers(cbi.arg, dnsServers);
+        checkDnsServers(cbi.getLp(), dnsServers);
 
         // Send a validation event for a server that is not part of the current
         // resolver config. The validation event should be ignored.
@@ -5097,13 +4657,13 @@
         LinkProperties lp2 = new LinkProperties(lp);
         lp2.addDnsServer(InetAddress.getByName("145.100.185.16"));
         mCellNetworkAgent.sendLinkProperties(lp2);
-        cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+        assertFalse(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
         dnsServers.add(InetAddress.getByName("145.100.185.16"));
-        checkDnsServers(cbi.arg, dnsServers);
+        checkDnsServers(cbi.getLp(), dnsServers);
 
         // Send a validation event containing a hostname that is not part of
         // the current resolver config. The validation event should be ignored.
@@ -5121,39 +4681,39 @@
         // private dns fields should be sent.
         mService.mNetdEventCallback.onPrivateDnsValidationEvent(
                 mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", true);
-        cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
-        checkDnsServers(cbi.arg, dnsServers);
+        assertTrue(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
+        checkDnsServers(cbi.getLp(), dnsServers);
 
         // The private dns fields in LinkProperties should be preserved when
         // the network agent sends unrelated changes.
         LinkProperties lp3 = new LinkProperties(lp2);
         lp3.setMtu(1300);
         mCellNetworkAgent.sendLinkProperties(lp3);
-        cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
-        checkDnsServers(cbi.arg, dnsServers);
-        assertEquals(1300, ((LinkProperties)cbi.arg).getMtu());
+        assertTrue(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
+        checkDnsServers(cbi.getLp(), dnsServers);
+        assertEquals(1300, cbi.getLp().getMtu());
 
         // Removing the only validated server should affect the private dns
         // fields in LinkProperties.
         LinkProperties lp4 = new LinkProperties(lp3);
         lp4.removeDnsServer(InetAddress.getByName("145.100.185.16"));
         mCellNetworkAgent.sendLinkProperties(lp4);
-        cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+        assertFalse(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
         dnsServers.remove(InetAddress.getByName("145.100.185.16"));
-        checkDnsServers(cbi.arg, dnsServers);
-        assertEquals(1300, ((LinkProperties)cbi.arg).getMtu());
+        checkDnsServers(cbi.getLp(), dnsServers);
+        assertEquals(1300, cbi.getLp().getMtu());
     }
 
     private void checkDirectlyConnectedRoutes(Object callbackObj,
@@ -5180,31 +4740,8 @@
         assertTrue(lp.getDnsServers().containsAll(dnsServers));
     }
 
-    private static <T> void assertEmpty(T[] ts) {
-        int length = ts.length;
-        assertEquals("expected empty array, but length was " + length, 0, length);
-    }
-
-    private static <T> void assertLength(int expected, T[] got) {
-        int length = got.length;
-        assertEquals(String.format("expected array of length %s, but length was %s for %s",
-                expected, length, Arrays.toString(got)), expected, length);
-    }
-
-    private static <T> void assertException(Runnable block, Class<T> expected) {
-        try {
-            block.run();
-            fail("Expected exception of type " + expected);
-        } catch (Exception got) {
-            if (!got.getClass().equals(expected)) {
-                fail("Expected exception of type " + expected + " but got " + got);
-            }
-            return;
-        }
-    }
-
     @Test
-    public void testVpnNetworkActive() {
+    public void testVpnNetworkActive() throws Exception {
         final int uid = Process.myUid();
 
         final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
@@ -5227,7 +4764,7 @@
         mCm.registerDefaultNetworkCallback(defaultCallback);
         defaultCallback.assertNoCallback();
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
 
         genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
@@ -5237,14 +4774,16 @@
         vpnNetworkCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        final TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
         mMockVpn.setUids(ranges);
         // VPN networks do not satisfy the default request and are automatically validated
         // by NetworkMonitor
-        assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities));
+        assertFalse(NetworkMonitorUtils.isValidationRequired(
+                vpnNetworkAgent.getNetworkCapabilities()));
         vpnNetworkAgent.setNetworkValid();
 
         vpnNetworkAgent.connect(false);
@@ -5258,19 +4797,19 @@
         defaultCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        genericNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
         genericNotVpnNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectCapabilitiesLike(nc -> null == nc.getUids(), vpnNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, nc -> null == nc.getUids());
+        defaultCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         ranges.clear();
         vpnNetworkAgent.setUids(ranges);
 
-        genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        vpnNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
 
         // TODO : The default network callback should actually get a LOST call here (also see the
         // comment below for AVAILABLE). This is because ConnectivityService does not look at UID
@@ -5278,7 +4817,7 @@
         // can't currently update their UIDs without disconnecting, so this does not matter too
         // much, but that is the reason the test here has to check for an update to the
         // capabilities instead of the expected LOST then AVAILABLE.
-        defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
 
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setUids(ranges);
@@ -5290,23 +4829,23 @@
         vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
         // TODO : Here like above, AVAILABLE would be correct, but because this can't actually
         // happen outside of the test, ConnectivityService does not rematch callbacks.
-        defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
 
         mWiFiNetworkAgent.disconnect();
 
-        genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        genericNotVpnNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        genericNotVpnNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        wifiNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         vpnNetworkCallback.assertNoCallback();
         defaultCallback.assertNoCallback();
 
         vpnNetworkAgent.disconnect();
 
-        genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        vpnNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         assertEquals(null, mCm.getActiveNetwork());
 
         mCm.unregisterNetworkCallback(genericNetworkCallback);
@@ -5316,19 +4855,20 @@
     }
 
     @Test
-    public void testVpnWithoutInternet() {
+    public void testVpnWithoutInternet() throws Exception {
         final int uid = Process.myUid();
 
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
 
         defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
@@ -5346,19 +4886,20 @@
     }
 
     @Test
-    public void testVpnWithInternet() {
+    public void testVpnWithInternet() throws Exception {
         final int uid = Process.myUid();
 
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
 
         defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
@@ -5370,7 +4911,7 @@
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         vpnNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
 
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -5382,14 +4923,15 @@
         mCm.registerDefaultNetworkCallback(callback);
 
         // Bring up Ethernet.
-        mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
         mEthernetNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
         callback.assertNoCallback();
 
         // Bring up a VPN that has the INTERNET capability, initially unvalidated.
         final int uid = Process.myUid();
-        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        final TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
@@ -5400,7 +4942,7 @@
         // Even though the VPN is unvalidated, it becomes the default network for our app.
         callback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
         // TODO: this looks like a spurious callback.
-        callback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        callback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
         callback.assertNoCallback();
 
         assertTrue(vpnNetworkAgent.getScore() > mEthernetNetworkAgent.getScore());
@@ -5411,9 +4953,10 @@
         assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED));
         assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET));
 
-        assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities));
+        assertFalse(NetworkMonitorUtils.isValidationRequired(
+                vpnNetworkAgent.getNetworkCapabilities()));
         assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired(
-                vpnNetworkAgent.mNetworkCapabilities));
+                vpnNetworkAgent.getNetworkCapabilities()));
 
         // Pretend that the VPN network validates.
         vpnNetworkAgent.setNetworkValid();
@@ -5424,12 +4967,12 @@
         callback.assertNoCallback();
 
         vpnNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent);
     }
 
     @Test
-    public void testVpnSetUnderlyingNetworks() {
+    public void testVpnSetUnderlyingNetworks() throws Exception {
         final int uid = Process.myUid();
 
         final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
@@ -5441,7 +4984,8 @@
         mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
         vpnNetworkCallback.assertNoCallback();
 
-        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        final TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
@@ -5458,76 +5002,76 @@
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Connect cell and use it as an underlying network.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
 
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         mWiFiNetworkAgent.connect(true);
 
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Don't disconnect, but note the VPN is not using wifi any more.
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Use Wifi but not cell. Note the VPN is now unmetered.
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mWiFiNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Use both again.
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect cell. Receive update without even removing the dead network from the
         // underlying networks – it's dead anyway. Not metered any more.
         mCellNetworkAgent.disconnect();
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect wifi too. No underlying networks means this is now metered.
         mWiFiNetworkAgent.disconnect();
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         mMockVpn.disconnect();
     }
 
     @Test
-    public void testNullUnderlyingNetworks() {
+    public void testNullUnderlyingNetworks() throws Exception {
         final int uid = Process.myUid();
 
         final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
@@ -5539,7 +5083,8 @@
         mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
         vpnNetworkCallback.assertNoCallback();
 
-        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        final TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
@@ -5557,23 +5102,23 @@
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Connect to Cell; Cell is the default network.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Connect to WiFi; WiFi is the new default.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         mWiFiNetworkAgent.connect(true);
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect Cell. The default network did not change, so there shouldn't be any changes in
         // the capabilities.
@@ -5582,19 +5127,19 @@
         // Disconnect wifi too. Now we have no default network.
         mWiFiNetworkAgent.disconnect();
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         mMockVpn.disconnect();
     }
 
     @Test
-    public void testIsActiveNetworkMeteredOverWifi() {
+    public void testIsActiveNetworkMeteredOverWifi() throws Exception {
         // Returns true by default when no network is available.
         assertTrue(mCm.isActiveNetworkMetered());
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         mWiFiNetworkAgent.connect(true);
         waitForIdle();
@@ -5603,10 +5148,10 @@
     }
 
     @Test
-    public void testIsActiveNetworkMeteredOverCell() {
+    public void testIsActiveNetworkMeteredOverCell() throws Exception {
         // Returns true by default when no network is available.
         assertTrue(mCm.isActiveNetworkMetered());
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
         mCellNetworkAgent.connect(true);
         waitForIdle();
@@ -5615,17 +5160,18 @@
     }
 
     @Test
-    public void testIsActiveNetworkMeteredOverVpnTrackingPlatformDefault() {
+    public void testIsActiveNetworkMeteredOverVpnTrackingPlatformDefault() throws Exception {
         // Returns true by default when no network is available.
         assertTrue(mCm.isActiveNetworkMetered());
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
         mCellNetworkAgent.connect(true);
         waitForIdle();
         assertTrue(mCm.isActiveNetworkMetered());
 
         // Connect VPN network. By default it is using current default network (Cell).
-        MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         final int uid = Process.myUid();
         ranges.add(new UidRange(uid, uid));
@@ -5641,7 +5187,7 @@
         assertTrue(mCm.isActiveNetworkMetered());
 
         // Connect WiFi.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         mWiFiNetworkAgent.connect(true);
         waitForIdle();
@@ -5669,23 +5215,24 @@
     }
 
    @Test
-   public void testIsActiveNetworkMeteredOverVpnSpecifyingUnderlyingNetworks() {
+   public void testIsActiveNetworkMeteredOverVpnSpecifyingUnderlyingNetworks() throws Exception {
         // Returns true by default when no network is available.
         assertTrue(mCm.isActiveNetworkMetered());
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
         mCellNetworkAgent.connect(true);
         waitForIdle();
         assertTrue(mCm.isActiveNetworkMetered());
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         mWiFiNetworkAgent.connect(true);
         waitForIdle();
         assertFalse(mCm.isActiveNetworkMetered());
 
         // Connect VPN network.
-        MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         final int uid = Process.myUid();
         ranges.add(new UidRange(uid, uid));
@@ -5740,17 +5287,18 @@
     }
 
     @Test
-    public void testIsActiveNetworkMeteredOverAlwaysMeteredVpn() {
+    public void testIsActiveNetworkMeteredOverAlwaysMeteredVpn() throws Exception {
         // Returns true by default when no network is available.
         assertTrue(mCm.isActiveNetworkMetered());
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         mWiFiNetworkAgent.connect(true);
         waitForIdle();
         assertFalse(mCm.isActiveNetworkMetered());
 
         // Connect VPN network.
-        MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         final int uid = Process.myUid();
         ranges.add(new UidRange(uid, uid));
@@ -5787,28 +5335,28 @@
     }
 
     @Test
-    public void testNetworkBlockedStatus() {
+    public void testNetworkBlockedStatus() throws Exception {
         final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
         final NetworkRequest cellRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR)
                 .build();
         mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
 
-        mService.setUidRulesChanged(RULE_REJECT_ALL);
+        setUidRulesChanged(RULE_REJECT_ALL);
         cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
 
         // ConnectivityService should cache it not to invoke the callback again.
-        mService.setUidRulesChanged(RULE_REJECT_METERED);
+        setUidRulesChanged(RULE_REJECT_METERED);
         cellNetworkCallback.assertNoCallback();
 
-        mService.setUidRulesChanged(RULE_NONE);
+        setUidRulesChanged(RULE_NONE);
         cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
 
-        mService.setUidRulesChanged(RULE_REJECT_METERED);
+        setUidRulesChanged(RULE_REJECT_METERED);
         cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
 
         // Restrict the network based on UID rule and NOT_METERED capability change.
@@ -5819,18 +5367,18 @@
         cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
                 mCellNetworkAgent);
         cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
-        mService.setUidRulesChanged(RULE_ALLOW_METERED);
+        setUidRulesChanged(RULE_ALLOW_METERED);
         cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
 
-        mService.setUidRulesChanged(RULE_NONE);
+        setUidRulesChanged(RULE_NONE);
         cellNetworkCallback.assertNoCallback();
 
         // Restrict the network based on BackgroundRestricted.
-        mService.setRestrictBackgroundChanged(true);
+        setRestrictBackgroundChanged(true);
         cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
-        mService.setRestrictBackgroundChanged(true);
+        setRestrictBackgroundChanged(true);
         cellNetworkCallback.assertNoCallback();
-        mService.setRestrictBackgroundChanged(false);
+        setRestrictBackgroundChanged(false);
         cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
@@ -5838,30 +5386,30 @@
     }
 
     @Test
-    public void testNetworkBlockedStatusBeforeAndAfterConnect() {
+    public void testNetworkBlockedStatusBeforeAndAfterConnect() throws Exception {
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
         // No Networkcallbacks invoked before any network is active.
-        mService.setUidRulesChanged(RULE_REJECT_ALL);
-        mService.setUidRulesChanged(RULE_NONE);
-        mService.setUidRulesChanged(RULE_REJECT_METERED);
+        setUidRulesChanged(RULE_REJECT_ALL);
+        setUidRulesChanged(RULE_NONE);
+        setUidRulesChanged(RULE_REJECT_METERED);
         defaultCallback.assertNoCallback();
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent);
 
         // Allow to use the network after switching to NOT_METERED network.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         mWiFiNetworkAgent.connect(true);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
 
         // Switch to METERED network. Restrict the use of the network.
         mWiFiNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksValidatedAndBlocked(mCellNetworkAgent);
 
         // Network becomes NOT_METERED.
@@ -5870,12 +5418,12 @@
         defaultCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
 
         // Verify there's no Networkcallbacks invoked after data saver on/off.
-        mService.setRestrictBackgroundChanged(true);
-        mService.setRestrictBackgroundChanged(false);
+        setRestrictBackgroundChanged(true);
+        setRestrictBackgroundChanged(false);
         defaultCallback.assertNoCallback();
 
         mCellNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         defaultCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -5907,7 +5455,7 @@
     }
 
     @Test
-    public void testStackedLinkProperties() throws UnknownHostException, RemoteException {
+    public void testStackedLinkProperties() throws Exception {
         final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24");
         final LinkAddress myIpv6 = new LinkAddress("2001:db8:1::1/64");
         final String kNat64PrefixString = "2001:db8:64:64:64:64::";
@@ -5921,7 +5469,7 @@
         mCm.registerNetworkCallback(networkRequest, networkCallback);
 
         // Prepare ipv6 only link properties.
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         final int cellNetId = mCellNetworkAgent.getNetwork().netId;
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -5951,7 +5499,7 @@
         // the NAT64 prefix was removed because one was never discovered.
         cellLp.addLinkAddress(myIpv4);
         mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
 
@@ -5964,23 +5512,23 @@
         cellLp.removeLinkAddress(myIpv4);
         cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
         mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
 
         // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started.
-        Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent);
+        Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent);
         assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix());
         mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
                 kNat64PrefixString, 96);
-        LinkProperties lpBeforeClat = (LinkProperties) networkCallback.expectCallback(
-                CallbackState.LINK_PROPERTIES, mCellNetworkAgent).arg;
+        LinkProperties lpBeforeClat = networkCallback.expectCallback(
+                CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp();
         assertEquals(0, lpBeforeClat.getStackedLinks().size());
         assertEquals(kNat64Prefix, lpBeforeClat.getNat64Prefix());
         verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
 
         // Clat iface comes up. Expect stacked link to be added.
         clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         List<LinkProperties> stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork())
                 .getStackedLinks();
         assertEquals(makeClatLinkProperties(myIpv4), stackedLps.get(0));
@@ -5988,7 +5536,7 @@
         // Change trivial linkproperties and see if stacked link is preserved.
         cellLp.addDnsServer(InetAddress.getByName("8.8.8.8"));
         mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
 
         List<LinkProperties> stackedLpsAfterChange =
                 mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getStackedLinks();
@@ -6006,12 +5554,12 @@
         cellLp.addLinkAddress(myIpv4);
         cellLp.addRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
         mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
         verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
 
         // As soon as stop is called, the linkproperties lose the stacked interface.
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork());
         LinkProperties expected = new LinkProperties(cellLp);
         expected.setNat64Prefix(kNat64Prefix);
@@ -6030,54 +5578,52 @@
         // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone.
         mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
                 kNat64PrefixString, 96);
-        networkCallback.expectLinkPropertiesLike((lp) -> lp.getNat64Prefix() == null,
-                mCellNetworkAgent);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getNat64Prefix() == null);
 
         // Remove IPv4 address and expect prefix discovery and clatd to be started again.
         cellLp.removeLinkAddress(myIpv4);
         cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
         cellLp.removeDnsServer(InetAddress.getByName("8.8.8.8"));
         mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
         mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
                 kNat64PrefixString, 96);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
 
 
         // Clat iface comes up. Expect stacked link to be added.
         clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
-        networkCallback.expectLinkPropertiesLike(
-                (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null,
-                mCellNetworkAgent);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null);
 
         // NAT64 prefix is removed. Expect that clat is stopped.
         mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
                 kNat64PrefixString, 96);
-        networkCallback.expectLinkPropertiesLike(
-                (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null,
-                mCellNetworkAgent);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null);
         verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
-        networkCallback.expectLinkPropertiesLike((lp) -> lp.getStackedLinks().size() == 0,
-                mCellNetworkAgent);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getStackedLinks().size() == 0);
 
         // Clean up.
         mCellNetworkAgent.disconnect();
-        networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         networkCallback.assertNoCallback();
         mCm.unregisterNetworkCallback(networkCallback);
     }
 
     @Test
-    public void testDataActivityTracking() throws RemoteException {
+    public void testDataActivityTracking() throws Exception {
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         final NetworkRequest networkRequest = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .build();
         mCm.registerNetworkCallback(networkRequest, networkCallback);
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
         mCellNetworkAgent.sendLinkProperties(cellLp);
@@ -6087,7 +5633,7 @@
         verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(),
                 eq(ConnectivityManager.TYPE_MOBILE));
 
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
         mWiFiNetworkAgent.sendLinkProperties(wifiLp);
@@ -6096,7 +5642,7 @@
         reset(mNetworkManagementService);
         mWiFiNetworkAgent.connect(true);
         networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        networkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         verify(mNetworkManagementService, times(1)).addIdleTimer(eq(WIFI_IFNAME), anyInt(),
                 eq(ConnectivityManager.TYPE_WIFI));
@@ -6105,26 +5651,26 @@
         // Disconnect wifi and switch back to cell
         reset(mNetworkManagementService);
         mWiFiNetworkAgent.disconnect();
-        networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         assertNoCallbacks(networkCallback);
         verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME));
         verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(),
                 eq(ConnectivityManager.TYPE_MOBILE));
 
         // reconnect wifi
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         wifiLp.setInterfaceName(WIFI_IFNAME);
         mWiFiNetworkAgent.sendLinkProperties(wifiLp);
         mWiFiNetworkAgent.connect(true);
         networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        networkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
 
         // Disconnect cell
         reset(mNetworkManagementService);
         reset(mMockNetd);
         mCellNetworkAgent.disconnect();
-        networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         // LOST callback is triggered earlier than removing idle timer. Broadcast should also be
         // sent as network being switched. Ensure rule removal for cell will not be triggered
         // unexpectedly before network being removed.
@@ -6145,49 +5691,59 @@
         mCm.unregisterNetworkCallback(networkCallback);
     }
 
-    private void verifyTcpBufferSizeChange(String tcpBufferSizes) {
+    private void verifyTcpBufferSizeChange(String tcpBufferSizes) throws Exception {
         String[] values = tcpBufferSizes.split(",");
         String rmemValues = String.join(" ", values[0], values[1], values[2]);
         String wmemValues = String.join(" ", values[3], values[4], values[5]);
-        waitForIdle();
-        try {
-            verify(mMockNetd, atLeastOnce()).setTcpRWmemorySize(rmemValues, wmemValues);
-        } catch (RemoteException e) {
-            fail("mMockNetd should never throw RemoteException");
-        }
+        verify(mMockNetd, atLeastOnce()).setTcpRWmemorySize(rmemValues, wmemValues);
         reset(mMockNetd);
     }
 
     @Test
-    public void testTcpBufferReset() {
+    @FlakyTest(bugId = 140305678)
+    public void testTcpBufferReset() throws Exception {
         final String testTcpBufferSizes = "1,2,3,4,5,6";
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(networkRequest, networkCallback);
 
-        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         reset(mMockNetd);
         // Switching default network updates TCP buffer sizes.
         mCellNetworkAgent.connect(false);
+        networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
 
         // Change link Properties should have updated tcp buffer size.
         LinkProperties lp = new LinkProperties();
         lp.setTcpBufferSizes(testTcpBufferSizes);
         mCellNetworkAgent.sendLinkProperties(lp);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verifyTcpBufferSizeChange(testTcpBufferSizes);
+
+        // Clean up.
+        mCellNetworkAgent.disconnect();
+        networkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+        networkCallback.assertNoCallback();
+        mCm.unregisterNetworkCallback(networkCallback);
     }
 
     @Test
-    public void testGetGlobalProxyForNetwork() {
+    public void testGetGlobalProxyForNetwork() throws Exception {
         final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         final Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
         when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo);
         assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork));
     }
 
     @Test
-    public void testGetProxyForActiveNetwork() {
+    public void testGetProxyForActiveNetwork() throws Exception {
         final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         waitForIdle();
         assertNull(mService.getProxyForNetwork(null));
@@ -6202,18 +5758,19 @@
     }
 
     @Test
-    public void testGetProxyForVPN() {
+    public void testGetProxyForVPN() throws Exception {
         final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
 
         // Set up a WiFi network with no proxy
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         waitForIdle();
         assertNull(mService.getProxyForNetwork(null));
 
         // Set up a VPN network with a proxy
         final int uid = Process.myUid();
-        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        final TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
         final ArraySet<UidRange> ranges = new ArraySet<>();
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setUids(ranges);
@@ -6262,7 +5819,7 @@
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
-        final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+        final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
 
         // Connected VPN should have interface rules set up. There are two expected invocations,
         // one during VPN uid update, one during VPN LinkProperties update
@@ -6288,7 +5845,8 @@
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
-        final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange);
+        final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(
+                lp, Process.SYSTEM_UID, vpnRange);
 
         // Legacy VPN should not have interface rules set up
         verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
@@ -6303,7 +5861,8 @@
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
-        final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange);
+        final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(
+                lp, Process.SYSTEM_UID, vpnRange);
 
         // IPv6 unreachable route should not be misinterpreted as a default route
         verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
@@ -6316,7 +5875,7 @@
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
-        final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+        final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
 
         // Connected VPN should have interface rules set up. There are two expected invocations,
         // one during VPN uid update, one during VPN LinkProperties update
@@ -6365,7 +5924,7 @@
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final UidRange vpnRange = UidRange.createForUser(VPN_USER);
-        final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID,
+        final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID,
                 Collections.singleton(vpnRange));
 
         reset(mMockNetd);
@@ -6387,9 +5946,10 @@
     }
 
 
-    private MockNetworkAgent establishVpn(LinkProperties lp, int establishingUid,
-            Set<UidRange> vpnRange) {
-        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN, lp);
+    private TestNetworkAgentWrapper establishVpn(LinkProperties lp, int establishingUid,
+            Set<UidRange> vpnRange) throws Exception {
+        final TestNetworkAgentWrapper
+                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp);
         vpnNetworkAgent.getNetworkCapabilities().setEstablishingVpnAppUid(establishingUid);
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
         mMockVpn.connect();
@@ -6399,14 +5959,6 @@
         return vpnNetworkAgent;
     }
 
-    private void assertContainsExactly(int[] actual, int... expected) {
-        int[] sortedActual = Arrays.copyOf(actual, actual.length);
-        int[] sortedExpected = Arrays.copyOf(expected, expected.length);
-        Arrays.sort(sortedActual);
-        Arrays.sort(sortedExpected);
-        assertArrayEquals(sortedExpected, sortedActual);
-    }
-
     private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) {
         final PackageInfo packageInfo = new PackageInfo();
         packageInfo.requestedPermissions = new String[0];
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index e4117b8..aef9386 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -19,8 +19,9 @@
 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
 
+import static com.android.testutils.MiscAssertsKt.assertStringContains;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -111,15 +112,15 @@
         String[] events2 = remove(listNetdEvent(), baseline);
         int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
         assertEquals(expectedLength2, events2.length);
-        assertContains(events2[0], "WakeupStats");
-        assertContains(events2[0], "wlan0");
-        assertContains(events2[0], "0x800");
-        assertContains(events2[0], "0x86dd");
+        assertStringContains(events2[0], "WakeupStats");
+        assertStringContains(events2[0], "wlan0");
+        assertStringContains(events2[0], "0x800");
+        assertStringContains(events2[0], "0x86dd");
         for (int i = 0; i < uids.length; i++) {
             String got = events2[i+1];
-            assertContains(got, "WakeupEvent");
-            assertContains(got, "wlan0");
-            assertContains(got, "uid: " + uids[i]);
+            assertStringContains(got, "WakeupEvent");
+            assertStringContains(got, "wlan0");
+            assertStringContains(got, "uid: " + uids[i]);
         }
 
         int uid = 20000;
@@ -131,13 +132,13 @@
         String[] events3 = remove(listNetdEvent(), baseline);
         int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
         assertEquals(expectedLength3, events3.length);
-        assertContains(events2[0], "WakeupStats");
-        assertContains(events2[0], "wlan0");
+        assertStringContains(events2[0], "WakeupStats");
+        assertStringContains(events2[0], "wlan0");
         for (int i = 1; i < expectedLength3; i++) {
             String got = events3[i];
-            assertContains(got, "WakeupEvent");
-            assertContains(got, "wlan0");
-            assertContains(got, "uid: " + uid);
+            assertStringContains(got, "WakeupEvent");
+            assertStringContains(got, "wlan0");
+            assertStringContains(got, "uid: " + uid);
         }
 
         uid = 45678;
@@ -145,9 +146,9 @@
 
         String[] events4 = remove(listNetdEvent(), baseline);
         String lastEvent = events4[events4.length - 1];
-        assertContains(lastEvent, "WakeupEvent");
-        assertContains(lastEvent, "wlan0");
-        assertContains(lastEvent, "uid: " + uid);
+        assertStringContains(lastEvent, "WakeupEvent");
+        assertStringContains(lastEvent, "wlan0");
+        assertStringContains(lastEvent, "uid: " + uid);
     }
 
     @Test
@@ -529,10 +530,6 @@
         return buffer.toString().split("\\n");
     }
 
-    static void assertContains(String got, String want) {
-        assertTrue(got + " did not contain \"" + want + "\"", got.contains(want));
-    }
-
     static <T> T[] remove(T[] array, T[] filtered) {
         List<T> c = Arrays.asList(filtered);
         int next = 0;
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index df1f57f..cd2bd26 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -65,6 +65,7 @@
 import android.os.UserManager;
 import android.util.SparseIntArray;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -96,6 +97,7 @@
     private static final int SYSTEM_UID1 = 1000;
     private static final int SYSTEM_UID2 = 1008;
     private static final int VPN_UID = 10002;
+    private static final String REAL_SYSTEM_PACKAGE_NAME = "android";
     private static final String MOCK_PACKAGE1 = "appName1";
     private static final String MOCK_PACKAGE2 = "appName2";
     private static final String SYSTEM_PACKAGE1 = "sysName1";
@@ -188,8 +190,10 @@
     private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
         final PackageInfo pkgInfo;
         if (hasSystemPermission) {
-            pkgInfo = packageInfoWithPermissions(new String[] {CHANGE_NETWORK_STATE, NETWORK_STACK},
-                    PARTITION_SYSTEM);
+            final String[] systemPermissions = new String[]{
+                    CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS
+            };
+            pkgInfo = packageInfoWithPermissions(systemPermissions, PARTITION_SYSTEM);
         } else {
             pkgInfo = packageInfoWithPermissions(new String[] {}, "");
         }
@@ -646,4 +650,16 @@
         mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
         mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
     }
+
+    @Test
+    public void testRealSystemPermission() throws Exception {
+        // Use the real context as this test must ensure the *real* system package holds the
+        // necessary permission.
+        final Context realContext = InstrumentationRegistry.getContext();
+        final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService);
+        final PackageManager manager = realContext.getPackageManager();
+        final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME,
+                GET_PERMISSIONS | MATCH_ANY_USER);
+        assertTrue(monitor.hasPermission(systemInfo, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index 6c42ac3..c030c3e 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -100,6 +100,8 @@
 import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
 
 import androidx.test.filters.SmallTest;
@@ -111,6 +113,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
 import com.android.server.connectivity.tethering.OffloadHardwareInterface;
+import com.android.server.connectivity.tethering.TetheringConfiguration;
 import com.android.server.connectivity.tethering.TetheringDependencies;
 import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
 
@@ -118,6 +121,7 @@
 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;
 
@@ -147,6 +151,7 @@
     @Mock private MockableSystemProperties mSystemProperties;
     @Mock private OffloadHardwareInterface mOffloadHardwareInterface;
     @Mock private Resources mResources;
+    @Mock private TelephonyManager mTelephonyManager;
     @Mock private UsbManager mUsbManager;
     @Mock private WifiManager mWifiManager;
     @Mock private CarrierConfigManager mCarrierConfigManager;
@@ -171,6 +176,7 @@
     private MockContentResolver mContentResolver;
     private BroadcastReceiver mBroadcastReceiver;
     private Tethering mTethering;
+    private PhoneStateListener mPhoneStateListener;
 
     private class MockContext extends BroadcastInterceptingContext {
         MockContext(Context base) {
@@ -193,6 +199,7 @@
         public Object getSystemService(String name) {
             if (Context.WIFI_SERVICE.equals(name)) return mWifiManager;
             if (Context.USB_SERVICE.equals(name)) return mUsbManager;
+            if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
             return super.getSystemService(name);
         }
     }
@@ -234,6 +241,17 @@
         }
     }
 
+    private class MockTetheringConfiguration extends TetheringConfiguration {
+        MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
+            super(ctx, log, id);
+        }
+
+        @Override
+        protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
+            return mResources;
+        }
+    }
+
     public class MockTetheringDependencies extends TetheringDependencies {
         StateMachine upstreamNetworkMonitorMasterSM;
         ArrayList<IpServer> ipv6CoordinatorNotifyList;
@@ -276,8 +294,9 @@
         }
 
         @Override
-        public int getDefaultDataSubscriptionId() {
-            return INVALID_SUBSCRIPTION_ID;
+        public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log,
+                int subId) {
+            return new MockTetheringConfiguration(ctx, log, subId);
         }
     }
 
@@ -372,6 +391,11 @@
         mTetheringDependencies.reset();
         mTethering = makeTethering();
         verify(mNMService).registerTetheringStatsProvider(any(), anyString());
+        final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
+                ArgumentCaptor.forClass(PhoneStateListener.class);
+        verify(mTelephonyManager).listen(phoneListenerCaptor.capture(),
+                eq(PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE));
+        mPhoneStateListener = phoneListenerCaptor.getValue();
     }
 
     private Tethering makeTethering() {
@@ -445,6 +469,8 @@
         if (emulateInterfaceStatusChanged) {
             assertEquals(1, mTetheringDependencies.isTetheringSupportedCalls);
             verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
+            verify(mWifiManager).updateInterfaceIpState(
+                    TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         }
         verifyNoMoreInteractions(mNMService);
         verifyNoMoreInteractions(mWifiManager);
@@ -510,6 +536,8 @@
         verify(mNMService, times(1)).startTethering(any(String[].class));
         verifyNoMoreInteractions(mNMService);
         verify(mWifiManager).updateInterfaceIpState(
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+        verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
         verifyNoMoreInteractions(mWifiManager);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
@@ -530,6 +558,8 @@
                 .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
         verify(mNMService, times(1)).stopTethering();
         verify(mNMService, times(1)).setIpForwardingEnabled(false);
+        verify(mWifiManager, times(3)).updateInterfaceIpState(
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         verifyNoMoreInteractions(mNMService);
         verifyNoMoreInteractions(mWifiManager);
         // Asking for the last error after the per-interface state machine
@@ -715,6 +745,8 @@
 
         assertEquals(1, mTetheringDependencies.isTetheringSupportedCalls);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
+        verify(mWifiManager).updateInterfaceIpState(
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         verifyNoMoreInteractions(mNMService);
         verifyNoMoreInteractions(mWifiManager);
     }
@@ -744,6 +776,8 @@
         verify(mNMService, times(1)).startTethering(any(String[].class));
         verifyNoMoreInteractions(mNMService);
         verify(mWifiManager).updateInterfaceIpState(
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+        verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
         verifyNoMoreInteractions(mWifiManager);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER);
@@ -781,6 +815,8 @@
                 .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
         verify(mNMService, times(1)).stopTethering();
         verify(mNMService, times(1)).setIpForwardingEnabled(false);
+        verify(mWifiManager, times(3)).updateInterfaceIpState(
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         verifyNoMoreInteractions(mNMService);
         verifyNoMoreInteractions(mWifiManager);
         // Asking for the last error after the per-interface state machine
@@ -818,6 +854,8 @@
         verify(mNetd, times(1)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName)));
         verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME);
         verify(mWifiManager).updateInterfaceIpState(
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+        verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
         // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
         assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls);
@@ -982,6 +1020,17 @@
         callback2.expectUpstreamChanged(upstreamState.network);
     }
 
+    @Test
+    public void testMultiSimAware() throws Exception {
+        final TetheringConfiguration initailConfig = mTethering.getTetheringConfiguration();
+        assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.subId);
+
+        final int fakeSubId = 1234;
+        mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
+        final TetheringConfiguration newConfig = mTethering.getTetheringConfiguration();
+        assertEquals(fakeSubId, newConfig.subId);
+    }
+
     // TODO: Test that a request for hotspot mode doesn't interfere with an
     // already operating tethering mode interface.
 }
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 2cae250..ce50bef 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -727,94 +727,4 @@
                 "::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6",
                 "fe00::/8", "2605:ef80:e:af1d::/64");
     }
-
-    @Test
-    public void testProvidesRoutesToMostDestinations() {
-        final LinkProperties lp = new LinkProperties();
-
-        // Default route provides routes to all IPv4 destinations.
-        lp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0")));
-        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
-        // Empty LP provides routes to no destination
-        lp.clear();
-        assertFalse(Vpn.providesRoutesToMostDestinations(lp));
-
-        // All IPv4 routes except for local networks. This is the case most relevant
-        // to this function. It provides routes to almost the entire space.
-        // (clone the stream so that we can reuse it later)
-        publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s))));
-        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
-        // Removing a 16-bit prefix, which is 65536 addresses. This is still enough to
-        // provide routes to "most" destinations.
-        lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16")));
-        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
-        // Remove the /2 route, which represent a quarter of the available routing space.
-        // This LP does not provides routes to "most" destinations any more.
-        lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2")));
-        assertFalse(Vpn.providesRoutesToMostDestinations(lp));
-
-        lp.clear();
-        publicIpV6Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s))));
-        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
-        lp.removeRoute(new RouteInfo(new IpPrefix("::/1")));
-        assertFalse(Vpn.providesRoutesToMostDestinations(lp));
-
-        // V6 does not provide sufficient coverage but v4 does
-        publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s))));
-        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
-        // V4 still does
-        lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16")));
-        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
-        // V4 does not any more
-        lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2")));
-        assertFalse(Vpn.providesRoutesToMostDestinations(lp));
-
-        // V4 does not, but V6 has sufficient coverage again
-        lp.addRoute(new RouteInfo(new IpPrefix("::/1")));
-        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
-        lp.clear();
-        // V4-unreachable route should not be treated as sufficient coverage
-        lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
-        assertFalse(Vpn.providesRoutesToMostDestinations(lp));
-
-        lp.clear();
-        // V6-unreachable route should not be treated as sufficient coverage
-        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
-        assertFalse(Vpn.providesRoutesToMostDestinations(lp));
-    }
-
-    @Test
-    public void testDoesNotLockUpWithTooManyRoutes() {
-        final LinkProperties lp = new LinkProperties();
-        final byte[] ad = new byte[4];
-        // Actually evaluating this many routes under 1500ms is impossible on
-        // current hardware and for some time, as the algorithm is O(n²).
-        // Make sure the system has a safeguard against this and does not
-        // lock up.
-        final int MAX_ROUTES = 4000;
-        final long MAX_ALLOWED_TIME_MS = 1500;
-        for (int i = 0; i < MAX_ROUTES; ++i) {
-            ad[0] = (byte)((i >> 24) & 0xFF);
-            ad[1] = (byte)((i >> 16) & 0xFF);
-            ad[2] = (byte)((i >> 8) & 0xFF);
-            ad[3] = (byte)(i & 0xFF);
-            try {
-                lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.getByAddress(ad), 32)));
-            } catch (UnknownHostException e) {
-                // UnknownHostException is only thrown for an address of illegal length,
-                // which can't happen in the case above.
-            }
-        }
-        final long start = SystemClock.currentThreadTimeMillis();
-        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-        final long end = SystemClock.currentThreadTimeMillis();
-        assertTrue(end - start < MAX_ALLOWED_TIME_MS);
-    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 2b2e8a7..5217e26 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -406,6 +406,13 @@
     }
 
     @Test
+    public void verifyPermissionWhenProvisioningNotStarted() {
+        assertTrue(mEnMgr.isCellularUpstreamPermitted());
+        setupForRequiredProvisioning();
+        assertFalse(mEnMgr.isCellularUpstreamPermitted());
+    }
+
+    @Test
     public void testRunTetherProvisioning() {
         setupForRequiredProvisioning();
         // 1. start ui provisioning, upstream is mobile
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index be54b1a..9931aec 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -25,6 +25,7 @@
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
 import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
+import static com.android.testutils.MiscAssertsKt.assertContainsAll;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -51,7 +52,6 @@
 import android.net.NetworkStats;
 import android.net.RouteInfo;
 import android.net.util.SharedLog;
-import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.Looper;
@@ -63,6 +63,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.testutils.HandlerUtilsKt;
 
 import org.junit.After;
 import org.junit.Before;
@@ -90,6 +91,7 @@
     private static final String IPV6_DISCARD_PREFIX = "100::/64";
     private static final String USB_PREFIX = "192.168.42.0/24";
     private static final String WIFI_PREFIX = "192.168.43.0/24";
+    private static final long WAIT_FOR_IDLE_TIMEOUT = 2 * 1000;
 
     @Mock private OffloadHardwareInterface mHardware;
     @Mock private ApplicationInfo mApplicationInfo;
@@ -131,9 +133,7 @@
     }
 
     private void waitForIdle() {
-        ConditionVariable cv = new ConditionVariable();
-        new Handler(Looper.getMainLooper()).post(() -> { cv.open(); });
-        cv.block();
+        HandlerUtilsKt.waitForIdle(new Handler(Looper.getMainLooper()), WAIT_FOR_IDLE_TIMEOUT);
     }
 
     private OffloadController makeOffloadController() throws Exception {
@@ -245,7 +245,7 @@
         inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
         ArrayList<String> localPrefixes = mStringArrayCaptor.getValue();
         assertEquals(4, localPrefixes.size());
-        assertArrayListContains(localPrefixes,
+        assertContainsAll(localPrefixes,
                 "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64");
         inOrder.verifyNoMoreInteractions();
 
@@ -361,7 +361,7 @@
         inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
         localPrefixes = mStringArrayCaptor.getValue();
         assertEquals(6, localPrefixes.size());
-        assertArrayListContains(localPrefixes,
+        assertContainsAll(localPrefixes,
                 "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64",
                 "2001:db8::6173:7369:676e:6564/128", "2001:db8::7261:6e64:6f6d/128");
         // The relevant parts of the LinkProperties have not changed, but at the
@@ -718,7 +718,7 @@
         verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
         ArrayList<String> localPrefixes = mStringArrayCaptor.getValue();
         assertEquals(4, localPrefixes.size());
-        assertArrayListContains(localPrefixes,
+        assertContainsAll(localPrefixes,
                 // TODO: The logic to find and exclude downstream IP prefixes
                 // is currently in Tethering's OffloadWrapper but must be moved
                 // into OffloadController proper. After this, also check for:
@@ -731,9 +731,4 @@
         verifyNoMoreInteractions(mHardware);
     }
 
-    private static void assertArrayListContains(ArrayList<String> list, String... elems) {
-        for (String element : elems) {
-            assertTrue(element + " not in list", list.contains(element));
-        }
-    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index 2140322..e282963 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.when;
 
 import android.content.ContentResolver;
@@ -141,7 +142,7 @@
 
     @Test
     public void testDunFromTelephonyManagerMeansDun() {
-        when(mTelephonyManager.getTetherApnRequired()).thenReturn(true);
+        when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(true);
 
         final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
         final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
@@ -165,7 +166,7 @@
 
     @Test
     public void testDunNotRequiredFromTelephonyManagerMeansNoDun() {
-        when(mTelephonyManager.getTetherApnRequired()).thenReturn(false);
+        when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false);
 
         final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
         final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
@@ -208,7 +209,7 @@
     @Test
     public void testNoDefinedUpstreamTypesAddsEthernet() {
         when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{});
-        when(mTelephonyManager.getTetherApnRequired()).thenReturn(false);
+        when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
@@ -231,7 +232,7 @@
     public void testDefinedUpstreamTypesSansEthernetAddsEthernet() {
         when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
                 new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
-        when(mTelephonyManager.getTetherApnRequired()).thenReturn(false);
+        when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
@@ -249,7 +250,7 @@
     public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() {
         when(mResources.getIntArray(config_tether_upstream_types))
                 .thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI});
-        when(mTelephonyManager.getTetherApnRequired()).thenReturn(false);
+        when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
diff --git a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
index 6e725dd..858358c 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
@@ -161,7 +161,7 @@
     }
 
     private void setHasCarrierPrivileges(boolean hasPrivileges) {
-        when(mTm.checkCarrierPrivilegesForPackage(TEST_PKG)).thenReturn(
+        when(mTm.checkCarrierPrivilegesForPackageAnyPhone(TEST_PKG)).thenReturn(
                 hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
                         : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
     }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java
new file mode 100644
index 0000000..28785f7
--- /dev/null
+++ b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkStats.ROAMING_ALL;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.ROAMING_YES;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.NetworkStats;
+
+import com.android.internal.net.VpnInfo;
+
+/** Superclass with utilities for NetworkStats(Service|Factory)Test */
+abstract class NetworkStatsBaseTest {
+    static final String TEST_IFACE = "test0";
+    static final String TEST_IFACE2 = "test1";
+    static final String TUN_IFACE = "test_nss_tun0";
+
+    static final int UID_RED = 1001;
+    static final int UID_BLUE = 1002;
+    static final int UID_GREEN = 1003;
+    static final int UID_VPN = 1004;
+
+    void assertValues(NetworkStats stats, String iface, int uid, long rxBytes,
+            long rxPackets, long txBytes, long txPackets) {
+        assertValues(
+                stats, iface, uid, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
+                rxBytes, rxPackets, txBytes, txPackets, 0);
+    }
+
+    void assertValues(NetworkStats stats, String iface, int uid, int set, int tag,
+            int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
+            long txBytes, long txPackets, long operations) {
+        final NetworkStats.Entry entry = new NetworkStats.Entry();
+        final int[] sets;
+        if (set == SET_ALL) {
+            sets = new int[] {SET_ALL, SET_DEFAULT, SET_FOREGROUND};
+        } else {
+            sets = new int[] {set};
+        }
+
+        final int[] roamings;
+        if (roaming == ROAMING_ALL) {
+            roamings = new int[] {ROAMING_ALL, ROAMING_YES, ROAMING_NO};
+        } else {
+            roamings = new int[] {roaming};
+        }
+
+        final int[] meterings;
+        if (metered == METERED_ALL) {
+            meterings = new int[] {METERED_ALL, METERED_YES, METERED_NO};
+        } else {
+            meterings = new int[] {metered};
+        }
+
+        final int[] defaultNetworks;
+        if (defaultNetwork == DEFAULT_NETWORK_ALL) {
+            defaultNetworks =
+                    new int[] {DEFAULT_NETWORK_ALL, DEFAULT_NETWORK_YES, DEFAULT_NETWORK_NO};
+        } else {
+            defaultNetworks = new int[] {defaultNetwork};
+        }
+
+        for (int s : sets) {
+            for (int r : roamings) {
+                for (int m : meterings) {
+                    for (int d : defaultNetworks) {
+                        final int i = stats.findIndex(iface, uid, s, tag, m, r, d);
+                        if (i != -1) {
+                            entry.add(stats.getValues(i, null));
+                        }
+                    }
+                }
+            }
+        }
+
+        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+        assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+        assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+        assertEquals("unexpected operations", operations, entry.operations);
+    }
+
+    VpnInfo createVpnInfo(String[] underlyingIfaces) {
+        VpnInfo info = new VpnInfo();
+        info.ownerUid = UID_VPN;
+        info.vpnIface = TUN_IFACE;
+        info.underlyingIfaces = underlyingIfaces;
+        return info;
+    }
+}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 9b4f49c..8f90f13 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -29,6 +29,7 @@
 
 import static com.android.server.net.NetworkStatsCollection.multiplySafe;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
@@ -43,7 +44,6 @@
 import android.os.UserHandle;
 import android.telephony.SubscriptionPlan;
 import android.telephony.TelephonyManager;
-import android.test.MoreAsserts;
 import android.text.format.DateUtils;
 import android.util.RecurrenceRule;
 
@@ -240,11 +240,11 @@
                 60 * MINUTE_IN_MILLIS, entry);
 
         // Verify the set of relevant UIDs for each access level.
-        MoreAsserts.assertEquals(new int[] { myUid },
+        assertArrayEquals(new int[] { myUid },
                 collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT));
-        MoreAsserts.assertEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
+        assertArrayEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
                 collection.getRelevantUids(NetworkStatsAccess.Level.USER));
-        MoreAsserts.assertEquals(
+        assertArrayEquals(
                 new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser },
                 collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE));
 
diff --git a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
index 95bc7d9..a21f509 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -16,8 +16,11 @@
 
 package com.android.server.net;
 
+import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_ALL;
 import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_ALL;
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
@@ -39,6 +42,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.tests.net.R;
+import com.android.internal.net.VpnInfo;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -54,12 +58,12 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 
-/**
- * Tests for {@link NetworkStatsFactory}.
- */
+/** Tests for {@link NetworkStatsFactory}. */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class NetworkStatsFactoryTest {
+public class NetworkStatsFactoryTest extends NetworkStatsBaseTest {
+    private static final String CLAT_PREFIX = "v4-";
+
     private File mTestProc;
     private NetworkStatsFactory mFactory;
 
@@ -75,6 +79,7 @@
         // related to networkStatsFactory is compiled to a minimal native library and loaded here.
         System.loadLibrary("networkstatsfactorytestjni");
         mFactory = new NetworkStatsFactory(mTestProc, false);
+        mFactory.updateVpnInfos(new VpnInfo[0]);
     }
 
     @After
@@ -99,6 +104,236 @@
     }
 
     @Test
+    public void vpnRewriteTrafficThroughItself() throws Exception {
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        mFactory.updateVpnInfos(vpnInfos);
+
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        //
+        // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+        // over VPN.
+        // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+        // over VPN.
+        //
+        // VPN UID rewrites packets read from TUN back to TUN, plus some of its own traffic
+
+        final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_rewrite_through_self);
+
+        assertValues(tunStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 2000L, 200L, 1000L, 100L, 0);
+        assertValues(tunStats, TUN_IFACE, UID_BLUE, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 1000L, 100L, 500L, 50L, 0);
+        assertValues(tunStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 0L, 0L, 1600L, 160L, 0);
+
+        assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L);
+        assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
+        assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 260L, 26L);
+    }
+
+    @Test
+    public void vpnWithClat() throws Exception {
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {CLAT_PREFIX + TEST_IFACE})};
+        mFactory.updateVpnInfos(vpnInfos);
+        mFactory.noteStackedIface(CLAT_PREFIX + TEST_IFACE, TEST_IFACE);
+
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+        // over VPN.
+        // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+        // over VPN.
+        // VPN sent 1650 bytes (150 packets), and received 3300 (300 packets) over v4-WiFi, and clat
+        // added 20 bytes per packet of extra overhead
+        //
+        // For 1650 bytes sent over v4-WiFi, 4650 bytes were actually sent over WiFi, which is
+        // expected to be split as follows:
+        // UID_RED: 1000 bytes, 100 packets
+        // UID_BLUE: 500 bytes, 50 packets
+        // UID_VPN: 3150 bytes, 0 packets
+        //
+        // For 3300 bytes received over v4-WiFi, 9300 bytes were actually sent over WiFi, which is
+        // expected to be split as follows:
+        // UID_RED: 2000 bytes, 200 packets
+        // UID_BLUE: 1000 bytes, 100 packets
+        // UID_VPN: 6300 bytes, 0 packets
+        final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_with_clat);
+
+        assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_RED, 2000L, 200L, 1000, 100L);
+        assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
+        assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_VPN, 6300L, 0L, 3150L, 0L);
+    }
+
+    @Test
+    public void vpnWithOneUnderlyingIface() throws Exception {
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        mFactory.updateVpnInfos(vpnInfos);
+
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+        // over VPN.
+        // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+        // over VPN.
+        // VPN sent 1650 bytes (150 packets), and received 3300 (300 packets) over WiFi.
+        // Of 1650 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes
+        // attributed to UID_BLUE, and 150 bytes attributed to UID_VPN.
+        // Of 3300 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes
+        // attributed to UID_BLUE, and 300 bytes attributed to UID_VPN.
+        final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying);
+
+        assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L);
+        assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
+        assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 150L, 0L);
+    }
+
+    @Test
+    public void vpnWithOneUnderlyingIfaceAndOwnTraffic() throws Exception {
+        // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        mFactory.updateVpnInfos(vpnInfos);
+
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+        // over VPN.
+        // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+        // over VPN.
+        // Additionally, the VPN sends 6000 bytes (600 packets) of its own traffic into the tun
+        // interface (passing that traffic to the VPN endpoint), and receives 5000 bytes (500
+        // packets) from it. Including overhead that is 6600/5500 bytes.
+        // VPN sent 8250 bytes (750 packets), and received 8800 (800 packets) over WiFi.
+        // Of 8250 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes
+        // attributed to UID_BLUE, and 6750 bytes attributed to UID_VPN.
+        // Of 8800 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes
+        // attributed to UID_BLUE, and 5800 bytes attributed to UID_VPN.
+        final NetworkStats tunStats =
+                parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_own_traffic);
+
+        assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L);
+        assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
+        assertValues(tunStats, TEST_IFACE, UID_VPN, 5800L, 500L, 6750L, 600L);
+    }
+
+    @Test
+    public void vpnWithOneUnderlyingIface_withCompression() throws Exception {
+        // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        mFactory.updateVpnInfos(vpnInfos);
+
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
+        // 3000 bytes (300 packets) were sent/received by UID_BLUE over VPN.
+        // VPN sent/received 1000 bytes (100 packets) over WiFi.
+        // Of 1000 bytes over WiFi, expect 250 bytes attributed UID_RED and 750 bytes to UID_BLUE,
+        // with nothing attributed to UID_VPN for both rx/tx traffic.
+        final NetworkStats tunStats =
+                parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_compression);
+
+        assertValues(tunStats, TEST_IFACE, UID_RED, 250L, 25L, 250L, 25L);
+        assertValues(tunStats, TEST_IFACE, UID_BLUE, 750L, 75L, 750L, 75L);
+        assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L);
+    }
+
+    @Test
+    public void vpnWithTwoUnderlyingIfaces_packetDuplication() throws Exception {
+        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
+        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
+        // Additionally, VPN is duplicating traffic across both WiFi and Cell.
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+        mFactory.updateVpnInfos(vpnInfos);
+
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent/received by UID_RED and UID_BLUE over VPN.
+        // VPN sent/received 4400 bytes (400 packets) over both WiFi and Cell (8800 bytes in total).
+        // Of 8800 bytes over WiFi/Cell, expect:
+        // - 500 bytes rx/tx each over WiFi/Cell attributed to both UID_RED and UID_BLUE.
+        // - 1200 bytes rx/tx each over WiFi/Cell for VPN_UID.
+        final NetworkStats tunStats =
+                parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_duplication);
+
+        assertValues(tunStats, TEST_IFACE, UID_RED, 500L, 50L, 500L, 50L);
+        assertValues(tunStats, TEST_IFACE, UID_BLUE, 500L, 50L, 500L, 50L);
+        assertValues(tunStats, TEST_IFACE, UID_VPN, 1200L, 100L, 1200L, 100L);
+        assertValues(tunStats, TEST_IFACE2, UID_RED, 500L, 50L, 500L, 50L);
+        assertValues(tunStats, TEST_IFACE2, UID_BLUE, 500L, 50L, 500L, 50L);
+        assertValues(tunStats, TEST_IFACE2, UID_VPN, 1200L, 100L, 1200L, 100L);
+    }
+
+    @Test
+    public void vpnWithTwoUnderlyingIfaces_splitTraffic() throws Exception {
+        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
+        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
+        // Additionally, VPN is arbitrarily splitting traffic across WiFi and Cell.
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+        mFactory.updateVpnInfos(vpnInfos);
+
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent, and 500 bytes (50 packets) received by UID_RED over
+        // VPN.
+        // VPN sent 660 bytes (60 packets) over WiFi and 440 bytes (40 packets) over Cell.
+        // And, it received 330 bytes (30 packets) over WiFi and 220 bytes (20 packets) over Cell.
+        // For UID_RED, expect 600 bytes attributed over WiFi and 400 bytes over Cell for sent (tx)
+        // traffic. For received (rx) traffic, expect 300 bytes over WiFi and 200 bytes over Cell.
+        //
+        // For UID_VPN, expect 60 bytes attributed over WiFi and 40 bytes over Cell for tx traffic.
+        // And, 30 bytes over WiFi and 20 bytes over Cell for rx traffic.
+        final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_split);
+
+        assertValues(tunStats, TEST_IFACE, UID_RED, 300L, 30L, 600L, 60L);
+        assertValues(tunStats, TEST_IFACE, UID_VPN, 30L, 0L, 60L, 0L);
+        assertValues(tunStats, TEST_IFACE2, UID_RED, 200L, 20L, 400L, 40L);
+        assertValues(tunStats, TEST_IFACE2, UID_VPN, 20L, 0L, 40L, 0L);
+    }
+
+    @Test
+    public void vpnWithTwoUnderlyingIfaces_splitTrafficWithCompression() throws Exception {
+        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
+        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
+        // Additionally, VPN is arbitrarily splitting compressed traffic across WiFi and Cell.
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+        mFactory.updateVpnInfos(vpnInfos);
+
+        // create some traffic (assume 10 bytes of MTU for VPN interface:
+        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
+        // VPN sent/received 600 bytes (60 packets) over WiFi and 200 bytes (20 packets) over Cell.
+        // For UID_RED, expect 600 bytes attributed over WiFi and 200 bytes over Cell for both
+        // rx/tx.
+        // UID_VPN gets nothing attributed to it (avoiding negative stats).
+        final NetworkStats tunStats =
+                parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_split_compression);
+
+        assertValues(tunStats, TEST_IFACE, UID_RED, 600L, 60L, 600L, 60L);
+        assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L);
+        assertValues(tunStats, TEST_IFACE2, UID_RED, 200L, 20L, 200L, 20L);
+        assertValues(tunStats, TEST_IFACE2, UID_VPN, 0L, 0L, 0L, 0L);
+    }
+
+    @Test
+    public void vpnWithIncorrectUnderlyingIface() throws Exception {
+        // WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2),
+        // but has declared only WiFi (TEST_IFACE) in its underlying network set.
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        mFactory.updateVpnInfos(vpnInfos);
+
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
+        // VPN sent/received 1100 bytes (100 packets) over Cell.
+        // Of 1100 bytes over Cell, expect all of it attributed to UID_VPN for both rx/tx traffic.
+        final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_incorrect_iface);
+
+        assertValues(tunStats, TEST_IFACE, UID_RED, 0L, 0L, 0L, 0L);
+        assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L);
+        assertValues(tunStats, TEST_IFACE2, UID_RED, 0L, 0L, 0L, 0L);
+        assertValues(tunStats, TEST_IFACE2, UID_VPN, 1100L, 100L, 1100L, 100L);
+    }
+
+    @Test
     public void testKernelTags() throws Exception {
         assertEquals(0, kernelToTag("0x0000000000000000"));
         assertEquals(0x32, kernelToTag("0x0000003200000000"));
@@ -146,20 +381,25 @@
     }
 
     @Test
-    public void testDoubleClatAccounting() throws Exception {
-        NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0");
+    public void testDoubleClatAccountingSimple() throws Exception {
+        mFactory.noteStackedIface("v4-wlan0", "wlan0");
 
         // xt_qtaguid_with_clat_simple is a synthetic file that simulates
         //  - 213 received 464xlat packets of size 200 bytes
         //  - 41 sent 464xlat packets of size 100 bytes
         //  - no other traffic on base interface for root uid.
         NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_simple);
-        assertEquals(4, stats.size());
+        assertEquals(3, stats.size());
 
         assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 46860L, 4920L);
         assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 0L, 0L);
+    }
 
-        stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat);
+    @Test
+    public void testDoubleClatAccounting() throws Exception {
+        mFactory.noteStackedIface("v4-wlan0", "wlan0");
+
+        NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat);
         assertEquals(42, stats.size());
 
         assertStatsEntry(stats, "v4-wlan0", 0, SET_DEFAULT, 0x0, 356L, 276L);
@@ -178,8 +418,6 @@
         assertStatsEntry(stats, "lo", 0, SET_DEFAULT, 0x0, 1288L, 1288L);
 
         assertNoStatsEntry(stats, "wlan0", 1029, SET_DEFAULT, 0x0);
-
-        NetworkStatsFactory.clearStackedIfaces();
     }
 
     @Test
@@ -194,7 +432,7 @@
         long rootRxBytesAfter = 1398634L;
         assertEquals("UID 0 traffic should be ~0", 4623, rootRxBytesAfter - rootRxBytesBefore);
 
-        NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0");
+        mFactory.noteStackedIface("v4-wlan0", "wlan0");
         NetworkStats stats;
 
         // Stats snapshot before the download
@@ -206,8 +444,6 @@
         stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_after);
         assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesAfter, 7867488L);
         assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesAfter, 0L);
-
-        NetworkStatsFactory.clearStackedIfaces();
     }
 
     /**
@@ -272,11 +508,19 @@
 
     private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
             int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
-        final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO,
-                DEFAULT_NETWORK_NO);
+        assertStatsEntry(stats, iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+                rxBytes, rxPackets, txBytes, txPackets);
+    }
+
+    private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+            int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
+            long txBytes, long txPackets) {
+        final int i = stats.findIndex(iface, uid, set, tag, metered, roaming, defaultNetwork);
+
         if (i < 0) {
-            fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d)",
-                    iface, uid, set, tag));
+            fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d, metered:"
+                    + " %d, roaming: %d, defaultNetwork: %d)",
+                    iface, uid, set, tag, metered, roaming, defaultNetwork));
         }
         final NetworkStats.Entry entry = stats.getValues(i, null);
         assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index 43a3803..c0f9dc1 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -28,8 +28,6 @@
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
-import static com.android.internal.util.TestUtils.waitForIdleHandler;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
@@ -54,8 +52,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.net.VpnInfo;
 import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
+import com.android.testutils.HandlerUtilsKt;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -94,8 +92,6 @@
     private static final long BASE_BYTES = 7 * MB_IN_BYTES;
     private static final int INVALID_TYPE = -1;
 
-    private static final VpnInfo[] VPN_INFO = new VpnInfo[0];
-
     private long mElapsedRealtime;
 
     private HandlerThread mObserverHandlerThread;
@@ -248,8 +244,7 @@
         NetworkStats uidSnapshot = null;
 
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
         waitForObserverToIdle();
     }
 
@@ -272,15 +267,13 @@
                 .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
         NetworkStats uidSnapshot = null;
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
                 .addIfaceValues(TEST_IFACE, BASE_BYTES + 1024L, 10L, BASE_BYTES + 2048L, 20L);
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
         waitForObserverToIdle();
     }
 
@@ -304,16 +297,14 @@
                 .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
         NetworkStats uidSnapshot = null;
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */)
                 .addIfaceValues(TEST_IFACE, BASE_BYTES + THRESHOLD_BYTES, 12L,
                         BASE_BYTES + THRESHOLD_BYTES, 22L);
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
         waitForObserverToIdle();
         assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
     }
@@ -338,8 +329,7 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
@@ -347,8 +337,7 @@
                         DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
         waitForObserverToIdle();
         assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
     }
@@ -373,8 +362,7 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
@@ -382,8 +370,7 @@
                         DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
         waitForObserverToIdle();
     }
 
@@ -407,8 +394,7 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
@@ -416,8 +402,7 @@
                         DEFAULT_NETWORK_YES, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
         waitForObserverToIdle();
         assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
     }
@@ -442,8 +427,7 @@
                 .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
                         ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
@@ -451,13 +435,12 @@
                         ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
-                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
-                VPN_INFO, TEST_START);
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
         waitForObserverToIdle();
     }
 
     private void waitForObserverToIdle() {
-        waitForIdleHandler(mObserverHandlerThread, WAIT_TIMEOUT_MS);
-        waitForIdleHandler(mHandler, WAIT_TIMEOUT_MS);
+        HandlerUtilsKt.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS);
+        HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT_MS);
     }
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index e35c34a..1d29a82 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -23,7 +23,6 @@
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivityManager.TYPE_WIMAX;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
-import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.INTERFACES_ALL;
@@ -38,11 +37,11 @@
 import static android.net.NetworkStats.SET_FOREGROUND;
 import static android.net.NetworkStats.STATS_PER_IFACE;
 import static android.net.NetworkStats.STATS_PER_UID;
+import static android.net.NetworkStats.TAG_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.NetworkStatsHistory.FIELD_ALL;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
-import static android.net.NetworkTemplate.buildTemplateMobileWildcard;
 import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.net.TrafficStats.UID_REMOVED;
@@ -52,17 +51,14 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
 
-import static com.android.internal.util.TestUtils.waitForIdleHandler;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -105,6 +101,7 @@
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
+import com.android.testutils.HandlerUtilsKt;
 
 import libcore.io.IoUtils;
 
@@ -130,13 +127,9 @@
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class NetworkStatsServiceTest {
+public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
     private static final String TAG = "NetworkStatsServiceTest";
 
-    private static final String TEST_IFACE = "test0";
-    private static final String TEST_IFACE2 = "test1";
-    private static final String TUN_IFACE = "test_nss_tun0";
-
     private static final long TEST_START = 1194220800000L;
 
     private static final String IMSI_1 = "310004";
@@ -147,11 +140,6 @@
     private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
     private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
 
-    private static final int UID_RED = 1001;
-    private static final int UID_BLUE = 1002;
-    private static final int UID_GREEN = 1003;
-    private static final int UID_VPN = 1004;
-
     private static final Network WIFI_NETWORK =  new Network(100);
     private static final Network MOBILE_NETWORK =  new Network(101);
     private static final Network VPN_NETWORK = new Network(102);
@@ -168,6 +156,7 @@
     private File mStatsDir;
 
     private @Mock INetworkManagementService mNetManager;
+    private @Mock NetworkStatsFactory mStatsFactory;
     private @Mock NetworkStatsSettings mSettings;
     private @Mock IBinder mBinder;
     private @Mock AlarmManager mAlarmManager;
@@ -203,8 +192,8 @@
 
         mService = new NetworkStatsService(
                 mServiceContext, mNetManager, mAlarmManager, wakeLock, mClock,
-                TelephonyManager.getDefault(), mSettings, new NetworkStatsObservers(),
-                mStatsDir, getBaseDir(mStatsDir));
+                TelephonyManager.getDefault(), mSettings, mStatsFactory,
+                new NetworkStatsObservers(),  mStatsDir, getBaseDir(mStatsDir));
         mHandlerThread = new HandlerThread("HandlerThread");
         mHandlerThread.start();
         Handler.Callback callback = new NetworkStatsService.HandlerCallback(mService);
@@ -217,12 +206,9 @@
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
-        assertNull(mService.getTunAdjustedStats());
         mService.systemReady();
-        // Verify that system ready fetches realtime stats and initializes tun adjusted stats.
-        verify(mNetManager).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL);
-        assertNotNull("failed to initialize TUN adjusted stats", mService.getTunAdjustedStats());
-        assertEquals(0, mService.getTunAdjustedStats().size());
+        // Verify that system ready fetches realtime stats
+        verify(mStatsFactory).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
 
         mSession = mService.openSession();
         assertNotNull("openSession() failed", mSession);
@@ -256,9 +242,8 @@
         NetworkState[] states = new NetworkState[] {buildWifiState()};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -300,9 +285,8 @@
         NetworkState[] states = new NetworkState[] {buildWifiState()};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -374,10 +358,8 @@
         NetworkState[] states = new NetworkState[] {buildWifiState()};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
-
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
 
         // modify some number on wifi, and trigger poll event
         incrementCurrentTime(2 * HOUR_IN_MILLIS);
@@ -416,10 +398,8 @@
         NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
-
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
 
         // create some traffic on first network
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -452,9 +432,8 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
         forcePollAndWaitForIdle();
 
 
@@ -492,10 +471,8 @@
         NetworkState[] states = new NetworkState[] {buildWifiState()};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
-
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
 
         // create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -551,10 +528,8 @@
         NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
-
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
 
         // create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -579,9 +554,8 @@
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
         forcePollAndWaitForIdle();
 
 
@@ -609,10 +583,8 @@
         NetworkState[] states = new NetworkState[] {buildWifiState()};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
-
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
 
         // create some traffic for two apps
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -668,9 +640,8 @@
         NetworkState[] states = new NetworkState[] {buildWifiState()};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
 
         NetworkStats.Entry entry1 = new NetworkStats.Entry(
                 TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L);
@@ -712,9 +683,8 @@
 
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
 
         NetworkStats.Entry uidStats = new NetworkStats.Entry(
                 TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
@@ -726,10 +696,13 @@
                 "otherif", UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
 
         final String[] ifaceFilter = new String[] { TEST_IFACE };
+        final String[] augmentedIfaceFilter = new String[] { stackedIface, TEST_IFACE };
         incrementCurrentTime(HOUR_IN_MILLIS);
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
-        when(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL), any()))
+        when(mStatsFactory.augmentWithStackedInterfaces(eq(ifaceFilter)))
+                .thenReturn(augmentedIfaceFilter);
+        when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL)))
                 .thenReturn(new NetworkStats(getElapsedRealtime(), 1)
                         .addValues(uidStats));
         when(mNetManager.getNetworkStatsTethering(STATS_PER_UID))
@@ -739,11 +712,17 @@
 
         NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
 
-        // mNetManager#getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL) has following invocations:
+        // mStatsFactory#readNetworkStatsDetail() has the following invocations:
         // 1) NetworkStatsService#systemReady from #setUp.
         // 2) mService#forceUpdateIfaces in the test above.
-        // 3) Finally, mService#getDetailedUidStats.
-        verify(mNetManager, times(3)).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL);
+        //
+        // Additionally, we should have one call from the above call to mService#getDetailedUidStats
+        // with the augmented ifaceFilter.
+        verify(mStatsFactory, times(2)).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
+        verify(mStatsFactory, times(1)).readNetworkStatsDetail(
+                eq(UID_ALL),
+                eq(augmentedIfaceFilter),
+                eq(TAG_ALL));
         assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE));
         assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface));
         assertEquals(2, stats.size());
@@ -758,10 +737,8 @@
         NetworkState[] states = new NetworkState[] {buildWifiState()};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
-
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
 
         // create some initial traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -816,10 +793,8 @@
         NetworkState[] states = new NetworkState[] {buildWifiState(true /* isMetered */)};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
-
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
 
         // create some initial traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -857,10 +832,8 @@
             new NetworkState[] {buildMobile3gState(IMSI_1, true /* isRoaming */)};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
-
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
 
         // Create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -896,10 +869,8 @@
         NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
-
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
 
         // create some tethering traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -931,369 +902,6 @@
     }
 
     @Test
-    public void vpnWithOneUnderlyingIface() throws Exception {
-        // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
-        expectDefaultSettings();
-        NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()};
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
-        // 500 bytes (50 packets) were sent/received by UID_BLUE over VPN.
-        // VPN sent/received 1650 bytes (150 packets) over WiFi.
-        // Of 1650 bytes over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes attributed to
-        // UID_BLUE, and 150 bytes attributed to UID_VPN for both rx/tx traffic.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L)
-                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 500L, 50L, 500L, 50L, 1L)
-                // VPN received 1650 bytes over WiFi in background (SET_DEFAULT).
-                .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1650L, 150L, 0L, 0L, 1L)
-                // VPN sent 1650 bytes over WiFi in foreground (SET_FOREGROUND).
-                .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 1650L, 150L, 1L));
-
-        forcePollAndWaitForIdle();
-
-        assertUidTotal(sTemplateWifi, UID_RED, 1000L, 100L, 1000L, 100L, 1);
-        assertUidTotal(sTemplateWifi, UID_BLUE, 500L, 50L, 500L, 50L, 1);
-        assertUidTotal(sTemplateWifi, UID_VPN, 150L, 0L, 150L, 0L, 2);
-    }
-
-    @Test
-    public void vpnWithOneUnderlyingIface_withCompression() throws Exception {
-        // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
-        expectDefaultSettings();
-        NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()};
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
-        // 3000 bytes (300 packets) were sent/received by UID_BLUE over VPN.
-        // VPN sent/received 1000 bytes (100 packets) over WiFi.
-        // Of 1000 bytes over WiFi, expect 250 bytes attributed UID_RED and 750 bytes to UID_BLUE,
-        // with nothing attributed to UID_VPN for both rx/tx traffic.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L)
-                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 3000L, 300L, 3000L, 300L, 1L)
-                .addValues(
-                    TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 0L));
-
-        forcePollAndWaitForIdle();
-
-        assertUidTotal(sTemplateWifi, UID_RED, 250L, 25L, 250L, 25L, 0);
-        assertUidTotal(sTemplateWifi, UID_BLUE, 750L, 75L, 750L, 75L, 0);
-        assertUidTotal(sTemplateWifi, UID_VPN, 0L, 0L, 0L, 0L, 0);
-    }
-
-    @Test
-    public void vpnWithTwoUnderlyingIfaces_packetDuplication() throws Exception {
-        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
-        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
-        // Additionally, VPN is duplicating traffic across both WiFi and Cell.
-        expectDefaultSettings();
-        NetworkState[] networkStates =
-                new NetworkState[] {
-                    buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
-                };
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were sent/received by UID_RED and UID_BLUE over VPN.
-        // VPN sent/received 4400 bytes (400 packets) over both WiFi and Cell (8800 bytes in total).
-        // Of 8800 bytes over WiFi/Cell, expect:
-        // - 500 bytes rx/tx each over WiFi/Cell attributed to both UID_RED and UID_BLUE.
-        // - 1200 bytes rx/tx each over WiFi/Cell for VPN_UID.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4)
-                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L)
-                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L)
-                .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 2200L, 200L, 2L)
-                .addValues(
-                    TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 2200L, 200L, 2L));
-
-        forcePollAndWaitForIdle();
-
-        assertUidTotal(sTemplateWifi, UID_RED, 500L, 50L, 500L, 50L, 1);
-        assertUidTotal(sTemplateWifi, UID_BLUE, 500L, 50L, 500L, 50L, 1);
-        assertUidTotal(sTemplateWifi, UID_VPN, 1200L, 100L, 1200L, 100L, 2);
-
-        assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 500L, 50L, 500L, 50L, 1);
-        assertUidTotal(buildTemplateMobileWildcard(), UID_BLUE, 500L, 50L, 500L, 50L, 1);
-        assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 1200L, 100L, 1200L, 100L, 2);
-    }
-
-    @Test
-    public void vpnWithTwoUnderlyingIfaces_splitTraffic() throws Exception {
-        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
-        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
-        // Additionally, VPN is arbitrarily splitting traffic across WiFi and Cell.
-        expectDefaultSettings();
-        NetworkState[] networkStates =
-                new NetworkState[] {
-                    buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
-                };
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
-        // VPN sent/received 660 bytes (60 packets) over WiFi and 440 bytes (40 packets) over Cell.
-        // For UID_RED, expect 600 bytes attributed over WiFi and 400 bytes over Cell for both
-        // rx/tx.
-        // For UID_VPN, expect 60 bytes attributed over WiFi and 40 bytes over Cell for both rx/tx.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 660L, 60L, 660L, 60L, 1L)
-              .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 440L, 40L, 440L, 40L, 1L));
-
-        forcePollAndWaitForIdle();
-
-        assertUidTotal(sTemplateWifi, UID_RED, 600L, 60L, 600L, 60L, 1);
-        assertUidTotal(sTemplateWifi, UID_VPN, 60L, 0L, 60L, 0L, 1);
-
-        assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 400L, 40L, 400L, 40L, 1);
-        assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 40L, 0L, 40L, 0L, 1);
-    }
-
-    @Test
-    public void vpnWithTwoUnderlyingIfaces_splitTrafficWithCompression() throws Exception {
-        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
-        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
-        // Additionally, VPN is arbitrarily splitting compressed traffic across WiFi and Cell.
-        expectDefaultSettings();
-        NetworkState[] networkStates =
-                new NetworkState[] {
-                    buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
-                };
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface:
-        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
-        // VPN sent/received 600 bytes (60 packets) over WiFi and 200 bytes (20 packets) over Cell.
-        // For UID_RED, expect 600 bytes attributed over WiFi and 200 bytes over Cell for both
-        // rx/tx.
-        // UID_VPN gets nothing attributed to it (avoiding negative stats).
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 600L, 60L, 600L, 60L, 0L)
-              .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 200L, 20L, 200L, 20L, 0L));
-
-        forcePollAndWaitForIdle();
-
-        assertUidTotal(sTemplateWifi, UID_RED, 600L, 60L, 600L, 60L, 0);
-        assertUidTotal(sTemplateWifi, UID_VPN, 0L, 0L, 0L, 0L, 0);
-
-        assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 200L, 20L, 200L, 20L, 0);
-        assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 0L, 0L, 0L, 0L, 0);
-    }
-
-    @Test
-    public void vpnWithIncorrectUnderlyingIface() throws Exception {
-        // WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2),
-        // but has declared only WiFi (TEST_IFACE) in its underlying network set.
-        expectDefaultSettings();
-        NetworkState[] networkStates =
-                new NetworkState[] {
-                    buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
-                };
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
-        // VPN sent/received 1100 bytes (100 packets) over Cell.
-        // Of 1100 bytes over Cell, expect all of it attributed to UID_VPN for both rx/tx traffic.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L)
-                .addValues(
-                    TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 1100L, 100L, 1L));
-
-        forcePollAndWaitForIdle();
-
-        assertUidTotal(sTemplateWifi, UID_RED, 0L, 0L, 0L, 0L, 0);
-        assertUidTotal(sTemplateWifi, UID_VPN, 0L, 0L, 0L, 0L, 0);
-        assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 0L, 0L, 0L, 0L, 0);
-        assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 1100L, 100L, 1100L, 100L, 1);
-    }
-
-    @Test
-    public void recordSnapshot_migratesTunTrafficAndUpdatesTunAdjustedStats() throws Exception {
-        assertEquals(0, mService.getTunAdjustedStats().size());
-        // VPN using WiFi (TEST_IFACE).
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectBandwidthControlCheck();
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were downloaded by UID_RED over VPN.
-        // VPN received 1100 bytes (100 packets) over WiFi.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L));
-
-        // this should lead to NSS#recordSnapshotLocked
-        mService.forceUpdateIfaces(
-                new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */);
-
-        // Verify TUN adjusted stats have traffic migrated correctly.
-        // Of 1100 bytes VPN received over WiFi, expect 1000 bytes attributed to UID_RED and 100
-        // bytes attributed to UID_VPN.
-        NetworkStats tunAdjStats = mService.getTunAdjustedStats();
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0);
-    }
-
-    @Test
-    public void getDetailedUidStats_migratesTunTrafficAndUpdatesTunAdjustedStats()
-            throws Exception {
-        assertEquals(0, mService.getTunAdjustedStats().size());
-        // VPN using WiFi (TEST_IFACE).
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectBandwidthControlCheck();
-        mService.forceUpdateIfaces(
-                new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */);
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were downloaded by UID_RED over VPN.
-        // VPN received 1100 bytes (100 packets) over WiFi.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L));
-
-        mService.getDetailedUidStats(INTERFACES_ALL);
-
-        // Verify internally maintained TUN adjusted stats
-        NetworkStats tunAdjStats = mService.getTunAdjustedStats();
-        // Verify stats for TEST_IFACE (WiFi):
-        // Of 1100 bytes VPN received over WiFi, expect 1000 bytes attributed to UID_RED and 100
-        // bytes attributed to UID_VPN.
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0);
-        // Verify stats for TUN_IFACE; only UID_RED should have usage on it.
-        assertValues(
-                tunAdjStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
-        assertValues(
-                tunAdjStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 0);
-
-        // lets assume that since last time, VPN received another 1100 bytes (same assumptions as
-        // before i.e. UID_RED downloaded another 1000 bytes).
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        // Note - NetworkStatsFactory returns counters that are monotonically increasing.
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 0L, 0L, 0L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 0L, 0L, 0L));
-
-        mService.getDetailedUidStats(INTERFACES_ALL);
-
-        tunAdjStats = mService.getTunAdjustedStats();
-        // verify TEST_IFACE stats:
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 2000L, 200L, 0L, 0L, 0);
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 200L, 0L, 0L, 0L, 0);
-        // verify TUN_IFACE stats:
-        assertValues(
-                tunAdjStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 2000L, 200L, 0L, 0L, 0);
-        assertValues(
-                tunAdjStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 0);
-    }
-
-    @Test
-    public void getDetailedUidStats_returnsCorrectStatsWithVpnRunning() throws Exception {
-        // VPN using WiFi (TEST_IFACE).
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectBandwidthControlCheck();
-        mService.forceUpdateIfaces(
-                new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */);
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were downloaded by UID_RED over VPN.
-        // VPN received 1100 bytes (100 packets) over WiFi.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L));
-
-        // Query realtime stats for TEST_IFACE.
-        NetworkStats queriedStats =
-                mService.getDetailedUidStats(new String[] {TEST_IFACE});
-
-        assertEquals(HOUR_IN_MILLIS, queriedStats.getElapsedRealtime());
-        // verify that returned stats are only for TEST_IFACE and VPN traffic is migrated correctly.
-        assertEquals(new String[] {TEST_IFACE}, queriedStats.getUniqueIfaces());
-        assertValues(
-                queriedStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
-        assertValues(
-                queriedStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0);
-    }
-
-    @Test
     public void testRegisterUsageCallback() throws Exception {
         // pretend that wifi network comes online; service should ask about full
         // network state, and poll any existing interfaces before updating.
@@ -1301,9 +909,8 @@
         NetworkState[] states = new NetworkState[] {buildWifiState()};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -1321,8 +928,6 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-
-
         // Register and verify request and that binder was called
         DataUsageRequest request =
                 mService.registerUsageCallback(mServiceContext.getOpPackageName(), inputRequest,
@@ -1334,14 +939,11 @@
 
         // Send dummy message to make sure that any previous message has been handled
         mHandler.sendMessage(mHandler.obtainMessage(-1));
-        waitForIdleHandler(mHandler, WAIT_TIMEOUT);
-
-
+        HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT);
 
         // Make sure that the caller binder gets connected
         verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
-
         // modify some number on wifi, and trigger poll event
         // not enough traffic to call data usage callback
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -1447,7 +1049,6 @@
 
     private void expectSystemReady() throws Exception {
         expectNetworkStatsSummary(buildEmptyStats());
-        expectBandwidthControlCheck();
     }
 
     private String getActiveIface(NetworkState... states) throws Exception {
@@ -1469,11 +1070,11 @@
     }
 
     private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
-        when(mNetManager.getNetworkStatsSummaryDev()).thenReturn(summary);
+        when(mStatsFactory.readNetworkStatsSummaryDev()).thenReturn(summary);
     }
 
     private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
-        when(mNetManager.getNetworkStatsSummaryXt()).thenReturn(summary);
+        when(mStatsFactory.readNetworkStatsSummaryXt()).thenReturn(summary);
     }
 
     private void expectNetworkStatsTethering(int how, NetworkStats stats)
@@ -1487,7 +1088,8 @@
 
     private void expectNetworkStatsUidDetail(NetworkStats detail, NetworkStats tetherStats)
             throws Exception {
-        when(mNetManager.getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL)).thenReturn(detail);
+        when(mStatsFactory.readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL))
+                .thenReturn(detail);
 
         // also include tethering details, since they are folded into UID
         when(mNetManager.getNetworkStatsTethering(STATS_PER_UID)).thenReturn(tetherStats);
@@ -1515,10 +1117,6 @@
         when(mSettings.getUidTagPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
     }
 
-    private void expectBandwidthControlCheck() throws Exception {
-        when(mNetManager.isBandwidthControlEnabled()).thenReturn(true);
-    }
-
     private void assertStatsFilesExist(boolean exist) {
         final File basePath = new File(mStatsDir, "netstats");
         if (exist) {
@@ -1528,59 +1126,6 @@
         }
     }
 
-    private static void assertValues(NetworkStats stats, String iface, int uid, int set,
-            int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
-            long txBytes, long txPackets, int operations) {
-        final NetworkStats.Entry entry = new NetworkStats.Entry();
-        final int[] sets;
-        if (set == SET_ALL) {
-            sets = new int[] { SET_ALL, SET_DEFAULT, SET_FOREGROUND };
-        } else {
-            sets = new int[] { set };
-        }
-
-        final int[] roamings;
-        if (roaming == ROAMING_ALL) {
-            roamings = new int[] { ROAMING_ALL, ROAMING_YES, ROAMING_NO };
-        } else {
-            roamings = new int[] { roaming };
-        }
-
-        final int[] meterings;
-        if (metered == METERED_ALL) {
-            meterings = new int[] { METERED_ALL, METERED_YES, METERED_NO };
-        } else {
-            meterings = new int[] { metered };
-        }
-
-        final int[] defaultNetworks;
-        if (defaultNetwork == DEFAULT_NETWORK_ALL) {
-            defaultNetworks = new int[] { DEFAULT_NETWORK_ALL, DEFAULT_NETWORK_YES,
-                    DEFAULT_NETWORK_NO };
-        } else {
-            defaultNetworks = new int[] { defaultNetwork };
-        }
-
-        for (int s : sets) {
-            for (int r : roamings) {
-                for (int m : meterings) {
-                    for (int d : defaultNetworks) {
-                        final int i = stats.findIndex(iface, uid, s, tag, m, r, d);
-                        if (i != -1) {
-                            entry.add(stats.getValues(i, null));
-                        }
-                    }
-                }
-            }
-        }
-
-        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
-        assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
-        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
-        assertEquals("unexpected txPackets", txPackets, entry.txPackets);
-        assertEquals("unexpected operations", operations, entry.operations);
-    }
-
     private static void assertValues(NetworkStatsHistory stats, long start, long end, long rxBytes,
             long rxPackets, long txBytes, long txPackets, int operations) {
         final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
@@ -1646,14 +1191,6 @@
         return new NetworkState(info, prop, new NetworkCapabilities(), VPN_NETWORK, null, null);
     }
 
-    private static VpnInfo createVpnInfo(String[] underlyingIfaces) {
-        VpnInfo info = new VpnInfo();
-        info.ownerUid = UID_VPN;
-        info.vpnIface = TUN_IFACE;
-        info.underlyingIfaces = underlyingIfaces;
-        return info;
-    }
-
     private long getElapsedRealtime() {
         return mElapsedRealtime;
     }
@@ -1674,7 +1211,7 @@
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
         // Send dummy message to make sure that any previous message has been handled
         mHandler.sendMessage(mHandler.obtainMessage(-1));
-        waitForIdleHandler(mHandler, WAIT_TIMEOUT);
+        HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT);
     }
 
     static class LatchedHandler extends Handler {
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_incorrect_iface b/tests/net/res/raw/xt_qtaguid_vpn_incorrect_iface
new file mode 100644
index 0000000..fc92715
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_incorrect_iface
@@ -0,0 +1,3 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test1 0x0 1004 0 1100 100 1100 100 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_one_underlying b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying
new file mode 100644
index 0000000..1ef1889
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying
@@ -0,0 +1,5 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 2000 200 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 1000 100 500 50 0 0 0 0 0 0 0 0 0 0 0 0
+4 test0 0x0 1004 0 3300 300 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+5 test0 0x0 1004 1 0 0 1650 150 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_compression b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_compression
new file mode 100644
index 0000000..6d6bf55
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_compression
@@ -0,0 +1,4 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 3000 300 3000 300 0 0 0 0 0 0 0 0 0 0 0 0
+4 test0 0x0 1004 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_own_traffic b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_own_traffic
new file mode 100644
index 0000000..2c2e5d2
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_own_traffic
@@ -0,0 +1,6 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 2000 200 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 1000 100 500 50 0 0 0 0 0 0 0 0 0 0 0 0
+4 test_nss_tun0 0x0 1004 0 5000 500 6000 600 0 0 0 0 0 0 0 0 0 0 0 0
+5 test0 0x0 1004 0 8800 800 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+6 test0 0x0 1004 1 0 0 8250 750 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_rewrite_through_self b/tests/net/res/raw/xt_qtaguid_vpn_rewrite_through_self
new file mode 100644
index 0000000..afcdd71
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_rewrite_through_self
@@ -0,0 +1,6 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 2000 200 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 1000 100 500 50 0 0 0 0 0 0 0 0 0 0 0 0
+4 test_nss_tun0 0x0 1004 0 0 0 1600 160 0 0 0 0 0 0 0 0 0 0 0 0
+5 test0 0x0 1004 1 0 0 1760 176 0 0 0 0 0 0 0 0 0 0 0 0
+6 test0 0x0 1004 0 3300 300 0 0 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_duplication b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_duplication
new file mode 100644
index 0000000..d7c7eb9
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_duplication
@@ -0,0 +1,5 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+4 test0 0x0 1004 0 2200 200 2200 200 0 0 0 0 0 0 0 0 0 0 0 0
+5 test1 0x0 1004 0 2200 200 2200 200 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split
new file mode 100644
index 0000000..38a3dce
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split
@@ -0,0 +1,4 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 500 50 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test0 0x0 1004 0 330 30 660 60 0 0 0 0 0 0 0 0 0 0 0 0
+4 test1 0x0 1004 0 220 20 440 40 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split_compression b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split_compression
new file mode 100644
index 0000000..d35244b
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split_compression
@@ -0,0 +1,4 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test0 0x0 1004 0 600 60 600 60 0 0 0 0 0 0 0 0 0 0 0 0
+4 test1 0x0 1004 0 200 20 200 20 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_with_clat b/tests/net/res/raw/xt_qtaguid_vpn_with_clat
new file mode 100644
index 0000000..0d893d5
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_with_clat
@@ -0,0 +1,8 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 2000 200 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 1000 100 500 50 0 0 0 0 0 0 0 0 0 0 0 0
+4 v4-test0 0x0 1004 0 3300 300 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+5 v4-test0 0x0 1004 1 0 0 1650 150 0 0 0 0 0 0 0 0 0 0 0 0
+6 test0 0x0 0 0 9300 300 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+7 test0 0x0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+8 test0 0x0 1029 0 0 0 4650 150 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_simple b/tests/net/res/raw/xt_qtaguid_with_clat_simple
index 8c132e7..b37fae6 100644
--- a/tests/net/res/raw/xt_qtaguid_with_clat_simple
+++ b/tests/net/res/raw/xt_qtaguid_with_clat_simple
@@ -2,5 +2,4 @@
 2 v4-wlan0 0x0 10060 0 42600 213 4100 41 42600 213 0 0 0 0 4100 41 0 0 0 0
 3 v4-wlan0 0x0 10060 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 4 wlan0 0x0 0 0 46860 213 0 0 46860 213 0 0 0 0 0 0 0 0 0 0
-5 wlan0 0x0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-6 wlan0 0x0 1029 0 0 0 4920 41 0 0 0 0 0 0 4920 41 0 0 0 0
+5 wlan0 0x0 1029 0 0 0 4920 41 0 0 0 0 0 0 4920 41 0 0 0 0
diff --git a/tests/net/smoketest/Android.bp b/tests/net/smoketest/Android.bp
index ef1ad2c..84ae2b5 100644
--- a/tests/net/smoketest/Android.bp
+++ b/tests/net/smoketest/Android.bp
@@ -14,4 +14,9 @@
     defaults: ["FrameworksNetTests-jni-defaults"],
     srcs: ["java/SmokeTest.java"],
     test_suites: ["device-tests"],
-}
+    static_libs: [
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+        "services.core",
+    ],
+}
\ No newline at end of file
diff --git a/tests/net/util/java/com/android/internal/util/ParcelableTestUtil.java b/tests/net/util/java/com/android/internal/util/ParcelableTestUtil.java
deleted file mode 100644
index 87537b9..0000000
--- a/tests/net/util/java/com/android/internal/util/ParcelableTestUtil.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import static org.junit.Assert.assertEquals;
-
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-
-/**
- * Utility classes to write tests for stable AIDL parceling/unparceling
- */
-public final class ParcelableTestUtil {
-
-    /**
-     * Verifies that the number of nonstatic fields in a class equals a given count.
-     *
-     * <p>This assertion serves as a reminder to update test code around it if fields are added
-     * after the test is written.
-     * @param count Expected number of nonstatic fields in the class.
-     * @param clazz Class to test.
-     */
-    public static <T> void assertFieldCountEquals(int count, Class<T> clazz) {
-        assertEquals(count, Arrays.stream(clazz.getDeclaredFields())
-                .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
-    }
-}
diff --git a/tests/net/util/java/com/android/internal/util/TestUtils.java b/tests/net/util/java/com/android/internal/util/TestUtils.java
deleted file mode 100644
index a99cd47..0000000
--- a/tests/net/util/java/com/android/internal/util/TestUtils.java
+++ /dev/null
@@ -1,100 +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.internal.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import androidx.annotation.NonNull;
-
-import java.util.concurrent.Executor;
-
-public final class TestUtils {
-    private TestUtils() { }
-
-    /**
-     * Block until the given Handler thread becomes idle, or until timeoutMs has passed.
-     */
-    public static void waitForIdleHandler(HandlerThread handlerThread, long timeoutMs) {
-        waitForIdleLooper(handlerThread.getLooper(), timeoutMs);
-    }
-
-    /**
-     * Block until the given Looper becomes idle, or until timeoutMs has passed.
-     */
-    public static void waitForIdleLooper(Looper looper, long timeoutMs) {
-        waitForIdleHandler(new Handler(looper), timeoutMs);
-    }
-
-    /**
-     * Block until the given Handler becomes idle, or until timeoutMs has passed.
-     */
-    public static void waitForIdleHandler(Handler handler, long timeoutMs) {
-        final ConditionVariable cv = new ConditionVariable();
-        handler.post(() -> cv.open());
-        if (!cv.block(timeoutMs)) {
-            fail(handler.toString() + " did not become idle after " + timeoutMs + " ms");
-        }
-    }
-
-    /**
-     * Block until the given Serial Executor becomes idle, or until timeoutMs has passed.
-     */
-    public static void waitForIdleSerialExecutor(@NonNull Executor executor, long timeoutMs) {
-        final ConditionVariable cv = new ConditionVariable();
-        executor.execute(() -> cv.open());
-        if (!cv.block(timeoutMs)) {
-            fail(executor.toString() + " did not become idle after " + timeoutMs + " ms");
-        }
-    }
-
-    /**
-     * Return a new instance of {@code T} after being parceled then unparceled.
-     */
-    public static <T extends Parcelable> T parcelingRoundTrip(T source) {
-        final Parcelable.Creator<T> creator;
-        try {
-            creator = (Parcelable.Creator<T>) source.getClass().getField("CREATOR").get(null);
-        } catch (IllegalAccessException | NoSuchFieldException e) {
-            fail("Missing CREATOR field: " + e.getMessage());
-            return null;
-        }
-        Parcel p = Parcel.obtain();
-        source.writeToParcel(p, /* flags */ 0);
-        p.setDataPosition(0);
-        final byte[] marshalled = p.marshall();
-        p = Parcel.obtain();
-        p.unmarshall(marshalled, 0, marshalled.length);
-        p.setDataPosition(0);
-        return creator.createFromParcel(p);
-    }
-
-    /**
-     * Assert that after being parceled then unparceled, {@code source} is equal to the original
-     * object.
-     */
-    public static <T extends Parcelable> void assertParcelingIsLossless(T source) {
-        assertEquals(source, parcelingRoundTrip(source));
-    }
-}
diff --git a/tests/permission/src/com/android/framework/permission/tests/ServiceManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/ServiceManagerPermissionTests.java
deleted file mode 100644
index dcbbdbb..0000000
--- a/tests/permission/src/com/android/framework/permission/tests/ServiceManagerPermissionTests.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.framework.permission.tests;
-
-import com.android.internal.os.BinderInternal;
-
-import android.app.AppOpsManager;
-import android.os.Binder;
-import android.os.IPermissionController;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManagerNative;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import junit.framework.TestCase;
-
-/**
- * TODO: Remove this. This is only a placeholder, need to implement this.
- */
-public class ServiceManagerPermissionTests extends TestCase {
-    @SmallTest
-    public void testAddService() {
-        try {
-            // The security in the service manager is that you can't replace
-            // a service that is already published.
-            Binder binder = new Binder();
-            ServiceManager.addService("activity", binder);
-            fail("ServiceManager.addService did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    @SmallTest
-    public void testSetPermissionController() {
-        try {
-            IPermissionController pc = new IPermissionController.Stub() {
-                @Override
-                public boolean checkPermission(java.lang.String permission, int pid, int uid) {
-                    return true;
-                }
-
-                @Override
-                public int noteOp(String op, int uid, String packageName) {
-                    return AppOpsManager.MODE_ALLOWED;
-                }
-
-                @Override
-                public String[] getPackagesForUid(int uid) {
-                    return new String[0];
-                }
-
-                @Override
-                public boolean isRuntimePermission(String permission) {
-                    return false;
-                }
-
-                @Override
-                public int getPackageUid(String packageName, int flags) {
-                    return -1;
-                }
-            };
-            ServiceManagerNative.asInterface(BinderInternal.getContextObject())
-                    .setPermissionController(pc);
-            fail("IServiceManager.setPermissionController did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        } catch (RemoteException e) {
-            fail("Unexpected remote exception");
-        }
-    }
-}
diff --git a/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
index 273943f..4172743 100644
--- a/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
@@ -16,12 +16,12 @@
 
 package com.android.framework.permission.tests;
 
-import java.util.ArrayList;
-
 import android.telephony.SmsManager;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.ArrayList;
+
 /**
  * Verify that SmsManager apis cannot be called without required permissions.
  */
@@ -32,6 +32,10 @@
     private static final String DEST_NUMBER = "4567";
     private static final String SRC_NUMBER = "1234";
 
+    private static final int CELL_BROADCAST_MESSAGE_ID_START = 10;
+    private static final int CELL_BROADCAST_MESSAGE_ID_END = 20;
+    private static final int CELL_BROADCAST_GSM_RAN_TYPE = 0;
+
     /**
      * Verify that SmsManager.sendTextMessage requires permissions.
      * <p>Tests Permission:
@@ -82,4 +86,34 @@
             // expected
         }
     }
+
+    /**
+     * Verify that SmsManager.enableCellBroadcastRange requires permissions.
+     * <p>Tests system permission: android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
+     */
+    @SmallTest
+    public void testEnableCellBroadcastRange() {
+        try {
+            SmsManager.getDefault().enableCellBroadcastRange(CELL_BROADCAST_MESSAGE_ID_START,
+                    CELL_BROADCAST_MESSAGE_ID_END, CELL_BROADCAST_GSM_RAN_TYPE);
+            fail("SmsManager.sendDataMessage did not throw SecurityException as expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that SmsManager.disableCellBroadcastRange requires permissions.
+     * <p>Tests system permission: android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
+     */
+    @SmallTest
+    public void testDisableCellBroadcastRange() {
+        try {
+            SmsManager.getDefault().disableCellBroadcastRange(CELL_BROADCAST_MESSAGE_ID_START,
+                    CELL_BROADCAST_MESSAGE_ID_END, CELL_BROADCAST_GSM_RAN_TYPE);
+            fail("SmsManager.sendDataMessage did not throw SecurityException as expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
 }
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 550edd3..77ecf62 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -115,12 +115,9 @@
   using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
   IterationEnder iteration_ender(cookie, EndIteration);
 
-  ZipString zip_entry_name;
+  std::string zip_entry_path;
   ZipEntry zip_data;
-  while ((result = Next(cookie, &zip_data, &zip_entry_name)) == 0) {
-    std::string zip_entry_path =
-        std::string(reinterpret_cast<const char*>(zip_entry_name.name),
-                    zip_entry_name.name_length);
+  while ((result = Next(cookie, &zip_data, &zip_entry_path)) == 0) {
     std::string nested_path = path.to_string() + "@" + zip_entry_path;
     std::unique_ptr<IFile> file =
         util::make_unique<ZipFile>(collection->handle_, zip_data, Source(nested_path));
diff --git a/tests/net/util/Android.bp b/tools/dump-coverage/Android.bp
similarity index 65%
copy from tests/net/util/Android.bp
copy to tools/dump-coverage/Android.bp
index d8c502d..4519ce3 100644
--- a/tests/net/util/Android.bp
+++ b/tools/dump-coverage/Android.bp
@@ -14,17 +14,16 @@
 // limitations under the License.
 //
 
-// Common utilities for network tests.
-java_library {
-    name: "frameworks-net-testutils",
-    srcs: ["java/**/*.java"],
-    // test_current to be also appropriate for CTS tests
-    sdk_version: "test_current",
-    static_libs: [
-        "androidx.annotation_annotation",
-        "junit",
+// Build variants {target,host} x {32,64}
+cc_library {
+    name: "libdumpcoverage",
+    srcs: ["dump_coverage.cc"],
+    header_libs: [
+        "libopenjdkjvmti_headers",
     ],
-    libs: [
-        "android.test.base.stubs",
+
+    host_supported: true,
+    shared_libs: [
+        "libbase",
     ],
-}
\ No newline at end of file
+}
diff --git a/tools/dump-coverage/README.md b/tools/dump-coverage/README.md
new file mode 100644
index 0000000..d1c10bc
--- /dev/null
+++ b/tools/dump-coverage/README.md
@@ -0,0 +1,50 @@
+# dumpcoverage
+
+libdumpcoverage.so is a JVMTI agent designed to dump coverage information for a process, where the binaries have been instrumented by JaCoCo. JaCoCo automatically starts recording data on process start, and we need a way to trigger the resetting or dumping of this data.
+
+The JVMTI agent is used to make the calls to JaCoCo in its process.
+
+# Usage
+
+Note that these examples assume you have an instrumented build (userdebug_coverage). Here is, for example, how to dump coverage information regarding the default clock app. First some setup is necessary:
+
+```
+adb root # necessary to copy files in/out of the /data/data/{package} folder
+adb shell 'mkdir /data/data/com.android.deskclock/folder-to-use'
+```
+
+Then we can run the command to dump the data:
+
+```
+adb shell 'am attach-agent com.android.deskclock /system/lib/libdumpcoverage.so=dump:/data/data/com.android.deskclock/folder-to-use/coverage-file.ec'
+```
+
+We can also reset the coverage information with
+
+```
+adb shell 'am attach-agent com.android.deskclock /system/lib/libdumpcoverage.so=reset'
+```
+
+then perform more actions, then dump the data again. To get the files, we can get
+
+```
+adb pull /data/data/com.android.deskclock/folder-to-use/coverage-file.ec ~/path-on-your-computer
+```
+
+And you should have `coverage-file.ec` on your machine under the folder `~/path-on-your-computer`
+
+# Details
+
+In dump mode, the agent makes JNI calls equivalent to
+
+```
+Agent.getInstance().getExecutionData(/*reset = */ false);
+```
+
+and then saves the result to a file specified by the passed in directory
+
+In reset mode, it makes a JNI call equivalent to
+
+```
+Agent.getInstance().reset();
+```
diff --git a/tools/dump-coverage/dump_coverage.cc b/tools/dump-coverage/dump_coverage.cc
new file mode 100644
index 0000000..0808e77
--- /dev/null
+++ b/tools/dump-coverage/dump_coverage.cc
@@ -0,0 +1,205 @@
+// 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.
+//
+
+#include <android-base/logging.h>
+#include <jni.h>
+#include <jvmti.h>
+#include <string.h>
+
+#include <fstream>
+
+using std::get;
+using std::tuple;
+
+namespace dump_coverage {
+
+#define CHECK_JVMTI(x) CHECK_EQ((x), JVMTI_ERROR_NONE)
+#define CHECK_NOTNULL(x) CHECK((x) != nullptr)
+#define CHECK_NO_EXCEPTION(env) CHECK(!(env)->ExceptionCheck());
+
+static JavaVM* java_vm = nullptr;
+
+// Get the current JNI environment.
+static JNIEnv* GetJNIEnv() {
+  JNIEnv* env = nullptr;
+  CHECK_EQ(java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6),
+           JNI_OK);
+  return env;
+}
+
+// Get the JaCoCo Agent class and an instance of the class, given a JNI
+// environment.
+// Will crash if the Agent isn't found or if any Java Exception occurs.
+static tuple<jclass, jobject> GetJavaAgent(JNIEnv* env) {
+  jclass java_agent_class =
+      env->FindClass("org/jacoco/agent/rt/internal/Agent");
+  CHECK_NOTNULL(java_agent_class);
+
+  jmethodID java_agent_get_instance =
+      env->GetStaticMethodID(java_agent_class, "getInstance",
+                             "()Lorg/jacoco/agent/rt/internal/Agent;");
+  CHECK_NOTNULL(java_agent_get_instance);
+
+  jobject java_agent_instance =
+      env->CallStaticObjectMethod(java_agent_class, java_agent_get_instance);
+  CHECK_NO_EXCEPTION(env);
+  CHECK_NOTNULL(java_agent_instance);
+
+  return tuple(java_agent_class, java_agent_instance);
+}
+
+// Runs equivalent of Agent.getInstance().getExecutionData(false) and returns
+// the result.
+// Will crash if the Agent isn't found or if any Java Exception occurs.
+static jbyteArray GetExecutionData(JNIEnv* env) {
+  auto java_agent = GetJavaAgent(env);
+  jmethodID java_agent_get_execution_data =
+      env->GetMethodID(get<0>(java_agent), "getExecutionData", "(Z)[B");
+  CHECK_NO_EXCEPTION(env);
+  CHECK_NOTNULL(java_agent_get_execution_data);
+
+  jbyteArray java_result_array = (jbyteArray)env->CallObjectMethod(
+      get<1>(java_agent), java_agent_get_execution_data, false);
+  CHECK_NO_EXCEPTION(env);
+
+  return java_result_array;
+}
+
+// Writes the execution data to a file.
+//  data, length: represent the data, as a sequence of bytes.
+//  filename: file to write coverage data to.
+//  returns JNI_ERR if there is an error in writing the file, otherwise JNI_OK.
+static jint WriteFile(const char* data, int length, const std::string& filename) {
+  LOG(INFO) << "Writing file of length " << length << " to '" << filename
+            << "'";
+  std::ofstream file(filename, std::ios::binary);
+
+  if (!file.is_open()) {
+    LOG(ERROR) << "Could not open file: '" << filename << "'";
+    return JNI_ERR;
+  }
+  file.write(data, length);
+  file.close();
+
+  if (!file) {
+    LOG(ERROR) << "I/O error in reading file";
+    return JNI_ERR;
+  }
+
+  LOG(INFO) << "Done writing file";
+  return JNI_OK;
+}
+
+// Grabs execution data and writes it to a file.
+//  filename: file to write coverage data to.
+//  returns JNI_ERR if there is an error writing the file.
+// Will crash if the Agent isn't found or if any Java Exception occurs.
+static jint Dump(const std::string& filename) {
+  LOG(INFO) << "Dumping file";
+
+  JNIEnv* env = GetJNIEnv();
+  jbyteArray java_result_array = GetExecutionData(env);
+  CHECK_NOTNULL(java_result_array);
+
+  jbyte* result_ptr = env->GetByteArrayElements(java_result_array, 0);
+  CHECK_NOTNULL(result_ptr);
+
+  int result_len = env->GetArrayLength(java_result_array);
+
+  return WriteFile((const char*) result_ptr, result_len, filename);
+}
+
+// Resets execution data, performing the equivalent of
+//  Agent.getInstance().reset();
+//  args: should be empty.
+//  returns JNI_ERR if the arguments are invalid.
+// Will crash if the Agent isn't found or if any Java Exception occurs.
+static jint Reset(const std::string& args) {
+  if (args != "") {
+    LOG(ERROR) << "reset takes no arguments, but received '" << args << "'";
+    return JNI_ERR;
+  }
+
+  JNIEnv* env = GetJNIEnv();
+  auto java_agent = GetJavaAgent(env);
+
+  jmethodID java_agent_reset =
+      env->GetMethodID(get<0>(java_agent), "reset", "()V");
+  CHECK_NOTNULL(java_agent_reset);
+
+  env->CallVoidMethod(get<1>(java_agent), java_agent_reset);
+  CHECK_NO_EXCEPTION(env);
+  return JNI_OK;
+}
+
+// Given a string of the form "<a>:<b>" returns (<a>, <b>).
+// Given a string <a> that doesn't contain a colon, returns (<a>, "").
+static tuple<std::string, std::string> SplitOnColon(const std::string& options) {
+  size_t loc_delim = options.find(':');
+  std::string command, args;
+
+  if (loc_delim == std::string::npos) {
+    command = options;
+  } else {
+    command = options.substr(0, loc_delim);
+    args = options.substr(loc_delim + 1, options.length());
+  }
+  return tuple(command, args);
+}
+
+// Parses and executes a command specified by options of the form
+// "<command>:<args>" where <command> is either "dump" or "reset".
+static jint ParseOptionsAndExecuteCommand(const std::string& options) {
+  auto split = SplitOnColon(options);
+  auto command = get<0>(split), args = get<1>(split);
+
+  LOG(INFO) << "command: '" << command << "' args: '" << args << "'";
+
+  if (command == "dump") {
+    return Dump(args);
+  }
+
+  if (command == "reset") {
+    return Reset(args);
+  }
+
+  LOG(ERROR) << "Invalid command: expected 'dump' or 'reset' but was '"
+             << command << "'";
+  return JNI_ERR;
+}
+
+static jint AgentStart(JavaVM* vm, char* options) {
+  android::base::InitLogging(/* argv= */ nullptr);
+  java_vm = vm;
+
+  return ParseOptionsAndExecuteCommand(options);
+}
+
+// Late attachment (e.g. 'am attach-agent').
+extern "C" JNIEXPORT jint JNICALL
+Agent_OnAttach(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) {
+  return AgentStart(vm, options);
+}
+
+// Early attachment.
+extern "C" JNIEXPORT jint JNICALL
+Agent_OnLoad(JavaVM* jvm ATTRIBUTE_UNUSED, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
+  LOG(ERROR)
+    << "The dumpcoverage agent will not work on load,"
+    << " as it does not have access to the runtime.";
+  return JNI_ERR;
+}
+
+}  // namespace dump_coverage
diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp
index c54e5b5..79dce4a 100644
--- a/tools/lock_agent/Android.bp
+++ b/tools/lock_agent/Android.bp
@@ -12,11 +12,9 @@
     ],
     sdk_version: "current",
     stl: "c++_static",
-    include_dirs: [
-        // NDK headers aren't available in platform NDK builds.
-        "libnativehelper/include_jni",
-    ],
     header_libs: [
+        // Use ScopedUtfChars.
+        "libnativehelper_header_only",
         "libopenjdkjvmti_headers",
     ],
     compile_multilib: "both",
@@ -30,11 +28,9 @@
         "libz",
         "slicer",
     ],
-    include_dirs: [
-        // NDK headers aren't available in platform NDK builds.
-        "libnativehelper/include_jni",
-    ],
     header_libs: [
+        // Use ScopedUtfChars.
+        "libnativehelper_header_only",
         "libopenjdkjvmti_headers",
     ],
 }
@@ -51,11 +47,22 @@
     installable: true,
 }
 
+cc_binary {
+    name: "lockagent_crasher",
+    srcs: ["crasher.cpp"],
+    static_libs: ["libbase_ndk"],
+    shared_libs: ["liblog"],
+    sdk_version: "current",
+    stl: "c++_static",
+    compile_multilib: "first",
+}
+
 sh_binary {
     name: "start_with_lockagent",
     src: "start_with_lockagent.sh",
     required: [
         "liblockagent",
         "lockagent",
+        "lockagent_crasher",
     ],
 }
diff --git a/tools/lock_agent/agent.cpp b/tools/lock_agent/agent.cpp
index 59bfa2b..40293b6 100644
--- a/tools/lock_agent/agent.cpp
+++ b/tools/lock_agent/agent.cpp
@@ -19,6 +19,8 @@
 #include <memory>
 #include <sstream>
 
+#include <unistd.h>
+
 #include <jni.h>
 
 #include <jvmti.h>
@@ -26,10 +28,14 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/macros.h>
+#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 
 #include <fcntl.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <nativehelper/scoped_utf_chars.h>
 
 // We need dladdr.
 #if !defined(__APPLE__) && !defined(_WIN32)
@@ -61,6 +67,8 @@
 namespace {
 
 JavaVM* gJavaVM = nullptr;
+bool gForkCrash = false;
+bool gJavaCrash = false;
 
 // Converts a class name to a type descriptor
 // (ex. "java.lang.String" to "Ljava/lang/String;")
@@ -77,118 +85,143 @@
 using namespace dex;
 using namespace lir;
 
-bool transform(std::shared_ptr<ir::DexFile> dexIr) {
-    bool modified = false;
+class Transformer {
+public:
+    explicit Transformer(std::shared_ptr<ir::DexFile> dexIr) : dexIr_(dexIr) {}
 
-    std::unique_ptr<ir::Builder> builder;
+    bool transform() {
+        bool classModified = false;
 
-    for (auto& method : dexIr->encoded_methods) {
-        // Do not look into abstract/bridge/native/synthetic methods.
-        if ((method->access_flags & (kAccAbstract | kAccBridge | kAccNative | kAccSynthetic))
-                != 0) {
-            continue;
+        std::unique_ptr<ir::Builder> builder;
+
+        for (auto& method : dexIr_->encoded_methods) {
+            // Do not look into abstract/bridge/native/synthetic methods.
+            if ((method->access_flags & (kAccAbstract | kAccBridge | kAccNative | kAccSynthetic))
+                    != 0) {
+                continue;
+            }
+
+            struct HookVisitor: public Visitor {
+                HookVisitor(Transformer* transformer, CodeIr* c_ir)
+                        : transformer(transformer), cIr(c_ir) {
+                }
+
+                bool Visit(Bytecode* bytecode) override {
+                    if (bytecode->opcode == OP_MONITOR_ENTER) {
+                        insertHook(bytecode, true,
+                                reinterpret_cast<VReg*>(bytecode->operands[0])->reg);
+                        return true;
+                    }
+                    if (bytecode->opcode == OP_MONITOR_EXIT) {
+                        insertHook(bytecode, false,
+                                reinterpret_cast<VReg*>(bytecode->operands[0])->reg);
+                        return true;
+                    }
+                    return false;
+                }
+
+                void insertHook(lir::Instruction* before, bool pre, u4 reg) {
+                    transformer->preparePrePost();
+                    transformer->addCall(cIr, before, OP_INVOKE_STATIC_RANGE,
+                            transformer->hookType_, pre ? "preLock" : "postLock",
+                            transformer->voidType_, transformer->objectType_, reg);
+                    myModified = true;
+                }
+
+                Transformer* transformer;
+                CodeIr* cIr;
+                bool myModified = false;
+            };
+
+            CodeIr c(method.get(), dexIr_);
+            bool methodModified = false;
+
+            HookVisitor visitor(this, &c);
+            for (auto it = c.instructions.begin(); it != c.instructions.end(); ++it) {
+                lir::Instruction* fi = *it;
+                fi->Accept(&visitor);
+            }
+            methodModified |= visitor.myModified;
+
+            if (methodModified) {
+                classModified = true;
+                c.Assemble();
+            }
         }
 
-        struct HookVisitor: public Visitor {
-            HookVisitor(std::unique_ptr<ir::Builder>* b, std::shared_ptr<ir::DexFile> d_ir,
-                    CodeIr* c_ir) :
-                    b(b), dIr(d_ir), cIr(c_ir) {
-            }
+        return classModified;
+    }
 
-            bool Visit(Bytecode* bytecode) override {
-                if (bytecode->opcode == OP_MONITOR_ENTER) {
-                    prepare();
-                    addCall(bytecode, OP_INVOKE_STATIC_RANGE, hookType, "preLock", voidType,
-                            objectType, reinterpret_cast<VReg*>(bytecode->operands[0])->reg);
-                    myModified = true;
-                    return true;
-                }
-                if (bytecode->opcode == OP_MONITOR_EXIT) {
-                    prepare();
-                    addCall(bytecode, OP_INVOKE_STATIC_RANGE, hookType, "postLock", voidType,
-                            objectType, reinterpret_cast<VReg*>(bytecode->operands[0])->reg);
-                    myModified = true;
-                    return true;
-                }
-                return false;
-            }
+private:
+    void preparePrePost() {
+        // Insert "void LockHook.(pre|post)(Object o)."
 
-            void prepare() {
-                if (*b == nullptr) {
-                    *b = std::unique_ptr<ir::Builder>(new ir::Builder(dIr));
-                }
-                if (voidType == nullptr) {
-                    voidType = (*b)->GetType("V");
-                    hookType = (*b)->GetType("Lcom/android/lock_checker/LockHook;");
-                    objectType = (*b)->GetType("Ljava/lang/Object;");
-                }
-            }
+        prepareBuilder();
 
-            void addInst(lir::Instruction* instructionAfter, Opcode opcode,
-                    const std::list<Operand*>& operands) {
-                auto instruction = cIr->Alloc<Bytecode>();
-
-                instruction->opcode = opcode;
-
-                for (auto it = operands.begin(); it != operands.end(); it++) {
-                    instruction->operands.push_back(*it);
-                }
-
-                cIr->instructions.InsertBefore(instructionAfter, instruction);
-            }
-
-            void addCall(lir::Instruction* instructionAfter, Opcode opcode, ir::Type* type,
-                    const char* methodName, ir::Type* returnType,
-                    const std::vector<ir::Type*>& types, const std::list<int>& regs) {
-                auto proto = (*b)->GetProto(returnType, (*b)->GetTypeList(types));
-                auto method = (*b)->GetMethodDecl((*b)->GetAsciiString(methodName), proto, type);
-
-                VRegList* paramRegs = cIr->Alloc<VRegList>();
-                for (auto it = regs.begin(); it != regs.end(); it++) {
-                    paramRegs->registers.push_back(*it);
-                }
-
-                addInst(instructionAfter, opcode,
-                        { paramRegs, cIr->Alloc<Method>(method, method->orig_index) });
-            }
-
-            void addCall(lir::Instruction* instructionAfter, Opcode opcode, ir::Type* type,
-                    const char* methodName, ir::Type* returnType, ir::Type* paramType,
-                    u4 paramVReg) {
-                auto proto = (*b)->GetProto(returnType, (*b)->GetTypeList( { paramType }));
-                auto method = (*b)->GetMethodDecl((*b)->GetAsciiString(methodName), proto, type);
-
-                VRegRange* args = cIr->Alloc<VRegRange>(paramVReg, 1);
-
-                addInst(instructionAfter, opcode,
-                        { args, cIr->Alloc<Method>(method, method->orig_index) });
-            }
-
-            std::unique_ptr<ir::Builder>* b;
-            std::shared_ptr<ir::DexFile> dIr;
-            CodeIr* cIr;
-            ir::Type* voidType = nullptr;
-            ir::Type* hookType = nullptr;
-            ir::Type* objectType = nullptr;
-            bool myModified = false;
-        };
-
-        CodeIr c(method.get(), dexIr);
-        HookVisitor visitor(&builder, dexIr, &c);
-
-        for (auto it = c.instructions.begin(); it != c.instructions.end(); ++it) {
-            lir::Instruction* fi = *it;
-            fi->Accept(&visitor);
+        if (voidType_ == nullptr) {
+            voidType_ = builder_->GetType("V");
         }
-
-        if (visitor.myModified) {
-            modified = true;
-            c.Assemble();
+        if (hookType_ == nullptr) {
+            hookType_ = builder_->GetType("Lcom/android/lock_checker/LockHook;");
+        }
+        if (objectType_ == nullptr) {
+            objectType_ = builder_->GetType("Ljava/lang/Object;");
         }
     }
 
-    return modified;
-}
+    void prepareBuilder() {
+        if (builder_ == nullptr) {
+            builder_ = std::unique_ptr<ir::Builder>(new ir::Builder(dexIr_));
+        }
+    }
+
+    static void addInst(CodeIr* cIr, lir::Instruction* instructionAfter, Opcode opcode,
+            const std::list<Operand*>& operands) {
+        auto instruction = cIr->Alloc<Bytecode>();
+
+        instruction->opcode = opcode;
+
+        for (auto it = operands.begin(); it != operands.end(); it++) {
+            instruction->operands.push_back(*it);
+        }
+
+        cIr->instructions.InsertBefore(instructionAfter, instruction);
+    }
+
+    void addCall(CodeIr* cIr, lir::Instruction* instructionAfter, Opcode opcode, ir::Type* type,
+            const char* methodName, ir::Type* returnType,
+            const std::vector<ir::Type*>& types, const std::list<int>& regs) {
+        auto proto = builder_->GetProto(returnType, builder_->GetTypeList(types));
+        auto method = builder_->GetMethodDecl(builder_->GetAsciiString(methodName), proto, type);
+
+        VRegList* paramRegs = cIr->Alloc<VRegList>();
+        for (auto it = regs.begin(); it != regs.end(); it++) {
+            paramRegs->registers.push_back(*it);
+        }
+
+        addInst(cIr, instructionAfter, opcode,
+                { paramRegs, cIr->Alloc<Method>(method, method->orig_index) });
+    }
+
+    void addCall(CodeIr* cIr, lir::Instruction* instructionAfter, Opcode opcode, ir::Type* type,
+            const char* methodName, ir::Type* returnType, ir::Type* paramType,
+            u4 paramVReg) {
+        auto proto = builder_->GetProto(returnType, builder_->GetTypeList( { paramType }));
+        auto method = builder_->GetMethodDecl(builder_->GetAsciiString(methodName), proto, type);
+
+        VRegRange* args = cIr->Alloc<VRegRange>(paramVReg, 1);
+
+        addInst(cIr, instructionAfter, opcode,
+                { args, cIr->Alloc<Method>(method, method->orig_index) });
+    }
+
+    std::shared_ptr<ir::DexFile> dexIr_;
+    std::unique_ptr<ir::Builder> builder_;
+
+    ir::Type* voidType_ = nullptr;
+    ir::Type* hookType_ = nullptr;
+    ir::Type* objectType_ = nullptr;
+};
 
 std::pair<dex::u1*, size_t> maybeTransform(const char* name, size_t classDataLen,
         const unsigned char* classData, dex::Writer::Allocator* allocator) {
@@ -201,8 +234,11 @@
     reader.CreateClassIr(index);
     std::shared_ptr<ir::DexFile> ir = reader.GetIr();
 
-    if (!transform(ir)) {
-        return std::make_pair(nullptr, 0);
+    {
+        Transformer transformer(ir);
+        if (!transformer.transform()) {
+            return std::make_pair(nullptr, 0);
+        }
     }
 
     size_t new_size;
@@ -372,7 +408,7 @@
     }
 }
 
-jint attach(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
+jint attach(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) {
     gJavaVM = vm;
 
     jvmtiEnv* env;
@@ -383,9 +419,66 @@
 
     prepareHook(env);
 
+    std::vector<std::string> config = android::base::Split(options, ",");
+    for (const std::string& c : config) {
+        if (c == "native_crash") {
+            gForkCrash = true;
+        } else if (c == "java_crash") {
+            gJavaCrash = true;
+        }
+    }
+
     return JVMTI_ERROR_NONE;
 }
 
+extern "C" JNIEXPORT
+jboolean JNICALL Java_com_android_lock_1checker_LockHook_getNativeHandlingConfig(JNIEnv*, jclass) {
+    return gForkCrash ? JNI_TRUE : JNI_FALSE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_android_lock_1checker_LockHook_getSimulateCrashConfig(JNIEnv*, jclass) {
+    return gJavaCrash ? JNI_TRUE : JNI_FALSE;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_lock_1checker_LockHook_nWtf(JNIEnv* env, jclass,
+        jstring msg) {
+    if (!gForkCrash || msg == nullptr) {
+        return;
+    }
+
+    // Create a native crash with the given message. Decouple from the current crash to create a
+    // tombstone but continue on.
+    //
+    // TODO: Once there are not so many reports, consider making this fatal for the calling process.
+    ScopedUtfChars utf(env, msg);
+    if (utf.c_str() == nullptr) {
+        return;
+    }
+    const char* args[] = {
+        "/system/bin/lockagent_crasher",
+        utf.c_str(),
+        nullptr
+    };
+    pid_t pid = fork();
+    if (pid < 0) {
+        return;
+    }
+    if (pid == 0) {
+        // Double fork so we return quickly. Leave init to deal with the zombie.
+        pid_t pid2 = fork();
+        if (pid2 == 0) {
+            execv(args[0], const_cast<char* const*>(args));
+            _exit(1);
+            __builtin_unreachable();
+        }
+        _exit(0);
+        __builtin_unreachable();
+    }
+    int status;
+    waitpid(pid, &status, 0);  // Ignore any results.
+}
+
 extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
     return attach(vm, options, reserved);
 }
diff --git a/tools/lock_agent/crasher.cpp b/tools/lock_agent/crasher.cpp
new file mode 100644
index 0000000..2829d6d
--- /dev/null
+++ b/tools/lock_agent/crasher.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+
+// Simple binary that will just crash with the message given as the first parameter.
+//
+// This is helpful in cases the caller does not want to crash itself, e.g., fork+crash
+// instead, as LOG(FATAL) might not be safe (for example in a multi-threaded environment).
+int main(int argc, char *argv[]) {
+    if (argc != 2) {
+        LOG(FATAL) << "Need one argument for abort message";
+        __builtin_unreachable();
+    }
+    LOG(FATAL) << argv[1];
+    __builtin_unreachable();
+}
diff --git a/tools/lock_agent/java/com/android/lock_checker/LockHook.java b/tools/lock_agent/java/com/android/lock_checker/LockHook.java
index 95b3181..35c75cb 100644
--- a/tools/lock_agent/java/com/android/lock_checker/LockHook.java
+++ b/tools/lock_agent/java/com/android/lock_checker/LockHook.java
@@ -16,6 +16,7 @@
 
 package com.android.lock_checker;
 
+import android.app.ActivityThread;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -24,6 +25,7 @@
 import android.util.Log;
 import android.util.LogWriter;
 
+import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.StatLogger;
 
@@ -66,19 +68,29 @@
 
     static final StatLogger sStats = new StatLogger(new String[] { "on-thread", });
 
-    private static final ConcurrentLinkedQueue<Object> sViolations = new ConcurrentLinkedQueue<>();
+    private static final ConcurrentLinkedQueue<Violation> sViolations =
+            new ConcurrentLinkedQueue<>();
     private static final int MAX_VIOLATIONS = 50;
 
     private static final LockChecker[] sCheckers;
 
+    private static boolean sNativeHandling = false;
+    private static boolean sSimulateCrash = false;
+
     static {
         sHandlerThread = new HandlerThread("LockHook:wtf", Process.THREAD_PRIORITY_BACKGROUND);
         sHandlerThread.start();
         sHandler = new WtfHandler(sHandlerThread.getLooper());
 
         sCheckers = new LockChecker[] { new OnThreadLockChecker() };
+
+        sNativeHandling = getNativeHandlingConfig();
+        sSimulateCrash = getSimulateCrashConfig();
     }
 
+    private static native boolean getNativeHandlingConfig();
+    private static native boolean getSimulateCrashConfig();
+
     static <T> boolean shouldDumpStacktrace(StacktraceHasher hasher, Map<String, T> dumpedSet,
             T val, AnnotatedStackTraceElement[] st, int from, int to) {
         final String stacktraceHash = hasher.stacktraceHash(st, from, to);
@@ -101,8 +113,8 @@
         }
     }
 
-    static void wtf(String message) {
-        sHandler.wtf(message);
+    static void wtf(Violation v) {
+        sHandler.wtf(v);
     }
 
     static void doCheckOnThisThread(boolean check) {
@@ -151,10 +163,10 @@
             super(looper);
         }
 
-        public void wtf(String msg) {
+        public void wtf(Violation v) {
             sDoCheck.set(false);
             SomeArgs args = SomeArgs.obtain();
-            args.arg1 = msg;
+            args.arg1 = v;
             obtainMessage(MSG_WTF, args).sendToTarget();
             sDoCheck.set(true);
         }
@@ -164,13 +176,29 @@
             switch (msg.what) {
                 case MSG_WTF:
                     SomeArgs args = (SomeArgs) msg.obj;
-                    Log.wtf(TAG, (String) args.arg1);
+                    handleViolation((Violation) args.arg1);
                     args.recycle();
                     break;
             }
         }
     }
 
+    private static void handleViolation(Violation v) {
+        String msg = v.toString();
+        Log.wtf(TAG, msg);
+        if (sNativeHandling) {
+            nWtf(msg);  // Also send to native.
+        }
+        if (sSimulateCrash) {
+            RuntimeInit.logUncaught("LockAgent",
+                    ActivityThread.isSystem() ? "system_server"
+                            : ActivityThread.currentProcessName(),
+                    Process.myPid(), v.getException());
+        }
+    }
+
+    private static native void nWtf(String msg);
+
     /**
      * Generates a hash for a given stacktrace of a {@link Throwable}.
      */
@@ -224,8 +252,10 @@
         }
     }
 
-    static void addViolation(Object o) {
-        sViolations.offer(o);
+    static void addViolation(Violation v) {
+        wtf(v);
+
+        sViolations.offer(v);
         while (sViolations.size() > MAX_VIOLATIONS) {
             sViolations.poll();
         }
@@ -287,4 +317,8 @@
 
         void dump(PrintWriter pw);
     }
+
+    interface Violation {
+        Throwable getException();
+    }
 }
diff --git a/tools/lock_agent/java/com/android/lock_checker/OnThreadLockChecker.java b/tools/lock_agent/java/com/android/lock_checker/OnThreadLockChecker.java
index 0f3a285..e74ccf9 100644
--- a/tools/lock_agent/java/com/android/lock_checker/OnThreadLockChecker.java
+++ b/tools/lock_agent/java/com/android/lock_checker/OnThreadLockChecker.java
@@ -220,7 +220,7 @@
         heldLocks.remove(index);
     }
 
-    private static class Violation {
+    private static class Violation implements LockHook.Violation {
         int mSelfTid;
         String mSelfName;
         Object mAlreadyHeld;
@@ -228,6 +228,8 @@
         AnnotatedStackTraceElement[] mStack;
         OrderData mOppositeData;
 
+        private static final int STACK_OFFSET = 4;
+
         Violation(Thread self, Object alreadyHeld, Object lock,
                 AnnotatedStackTraceElement[] stack, OrderData oppositeData) {
             this.mSelfTid = (int) self.getId();
@@ -284,6 +286,26 @@
                     lock.getClass().getName());
         }
 
+        // Synthesize an exception.
+        public Throwable getException() {
+            RuntimeException inner = new RuntimeException("Previously locked");
+            inner.setStackTrace(synthesizeStackTrace(mOppositeData.mStack));
+
+            RuntimeException outer = new RuntimeException(toString(), inner);
+            outer.setStackTrace(synthesizeStackTrace(mStack));
+
+            return outer;
+        }
+
+        private StackTraceElement[] synthesizeStackTrace(AnnotatedStackTraceElement[] stack) {
+
+            StackTraceElement[] out = new StackTraceElement[stack.length - STACK_OFFSET];
+            for (int i = 0; i < out.length; i++) {
+                out[i] = stack[i + STACK_OFFSET].getStackTraceElement();
+            }
+            return out;
+        }
+
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("Lock inversion detected!\n");
@@ -294,7 +316,7 @@
             sb.append(" on thread ").append(mOppositeData.mTid).append(" (")
                     .append(mOppositeData.mThreadName).append(")");
             sb.append(" at:\n");
-            sb.append(getAnnotatedStackString(mOppositeData.mStack, 4,
+            sb.append(getAnnotatedStackString(mOppositeData.mStack, STACK_OFFSET,
                     describeLocking(mAlreadyHeld, "will lock"), getTo(mOppositeData.mStack, mLock)
                     + 1, "    | "));
             sb.append("  Locking ");
@@ -303,7 +325,8 @@
             sb.append(describeLock(mLock));
             sb.append(" on thread ").append(mSelfTid).append(" (").append(mSelfName).append(")");
             sb.append(" at:\n");
-            sb.append(getAnnotatedStackString(mStack, 4, describeLocking(mLock, "will lock"),
+            sb.append(getAnnotatedStackString(mStack, STACK_OFFSET,
+                    describeLocking(mLock, "will lock"),
                     getTo(mStack, mAlreadyHeld) + 1, "    | "));
 
             return sb.toString();
@@ -323,7 +346,6 @@
         if (LockHook.shouldDumpStacktrace(mStacktraceHasher.get(), mDumpedStacktraceHashes,
                 Boolean.TRUE, v.mStack, 0, to)) {
             mNumDetectedUnique.incrementAndGet();
-            LockHook.wtf(v.toString());
             LockHook.addViolation(v);
         }
     }
diff --git a/tools/lock_agent/start_with_lockagent.sh b/tools/lock_agent/start_with_lockagent.sh
index 9539222..70ed5c5 100755
--- a/tools/lock_agent/start_with_lockagent.sh
+++ b/tools/lock_agent/start_with_lockagent.sh
@@ -1,5 +1,13 @@
 #!/system/bin/sh
+
+AGENT_OPTIONS=
+if [[ "$1" == --agent-options ]] ; then
+  shift
+  AGENT_OPTIONS="=$1"
+  shift
+fi
+
 APP=$1
 shift
-$APP -Xplugin:libopenjdkjvmti.so -agentpath:liblockagent.so $@
 
+$APP -Xplugin:libopenjdkjvmti.so "-agentpath:liblockagent.so$AGENT_OPTIONS" $@
diff --git a/tools/preload-check/Android.bp b/tools/preload-check/Android.bp
index 2488341..87b31d2 100644
--- a/tools/preload-check/Android.bp
+++ b/tools/preload-check/Android.bp
@@ -19,4 +19,5 @@
     libs: ["tradefed"],
     test_suites: ["general-tests"],
     required: ["preload-check-device"],
+    data: [":preload-check-device"],
 }
diff --git a/tools/preload-check/device/Android.bp b/tools/preload-check/device/Android.bp
index 7782b0d..f40d8ba 100644
--- a/tools/preload-check/device/Android.bp
+++ b/tools/preload-check/device/Android.bp
@@ -20,7 +20,6 @@
 
     sdk_version: "current",
     srcs: ["src/**/*.java"],
-    test_suites: ["general-tests"],
     dex_preopt: {
         enabled: false,
     },
diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk
deleted file mode 100644
index d3ee1d3..0000000
--- a/tools/preload2/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-
-# To connect to devices (and take hprof dumps).
-LOCAL_STATIC_JAVA_LIBRARIES := ddmlib-prebuilt tools-common-prebuilt
-
-# To process hprof dumps.
-LOCAL_STATIC_JAVA_LIBRARIES += perflib-prebuilt trove-prebuilt guavalib
-
-# For JDWP access we use the framework in the JDWP tests from Apache Harmony, for
-# convenience (and to not depend on internal JDK APIs).
-LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit-host
-
-LOCAL_MODULE:= preload2
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-# Copy to build artifacts
-$(call dist-for-goals,dist_files,$(LOCAL_BUILT_MODULE):$(LOCAL_MODULE).jar)
-
-# Copy the preload-tool shell script to the host's bin directory.
-include $(CLEAR_VARS)
-LOCAL_IS_HOST_MODULE := true
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MODULE := preload-tool
-LOCAL_SRC_FILES := preload-tool
-LOCAL_REQUIRED_MODULES := preload2
-include $(BUILD_PREBUILT)
diff --git a/tools/preload2/preload-tool b/tools/preload2/preload-tool
deleted file mode 100644
index 322b62f..0000000
--- a/tools/preload2/preload-tool
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This script is used on the host only. It uses a common subset
-# shell dialect that should work well. It is partially derived
-# from art/tools/art.
-
-function follow_links() {
-  if [ z"$BASH_SOURCE" != z ]; then
-    file="$BASH_SOURCE"
-  else
-    file="$0"
-  fi
-  while [ -h "$file" ]; do
-    # On Mac OS, readlink -f doesn't work.
-    file="$(readlink "$file")"
-  done
-  echo "$file"
-}
-
-
-PROG_NAME="$(follow_links)"
-PROG_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
-ANDROID_ROOT=$PROG_DIR/..
-
-java -cp $ANDROID_ROOT/framework/preload2.jar com.android.preload.Main $@
diff --git a/tools/preload2/src/com/android/preload/ClientUtils.java b/tools/preload2/src/com/android/preload/ClientUtils.java
deleted file mode 100644
index 71ef025..0000000
--- a/tools/preload2/src/com/android/preload/ClientUtils.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload;
-
-import com.android.ddmlib.AndroidDebugBridge;
-import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-
-/**
- * Helper class for common communication with a Client (the ddms name for a running application).
- *
- * Instances take a default timeout parameter that's applied to all functions without explicit
- * timeout. Timeouts are in milliseconds.
- */
-public class ClientUtils {
-
-    private int defaultTimeout;
-
-    public ClientUtils() {
-        this(10000);
-    }
-
-    public ClientUtils(int defaultTimeout) {
-        this.defaultTimeout = defaultTimeout;
-    }
-
-    /**
-     * Shortcut for findClient with default timeout.
-     */
-    public Client findClient(IDevice device, String processName, int processPid) {
-        return findClient(device, processName, processPid, defaultTimeout);
-    }
-
-    /**
-     * Find the client with the given process name or process id. The name takes precedence over
-     * the process id (if valid). Stop looking after the given timeout.
-     *
-     * @param device The device to communicate with.
-     * @param processName The name of the process. May be null.
-     * @param processPid The pid of the process. Values less than or equal to zero are ignored.
-     * @param timeout The amount of milliseconds to wait, at most.
-     * @return The client, if found. Otherwise null.
-     */
-    public Client findClient(IDevice device, String processName, int processPid, int timeout) {
-        WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout);
-        return wfc.get();
-    }
-
-    /**
-     * Shortcut for findAllClients with default timeout.
-     */
-    public Client[] findAllClients(IDevice device) {
-        return findAllClients(device, defaultTimeout);
-    }
-
-    /**
-     * Retrieve all clients known to the given device. Wait at most the given timeout.
-     *
-     * @param device The device to investigate.
-     * @param timeout The amount of milliseconds to wait, at most.
-     * @return An array of clients running on the given device. May be null depending on the
-     *         device implementation.
-     */
-    public Client[] findAllClients(IDevice device, int timeout) {
-        if (device.hasClients()) {
-            return device.getClients();
-        }
-        WaitForClients wfc = new WaitForClients(device, timeout);
-        return wfc.get();
-    }
-
-    private static class WaitForClient implements IClientChangeListener {
-
-        private IDevice device;
-        private String processName;
-        private int processPid;
-        private long timeout;
-        private Client result;
-
-        public WaitForClient(IDevice device, String processName, int processPid, long timeout) {
-            this.device = device;
-            this.processName = processName;
-            this.processPid = processPid;
-            this.timeout = timeout;
-            this.result = null;
-        }
-
-        public Client get() {
-            synchronized (this) {
-                AndroidDebugBridge.addClientChangeListener(this);
-
-                // Maybe it's already there.
-                if (result == null) {
-                    result = searchForClient(device);
-                }
-
-                if (result == null) {
-                    try {
-                        wait(timeout);
-                    } catch (InterruptedException e) {
-                        // Note: doesn't guard for spurious wakeup.
-                    }
-                }
-            }
-
-            AndroidDebugBridge.removeClientChangeListener(this);
-            return result;
-        }
-
-        private Client searchForClient(IDevice device) {
-            if (processName != null) {
-                Client tmp = device.getClient(processName);
-                if (tmp != null) {
-                    return tmp;
-                }
-            }
-            if (processPid > 0) {
-                String name = device.getClientName(processPid);
-                if (name != null && !name.isEmpty()) {
-                    Client tmp = device.getClient(name);
-                    if (tmp != null) {
-                        return tmp;
-                    }
-                }
-            }
-            if (processPid > 0) {
-                // Try manual search.
-                for (Client cl : device.getClients()) {
-                    if (cl.getClientData().getPid() == processPid
-                            && cl.getClientData().getClientDescription() != null) {
-                        return cl;
-                    }
-                }
-            }
-            return null;
-        }
-
-        private boolean isTargetClient(Client c) {
-            if (processPid > 0 && c.getClientData().getPid() == processPid) {
-                return true;
-            }
-            if (processName != null
-                    && processName.equals(c.getClientData().getClientDescription())) {
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public void clientChanged(Client arg0, int arg1) {
-            synchronized (this) {
-                if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
-                    if (isTargetClient(arg0)) {
-                        result = arg0;
-                        notifyAll();
-                    }
-                }
-            }
-        }
-    }
-
-    private static class WaitForClients implements IClientChangeListener {
-
-        private IDevice device;
-        private long timeout;
-
-        public WaitForClients(IDevice device, long timeout) {
-            this.device = device;
-            this.timeout = timeout;
-        }
-
-        public Client[] get() {
-            synchronized (this) {
-                AndroidDebugBridge.addClientChangeListener(this);
-
-                if (device.hasClients()) {
-                    return device.getClients();
-                }
-
-                try {
-                    wait(timeout); // Note: doesn't guard for spurious wakeup.
-                } catch (InterruptedException exc) {
-                }
-
-                // We will be woken up when the first client data arrives. Sleep a little longer
-                // to give (hopefully all of) the rest of the clients a chance to become available.
-                // Note: a loop with timeout is brittle as well and complicated, just accept this
-                //       for now.
-                try {
-                    Thread.sleep(500);
-                } catch (InterruptedException exc) {
-                }
-            }
-
-            AndroidDebugBridge.removeClientChangeListener(this);
-
-            return device.getClients();
-        }
-
-        @Override
-        public void clientChanged(Client arg0, int arg1) {
-            synchronized (this) {
-                if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
-                    notifyAll();
-                }
-            }
-        }
-    }
-}
diff --git a/tools/preload2/src/com/android/preload/DeviceUtils.java b/tools/preload2/src/com/android/preload/DeviceUtils.java
deleted file mode 100644
index 18cab7b..0000000
--- a/tools/preload2/src/com/android/preload/DeviceUtils.java
+++ /dev/null
@@ -1,420 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload;
-
-import com.android.ddmlib.AdbCommandRejectedException;
-import com.android.ddmlib.AndroidDebugBridge;
-import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
-import com.android.preload.classdataretrieval.hprof.Hprof;
-import com.android.ddmlib.DdmPreferences;
-import com.android.ddmlib.IDevice;
-import com.android.ddmlib.IShellOutputReceiver;
-import com.android.ddmlib.SyncException;
-import com.android.ddmlib.TimeoutException;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class for some device routines.
- */
-public class DeviceUtils {
-
-  // Locations
-  private static final String PRELOADED_CLASSES_FILE = "/etc/preloaded-classes";
-  // Shell commands
-  private static final String CREATE_EMPTY_PRELOADED_CMD = "touch " + PRELOADED_CLASSES_FILE;
-  private static final String DELETE_CACHE_CMD = "rm /data/dalvik-cache/*/*boot.art";
-  private static final String DELETE_PRELOADED_CMD = "rm " + PRELOADED_CLASSES_FILE;
-  private static final String READ_PRELOADED_CMD = "cat " + PRELOADED_CLASSES_FILE;
-  private static final String START_SHELL_CMD = "start";
-  private static final String STOP_SHELL_CMD = "stop";
-  private static final String REMOUNT_SYSTEM_CMD = "mount -o rw,remount /system";
-  private static final String UNSET_BOOTCOMPLETE_CMD = "setprop dev.bootcomplete \"0\"";
-
-  public static void init(int debugPort) {
-    DdmPreferences.setSelectedDebugPort(debugPort);
-
-    Hprof.init();
-
-    AndroidDebugBridge.init(true);
-
-    AndroidDebugBridge.createBridge();
-  }
-
-  /**
-   * Run a command in the shell on the device.
-   */
-  public static void doShell(IDevice device, String cmdline, long timeout, TimeUnit unit) {
-    doShell(device, cmdline, new NullShellOutputReceiver(), timeout, unit);
-  }
-
-  /**
-   * Run a command in the shell on the device. Collects and returns the console output.
-   */
-  public static String doShellReturnString(IDevice device, String cmdline, long timeout,
-      TimeUnit unit) {
-    CollectStringShellOutputReceiver rec = new CollectStringShellOutputReceiver();
-    doShell(device, cmdline, rec, timeout, unit);
-    return rec.toString();
-  }
-
-  /**
-   * Run a command in the shell on the device, directing all output to the given receiver.
-   */
-  public static void doShell(IDevice device, String cmdline, IShellOutputReceiver receiver,
-      long timeout, TimeUnit unit) {
-    try {
-      device.executeShellCommand(cmdline, receiver, timeout, unit);
-    } catch (Exception e) {
-      e.printStackTrace();
-    }
-  }
-
-  /**
-   * Run am start on the device.
-   */
-  public static void doAMStart(IDevice device, String name, String activity) {
-    doShell(device, "am start -n " + name + " /." + activity, 30, TimeUnit.SECONDS);
-  }
-
-  /**
-   * Find the device with the given serial. Give up after the given timeout (in milliseconds).
-   */
-  public static IDevice findDevice(String serial, int timeout) {
-    WaitForDevice wfd = new WaitForDevice(serial, timeout);
-    return wfd.get();
-  }
-
-  /**
-   * Get all devices ddms knows about. Wait at most for the given timeout.
-   */
-  public static IDevice[] findDevices(int timeout) {
-    WaitForDevice wfd = new WaitForDevice(null, timeout);
-    wfd.get();
-    return AndroidDebugBridge.getBridge().getDevices();
-  }
-
-  /**
-   * Return the build type of the given device. This is the value of the "ro.build.type"
-   * system property.
-   */
-  public static String getBuildType(IDevice device) {
-    try {
-      Future<String> buildType = device.getSystemProperty("ro.build.type");
-      return buildType.get(500, TimeUnit.MILLISECONDS);
-    } catch (Exception e) {
-    }
-    return null;
-  }
-
-  /**
-   * Check whether the given device has a pre-optimized boot image. More precisely, checks
-   * whether /system/framework/ * /boot.art exists.
-   */
-  public static boolean hasPrebuiltBootImage(IDevice device) {
-    String ret =
-        doShellReturnString(device, "ls /system/framework/*/boot.art", 500, TimeUnit.MILLISECONDS);
-
-    return !ret.contains("No such file or directory");
-  }
-
-    /**
-     * Write over the preloaded-classes file with an empty or existing file and regenerate the boot
-     * image as necessary.
-     *
-     * @param device
-     * @param pcFile
-     * @param bootTimeout
-     * @throws AdbCommandRejectedException
-     * @throws IOException
-     * @throws TimeoutException
-     * @throws SyncException
-     * @return true if successfully overwritten, false otherwise
-     */
-    public static boolean overwritePreloaded(IDevice device, File pcFile, long bootTimeout)
-            throws AdbCommandRejectedException, IOException, TimeoutException, SyncException {
-        boolean writeEmpty = (pcFile == null);
-        if (writeEmpty) {
-            // Check if the preloaded-classes file is already empty.
-            String oldContent =
-                    doShellReturnString(device, READ_PRELOADED_CMD, 1, TimeUnit.SECONDS);
-            if (oldContent.trim().equals("")) {
-                System.out.println("Preloaded-classes already empty.");
-                return true;
-            }
-        }
-
-        // Stop the system server etc.
-        doShell(device, STOP_SHELL_CMD, 1, TimeUnit.SECONDS);
-        // Remount the read-only system partition
-        doShell(device, REMOUNT_SYSTEM_CMD, 1, TimeUnit.SECONDS);
-        // Delete the preloaded-classes file
-        doShell(device, DELETE_PRELOADED_CMD, 1, TimeUnit.SECONDS);
-        // Delete the dalvik cache files
-        doShell(device, DELETE_CACHE_CMD, 1, TimeUnit.SECONDS);
-        if (writeEmpty) {
-            // Write an empty preloaded-classes file
-            doShell(device, CREATE_EMPTY_PRELOADED_CMD, 500, TimeUnit.MILLISECONDS);
-        } else {
-            // Push the new preloaded-classes file
-            device.pushFile(pcFile.getAbsolutePath(), PRELOADED_CLASSES_FILE);
-        }
-        // Manually reset the boot complete flag
-        doShell(device, UNSET_BOOTCOMPLETE_CMD, 1, TimeUnit.SECONDS);
-        // Restart system server on the device
-        doShell(device, START_SHELL_CMD, 1, TimeUnit.SECONDS);
-        // Wait for the boot complete flag and return the outcome.
-        return waitForBootComplete(device, bootTimeout);
-  }
-
-  private static boolean waitForBootComplete(IDevice device, long timeout) {
-    // Do a loop checking each second whether bootcomplete. Wait for at most the given
-    // threshold.
-    Date startDate = new Date();
-    for (;;) {
-      try {
-        Thread.sleep(1000);
-      } catch (InterruptedException e) {
-        // Ignore spurious wakeup.
-      }
-      // Check whether bootcomplete.
-      String ret =
-          doShellReturnString(device, "getprop dev.bootcomplete", 500, TimeUnit.MILLISECONDS);
-      if (ret.trim().equals("1")) {
-        break;
-      }
-      System.out.println("Still not booted: " + ret);
-
-      // Check whether we timed out. This is a simplistic check that doesn't take into account
-      // things like switches in time.
-      Date endDate = new Date();
-      long seconds =
-          TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS);
-      if (seconds > timeout) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  /**
-   * Enable method-tracing on device. The system should be restarted after this.
-   */
-  public static void enableTracing(IDevice device) {
-    // Disable selinux.
-    doShell(device, "setenforce 0", 100, TimeUnit.MILLISECONDS);
-
-    // Make the profile directory world-writable.
-    doShell(device, "chmod 777 /data/dalvik-cache/profiles", 100, TimeUnit.MILLISECONDS);
-
-    // Enable streaming method tracing with a small 1K buffer.
-    doShell(device, "setprop dalvik.vm.method-trace true", 100, TimeUnit.MILLISECONDS);
-    doShell(device, "setprop dalvik.vm.method-trace-file "
-                    + "/data/dalvik-cache/profiles/zygote.trace.bin", 100, TimeUnit.MILLISECONDS);
-    doShell(device, "setprop dalvik.vm.method-trace-file-siz 1024", 100, TimeUnit.MILLISECONDS);
-    doShell(device, "setprop dalvik.vm.method-trace-stream true", 100, TimeUnit.MILLISECONDS);
-  }
-
-  private static class NullShellOutputReceiver implements IShellOutputReceiver {
-    @Override
-    public boolean isCancelled() {
-      return false;
-    }
-
-    @Override
-    public void flush() {}
-
-    @Override
-    public void addOutput(byte[] arg0, int arg1, int arg2) {}
-  }
-
-  private static class CollectStringShellOutputReceiver implements IShellOutputReceiver {
-
-    private StringBuilder builder = new StringBuilder();
-
-    @Override
-    public String toString() {
-      String ret = builder.toString();
-      // Strip trailing newlines. They are especially ugly because adb uses DOS line endings.
-      while (ret.endsWith("\r") || ret.endsWith("\n")) {
-        ret = ret.substring(0, ret.length() - 1);
-      }
-      return ret;
-    }
-
-    @Override
-    public void addOutput(byte[] arg0, int arg1, int arg2) {
-      builder.append(new String(arg0, arg1, arg2));
-    }
-
-    @Override
-    public void flush() {}
-
-    @Override
-    public boolean isCancelled() {
-      return false;
-    }
-  }
-
-  private static class WaitForDevice {
-
-    private String serial;
-    private long timeout;
-    private IDevice device;
-
-    public WaitForDevice(String serial, long timeout) {
-      this.serial = serial;
-      this.timeout = timeout;
-      device = null;
-    }
-
-    public IDevice get() {
-      if (device == null) {
-          WaitForDeviceListener wfdl = new WaitForDeviceListener(serial);
-          synchronized (wfdl) {
-              AndroidDebugBridge.addDeviceChangeListener(wfdl);
-
-              // Check whether we already know about this device.
-              IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
-              if (serial != null) {
-                  for (IDevice d : devices) {
-                      if (serial.equals(d.getSerialNumber())) {
-                          // Only accept if there are clients already. Else wait for the callback informing
-                          // us that we now have clients.
-                          if (d.hasClients()) {
-                              device = d;
-                          }
-
-                          break;
-                      }
-                  }
-              } else {
-                  if (devices.length > 0) {
-                      device = devices[0];
-                  }
-              }
-
-              if (device == null) {
-                  try {
-                      wfdl.wait(timeout);
-                  } catch (InterruptedException e) {
-                      // Ignore spurious wakeups.
-                  }
-                  device = wfdl.getDevice();
-              }
-
-              AndroidDebugBridge.removeDeviceChangeListener(wfdl);
-          }
-      }
-
-      if (device != null) {
-          // Wait for clients.
-          WaitForClientsListener wfcl = new WaitForClientsListener(device);
-          synchronized (wfcl) {
-              AndroidDebugBridge.addDeviceChangeListener(wfcl);
-
-              if (!device.hasClients()) {
-                  try {
-                      wfcl.wait(timeout);
-                  } catch (InterruptedException e) {
-                      // Ignore spurious wakeups.
-                  }
-              }
-
-              AndroidDebugBridge.removeDeviceChangeListener(wfcl);
-          }
-      }
-
-      return device;
-    }
-
-    private static class WaitForDeviceListener implements IDeviceChangeListener {
-
-        private String serial;
-        private IDevice device;
-
-        public WaitForDeviceListener(String serial) {
-            this.serial = serial;
-        }
-
-        public IDevice getDevice() {
-            return device;
-        }
-
-        @Override
-        public void deviceChanged(IDevice arg0, int arg1) {
-            // We may get a device changed instead of connected. Handle like a connection.
-            deviceConnected(arg0);
-        }
-
-        @Override
-        public void deviceConnected(IDevice arg0) {
-            if (device != null) {
-                // Ignore updates.
-                return;
-            }
-
-            if (serial == null || serial.equals(arg0.getSerialNumber())) {
-                device = arg0;
-                synchronized (this) {
-                    notifyAll();
-                }
-            }
-        }
-
-        @Override
-        public void deviceDisconnected(IDevice arg0) {
-            // Ignore disconnects.
-        }
-
-    }
-
-    private static class WaitForClientsListener implements IDeviceChangeListener {
-
-        private IDevice myDevice;
-
-        public WaitForClientsListener(IDevice myDevice) {
-            this.myDevice = myDevice;
-        }
-
-        @Override
-        public void deviceChanged(IDevice arg0, int arg1) {
-            if (arg0 == myDevice && (arg1 & IDevice.CHANGE_CLIENT_LIST) != 0) {
-                // Got a client list, done here.
-                synchronized (this) {
-                    notifyAll();
-                }
-            }
-        }
-
-        @Override
-        public void deviceConnected(IDevice arg0) {
-        }
-
-        @Override
-        public void deviceDisconnected(IDevice arg0) {
-        }
-
-    }
-  }
-
-}
diff --git a/tools/preload2/src/com/android/preload/DumpData.java b/tools/preload2/src/com/android/preload/DumpData.java
deleted file mode 100644
index d997224..0000000
--- a/tools/preload2/src/com/android/preload/DumpData.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload;
-
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Holds the collected data for a process.
- */
-public class DumpData {
-    /**
-     * Name of the package (=application).
-     */
-    String packageName;
-
-    /**
-     * A map of class name to a string for the classloader. This may be a toString equivalent,
-     * or just a unique ID.
-     */
-    Map<String, String> dumpData;
-
-    /**
-     * The Date when this data was captured. Mostly for display purposes.
-     */
-    Date date;
-
-    /**
-     * A cached value for the number of boot classpath classes (classloader value in dumpData is
-     * null).
-     */
-    int bcpClasses;
-
-    public DumpData(String packageName, Map<String, String> dumpData, Date date) {
-        this.packageName = packageName;
-        this.dumpData = dumpData;
-        this.date = date;
-
-        countBootClassPath();
-    }
-
-    public String getPackageName() {
-        return packageName;
-    }
-
-    public Date getDate() {
-        return date;
-    }
-
-    public Map<String, String> getDumpData() {
-        return dumpData;
-    }
-
-    public void countBootClassPath() {
-        bcpClasses = 0;
-        for (Map.Entry<String, String> e : dumpData.entrySet()) {
-            if (e.getValue() == null) {
-                bcpClasses++;
-            }
-        }
-    }
-
-    // Return an inverted mapping.
-    public Map<String, Set<String>> invertData() {
-        Map<String, Set<String>> ret = new HashMap<>();
-        for (Map.Entry<String, String> e : dumpData.entrySet()) {
-            if (!ret.containsKey(e.getValue())) {
-                ret.put(e.getValue(), new HashSet<String>());
-            }
-            ret.get(e.getValue()).add(e.getKey());
-        }
-        return ret;
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/DumpDataIO.java b/tools/preload2/src/com/android/preload/DumpDataIO.java
deleted file mode 100644
index 28625c5..0000000
--- a/tools/preload2/src/com/android/preload/DumpDataIO.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.File;
-import java.io.FileReader;
-import java.text.DateFormat;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-/**
- * Helper class for serialization and deserialization of a collection of DumpData objects to XML.
- */
-public class DumpDataIO {
-
-  /**
-   * Serialize the given collection to an XML document. Returns the produced string.
-   */
-  public static String serialize(Collection<DumpData> data) {
-      // We'll do this by hand, constructing a DOM or similar is too complicated for our simple
-      // use case.
-
-      StringBuilder sb = new StringBuilder();
-      sb.append("<preloaded-classes-data>\n");
-
-      for (DumpData d : data) {
-          serialize(d, sb);
-      }
-
-      sb.append("</preloaded-classes-data>\n");
-      return sb.toString();
-  }
-
-  private static void serialize(DumpData d, StringBuilder sb) {
-      sb.append("<data package=\"" + d.packageName + "\" date=\"" +
-              DateFormat.getDateTimeInstance().format(d.date) +"\">\n");
-
-      for (Map.Entry<String, String> e : d.dumpData.entrySet()) {
-          sb.append("<class name=\"" + e.getKey() + "\" classloader=\"" + e.getValue() + "\"/>\n");
-      }
-
-      sb.append("</data>\n");
-  }
-
-  /**
-   * Load a collection of DumpData objects from the given file.
-   */
-  public static Collection<DumpData> deserialize(File f) throws Exception {
-      // Use SAX parsing. Our format is very simple. Don't do any schema validation or such.
-
-      SAXParserFactory spf = SAXParserFactory.newInstance();
-      spf.setNamespaceAware(false);
-      SAXParser saxParser = spf.newSAXParser();
-
-      XMLReader xmlReader = saxParser.getXMLReader();
-      DumpDataContentHandler ddch = new DumpDataContentHandler();
-      xmlReader.setContentHandler(ddch);
-      xmlReader.parse(new InputSource(new FileReader(f)));
-
-      return ddch.data;
-  }
-
-  private static class DumpDataContentHandler extends DefaultHandler {
-      Collection<DumpData> data = new LinkedList<DumpData>();
-      DumpData openData = null;
-
-      @Override
-      public void startElement(String uri, String localName, String qName, Attributes attributes)
-              throws SAXException {
-          if (qName.equals("data")) {
-              if (openData != null) {
-                  throw new IllegalStateException();
-              }
-              String pkg = attributes.getValue("package");
-              String dateString = attributes.getValue("date");
-
-              if (pkg == null || dateString == null) {
-                  throw new IllegalArgumentException();
-              }
-
-              try {
-                  Date date = DateFormat.getDateTimeInstance().parse(dateString);
-                  openData = new DumpData(pkg, new HashMap<String, String>(), date);
-              } catch (Exception e) {
-                  throw new RuntimeException(e);
-              }
-          } else if (qName.equals("class")) {
-              if (openData == null) {
-                  throw new IllegalStateException();
-              }
-              String className = attributes.getValue("name");
-              String classLoader = attributes.getValue("classloader");
-
-              if (className == null || classLoader == null) {
-                  throw new IllegalArgumentException();
-              }
-
-              openData.dumpData.put(className, classLoader.equals("null") ? null : classLoader);
-          }
-      }
-
-      @Override
-      public void endElement(String uri, String localName, String qName) throws SAXException {
-          if (qName.equals("data")) {
-              if (openData == null) {
-                  throw new IllegalStateException();
-              }
-              openData.countBootClassPath();
-
-              data.add(openData);
-              openData = null;
-          }
-      }
-  }
-}
diff --git a/tools/preload2/src/com/android/preload/DumpTableModel.java b/tools/preload2/src/com/android/preload/DumpTableModel.java
deleted file mode 100644
index d97cbf0..0000000
--- a/tools/preload2/src/com/android/preload/DumpTableModel.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.table.AbstractTableModel;
-
-/**
- * A table model for collected DumpData. This is both the internal storage as well as the model
- * for display.
- */
-public class DumpTableModel extends AbstractTableModel {
-
-    private List<DumpData> data = new ArrayList<DumpData>();
-
-    public void addData(DumpData d) {
-        data.add(d);
-        fireTableRowsInserted(data.size() - 1, data.size() - 1);
-    }
-
-    public void clear() {
-        int size = data.size();
-        if (size > 0) {
-            data.clear();
-            fireTableRowsDeleted(0, size - 1);
-        }
-    }
-
-    public List<DumpData> getData() {
-        return data;
-    }
-
-    @Override
-    public int getRowCount() {
-        return data.size();
-    }
-
-    @Override
-    public int getColumnCount() {
-        return 4;
-    }
-
-    @Override
-    public String getColumnName(int column) {
-        switch (column) {
-            case 0:
-                return "Package";
-            case 1:
-                return "Date";
-            case 2:
-                return "# All Classes";
-            case 3:
-                return "# Boot Classpath Classes";
-
-            default:
-                throw new IndexOutOfBoundsException(String.valueOf(column));
-        }
-    }
-
-    @Override
-    public Object getValueAt(int rowIndex, int columnIndex) {
-        DumpData d = data.get(rowIndex);
-        switch (columnIndex) {
-            case 0:
-                return d.packageName;
-            case 1:
-                return d.date;
-            case 2:
-                return d.dumpData.size();
-            case 3:
-                return d.bcpClasses;
-
-            default:
-                throw new IndexOutOfBoundsException(String.valueOf(columnIndex));
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java
deleted file mode 100644
index 2265e95..0000000
--- a/tools/preload2/src/com/android/preload/Main.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-import com.android.preload.actions.ClearTableAction;
-import com.android.preload.actions.ComputeThresholdAction;
-import com.android.preload.actions.ComputeThresholdXAction;
-import com.android.preload.actions.DeviceSpecific;
-import com.android.preload.actions.ExportAction;
-import com.android.preload.actions.ImportAction;
-import com.android.preload.actions.ReloadListAction;
-import com.android.preload.actions.RunMonkeyAction;
-import com.android.preload.actions.ScanAllPackagesAction;
-import com.android.preload.actions.ScanPackageAction;
-import com.android.preload.actions.ShowDataAction;
-import com.android.preload.actions.WritePreloadedClassesAction;
-import com.android.preload.classdataretrieval.ClassDataRetriever;
-import com.android.preload.classdataretrieval.hprof.Hprof;
-import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever;
-import com.android.preload.ui.IUI;
-import com.android.preload.ui.SequenceUI;
-import com.android.preload.ui.SwingUI;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-
-import javax.swing.Action;
-import javax.swing.DefaultListModel;
-
-public class Main {
-
-    /**
-     * Enable tracing mode. This is a work-in-progress to derive compiled-methods data, so it is
-     * off for now.
-     */
-    public final static boolean ENABLE_TRACING = false;
-
-    /**
-     * Ten-second timeout.
-     */
-    public final static int DEFAULT_TIMEOUT_MILLIS = 10 * 1000;
-
-    /**
-     * Hprof timeout. Two minutes.
-     */
-    public final static int HPROF_TIMEOUT_MILLIS = 120 * 1000;
-
-    private IDevice device;
-    private static ClientUtils clientUtils;
-
-    private DumpTableModel dataTableModel;
-    private DefaultListModel<Client> clientListModel;
-
-    private IUI ui;
-
-    // Actions that need to be updated once a device is selected.
-    private Collection<DeviceSpecific> deviceSpecificActions;
-
-    // Current main instance.
-    private static Main top;
-    private static boolean useJdwpClassDataRetriever = false;
-
-    public final static String CLASS_PRELOAD_BLACKLIST = "android.app.AlarmManager$" + "|"
-            + "android.app.SearchManager$" + "|" + "android.os.FileObserver$" + "|"
-            + "com.android.server.PackageManagerService\\$AppDirObserver$" + "|" +
-
-
-            // Threads
-            "android.os.AsyncTask$" + "|" + "android.pim.ContactsAsyncHelper$" + "|"
-            + "android.webkit.WebViewClassic\\$1$" + "|" + "java.lang.ProcessManager$" + "|"
-            + "(.*\\$NoPreloadHolder$)";
-
-    public final static String SCAN_ALL_CMD = "scan-all";
-    public final static String SCAN_PACKAGE_CMD = "scan";
-    public final static String COMPUTE_FILE_CMD = "comp";
-    public final static String EXPORT_CMD = "export";
-    public final static String IMPORT_CMD = "import";
-    public final static String WRITE_CMD = "write";
-
-    /**
-     * @param args
-     */
-    public static void main(String[] args) {
-        Main m;
-        if (args.length > 0 && args[0].equals("--seq")) {
-            m = createSequencedMain(args);
-        } else {
-            m = new Main(new SwingUI());
-        }
-
-        top = m;
-        m.startUp();
-    }
-
-    public Main(IUI ui) {
-        this.ui = ui;
-
-        clientListModel = new DefaultListModel<Client>();
-        dataTableModel = new DumpTableModel();
-
-        clientUtils = new ClientUtils(DEFAULT_TIMEOUT_MILLIS);  // Client utils with 10s timeout.
-
-        List<Action> actions = new ArrayList<Action>();
-        actions.add(new ReloadListAction(clientUtils, null, clientListModel));
-        actions.add(new ClearTableAction(dataTableModel));
-        actions.add(new RunMonkeyAction(null, dataTableModel));
-        actions.add(new ScanPackageAction(clientUtils, null, dataTableModel));
-        actions.add(new ScanAllPackagesAction(clientUtils, null, dataTableModel));
-        actions.add(new ComputeThresholdAction("Compute preloaded-classes", dataTableModel, 2,
-                CLASS_PRELOAD_BLACKLIST));
-        actions.add(new ComputeThresholdAction("Compute compiled-classes", dataTableModel, 1,
-                null));
-        actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel,
-                CLASS_PRELOAD_BLACKLIST));
-        actions.add(new WritePreloadedClassesAction(clientUtils, null, dataTableModel));
-        actions.add(new ShowDataAction(dataTableModel));
-        actions.add(new ImportAction(dataTableModel));
-        actions.add(new ExportAction(dataTableModel));
-
-        deviceSpecificActions = new ArrayList<DeviceSpecific>();
-        for (Action a : actions) {
-            if (a instanceof DeviceSpecific) {
-                deviceSpecificActions.add((DeviceSpecific)a);
-            }
-        }
-
-        ui.prepare(clientListModel, dataTableModel, actions);
-    }
-
-    /**
-     * @param args
-     * @return
-     */
-    private static Main createSequencedMain(String[] args) {
-        SequenceUI ui = new SequenceUI();
-        Main main = new Main(ui);
-
-        Iterator<String> it = Arrays.asList(args).iterator();
-        it.next();  // --seq
-        // Setup
-        ui.choice("#" + it.next());  // Device.
-        ui.confirmNo();              // Prepare: no.
-        // Actions
-        try {
-            while (it.hasNext()) {
-                String op = it.next();
-                // Operation: Scan a single package
-                if (SCAN_PACKAGE_CMD.equals(op)) {
-                    System.out.println("Scanning package.");
-                    ui.action(ScanPackageAction.class);
-                    ui.client(it.next());
-                // Operation: Scan all packages
-                } else if (SCAN_ALL_CMD.equals(op)) {
-                    System.out.println("Scanning all packages.");
-                    ui.action(ScanAllPackagesAction.class);
-                // Operation: Export the output to a file
-                } else if (EXPORT_CMD.equals(op)) {
-                    System.out.println("Exporting data.");
-                    ui.action(ExportAction.class);
-                    ui.output(new File(it.next()));
-                // Operation: Import the input from a file or directory
-                } else if (IMPORT_CMD.equals(op)) {
-                    System.out.println("Importing data.");
-                    File file = new File(it.next());
-                    if (!file.exists()) {
-                        throw new RuntimeException(
-                                String.format("File does not exist, %s.", file.getAbsolutePath()));
-                    } else if (file.isFile()) {
-                        ui.action(ImportAction.class);
-                        ui.input(file);
-                    } else if (file.isDirectory()) {
-                        for (File content : file.listFiles()) {
-                            ui.action(ImportAction.class);
-                            ui.input(content);
-                        }
-                    }
-                // Operation: Compute preloaded classes with specific threshold
-                } else if (COMPUTE_FILE_CMD.equals(op)) {
-                    System.out.println("Compute preloaded classes.");
-                    ui.action(ComputeThresholdXAction.class);
-                    ui.input(it.next());
-                    ui.confirmYes();
-                    ui.output(new File(it.next()));
-                // Operation: Write preloaded classes from a specific file
-                } else if (WRITE_CMD.equals(op)) {
-                    System.out.println("Writing preloaded classes.");
-                    ui.action(WritePreloadedClassesAction.class);
-                    ui.input(new File(it.next()));
-                }
-            }
-        } catch (NoSuchElementException e) {
-            System.out.println("Failed to parse action sequence correctly.");
-            throw e;
-        }
-
-        return main;
-    }
-
-    public static IUI getUI() {
-        return top.ui;
-    }
-
-    public static ClassDataRetriever getClassDataRetriever() {
-        if (useJdwpClassDataRetriever) {
-            return new JDWPClassDataRetriever();
-        } else {
-            return new Hprof(HPROF_TIMEOUT_MILLIS);
-        }
-    }
-
-    public IDevice getDevice() {
-        return device;
-    }
-
-    public void setDevice(IDevice device) {
-        this.device = device;
-        for (DeviceSpecific ds : deviceSpecificActions) {
-            ds.setDevice(device);
-        }
-    }
-
-    public DefaultListModel<Client> getClientListModel() {
-        return clientListModel;
-    }
-
-    static class DeviceWrapper {
-        IDevice device;
-
-        public DeviceWrapper(IDevice d) {
-            device = d;
-        }
-
-        @Override
-        public String toString() {
-            return device.getName() + " (#" + device.getSerialNumber() + ")";
-        }
-    }
-
-    private void startUp() {
-        getUI().showWaitDialog();
-        initDevice();
-
-        // Load clients.
-        new ReloadListAction(clientUtils, getDevice(), clientListModel).run();
-
-        getUI().hideWaitDialog();
-        getUI().ready();
-    }
-
-    private void initDevice() {
-        DeviceUtils.init(DEFAULT_TIMEOUT_MILLIS);
-
-        IDevice devices[] = DeviceUtils.findDevices(DEFAULT_TIMEOUT_MILLIS);
-        if (devices == null || devices.length == 0) {
-            throw new RuntimeException("Could not find any devices...");
-        }
-
-        getUI().hideWaitDialog();
-
-        DeviceWrapper deviceWrappers[] = new DeviceWrapper[devices.length];
-        for (int i = 0; i < devices.length; i++) {
-            deviceWrappers[i] = new DeviceWrapper(devices[i]);
-        }
-
-        DeviceWrapper ret = Main.getUI().showChoiceDialog("Choose a device", "Choose device",
-                deviceWrappers);
-        if (ret != null) {
-            setDevice(ret.device);
-        } else {
-            System.exit(0);
-        }
-
-        boolean prepare = Main.getUI().showConfirmDialog("Prepare device?",
-                "Do you want to prepare the device? This is highly recommended.");
-        if (prepare) {
-            String buildType = DeviceUtils.getBuildType(device);
-            if (buildType == null || (!buildType.equals("userdebug") && !buildType.equals("eng"))) {
-                Main.getUI().showMessageDialog("Need a userdebug or eng build! (Found " + buildType
-                        + ")");
-                return;
-            }
-            if (DeviceUtils.hasPrebuiltBootImage(device)) {
-                Main.getUI().showMessageDialog("Cannot prepare a device with pre-optimized boot "
-                        + "image!");
-                return;
-            }
-
-            if (ENABLE_TRACING) {
-                DeviceUtils.enableTracing(device);
-            }
-
-            Main.getUI().showMessageDialog("The device will reboot. This will potentially take a "
-                    + "long time. Please be patient.");
-            boolean success = false;
-            try {
-                success = DeviceUtils.overwritePreloaded(device, null, 15 * 60);
-            } catch (Exception e) {
-                System.err.println(e);
-            } finally {
-                if (!success) {
-                    Main.getUI().showMessageDialog(
-                            "Removing preloaded-classes failed unexpectedly!");
-                }
-            }
-        }
-    }
-
-    public static Map<String, String> findAndGetClassData(IDevice device, String packageName)
-            throws Exception {
-        Client client = clientUtils.findClient(device, packageName, -1);
-        if (client == null) {
-            throw new RuntimeException("Could not find client...");
-        }
-        System.out.println("Found client: " + client);
-
-        return getClassDataRetriever().getClassData(client);
-    }
-
-}
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
deleted file mode 100644
index 5787d85..0000000
--- a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.preload.Main;
-import java.awt.event.ActionEvent;
-
-import javax.swing.AbstractAction;
-
-public abstract class AbstractThreadedAction extends AbstractAction implements Runnable {
-
-    protected AbstractThreadedAction(String title) {
-        super(title);
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        if (Main.getUI().isSingleThreaded()) {
-            run();
-        } else {
-            new Thread(this).start();
-        }
-    }
-
-}
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java
deleted file mode 100644
index 7906417..0000000
--- a/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.ddmlib.IDevice;
-
-import java.awt.event.ActionEvent;
-
-public abstract class AbstractThreadedDeviceSpecificAction extends AbstractThreadedAction
-        implements DeviceSpecific {
-
-    protected IDevice device;
-
-    protected AbstractThreadedDeviceSpecificAction(String title, IDevice device) {
-        super(title);
-        this.device = device;
-    }
-
-    @Override
-    public void setDevice(IDevice device) {
-        this.device = device;
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        if (device == null) {
-            return;
-        }
-        super.actionPerformed(e);
-    }
-}
diff --git a/tools/preload2/src/com/android/preload/actions/ClearTableAction.java b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java
deleted file mode 100644
index c0e4795..0000000
--- a/tools/preload2/src/com/android/preload/actions/ClearTableAction.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.preload.DumpTableModel;
-
-import java.awt.event.ActionEvent;
-
-import javax.swing.AbstractAction;
-
-public class ClearTableAction extends AbstractAction {
-    private final DumpTableModel dataTableModel;
-
-    public ClearTableAction(DumpTableModel dataTableModel) {
-        super("Clear");
-        this.dataTableModel = dataTableModel;
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        dataTableModel.clear();
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
deleted file mode 100644
index 3a7f7f7..0000000
--- a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.awt.event.ActionEvent;
-import java.io.File;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-import javax.swing.AbstractAction;
-
-/**
- * Compute an intersection of classes from the given data. A class is in the intersection if it
- * appears in at least the number of threshold given packages. An optional blacklist can be
- * used to filter classes from the intersection.
- */
-public class ComputeThresholdAction extends AbstractThreadedAction {
-    protected int threshold;
-    private Pattern blacklist;
-    private DumpTableModel dataTableModel;
-
-    /**
-     * Create an action with the given parameters. The blacklist is a regular expression
-     * that filters classes.
-     */
-    public ComputeThresholdAction(String name, DumpTableModel dataTableModel, int threshold,
-            String blacklist) {
-        super(name);
-        this.dataTableModel = dataTableModel;
-        this.threshold = threshold;
-        if (blacklist != null) {
-            this.blacklist = Pattern.compile(blacklist);
-        }
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        List<DumpData> data = dataTableModel.getData();
-        if (data.size() == 0) {
-            Main.getUI().showMessageDialog("No data available, please scan packages or run "
-                    + "monkeys.");
-            return;
-        }
-        if (data.size() == 1) {
-            Main.getUI().showMessageDialog("Cannot compute list from only one data set, please "
-                    + "scan packages or run monkeys.");
-            return;
-        }
-
-        super.actionPerformed(e);
-    }
-
-    @Override
-    public void run() {
-        Main.getUI().showWaitDialog();
-
-        Map<String, Set<String>> uses = new HashMap<String, Set<String>>();
-        for (DumpData d : dataTableModel.getData()) {
-            Main.getUI().updateWaitDialog("Merging " + d.getPackageName());
-            updateClassUse(d.getPackageName(), uses, getBootClassPathClasses(d.getDumpData()));
-        }
-
-        Main.getUI().updateWaitDialog("Computing thresholded set");
-        Set<String> result = fromThreshold(uses, blacklist, threshold);
-        Main.getUI().hideWaitDialog();
-
-        boolean ret = Main.getUI().showConfirmDialog("Computed a set with " + result.size()
-                + " classes, would you like to save to disk?", "Save?");
-        if (ret) {
-            File f = Main.getUI().showSaveDialog();
-            if (f != null) {
-                saveSet(result, f);
-            }
-        }
-    }
-
-    private Set<String> fromThreshold(Map<String, Set<String>> classUses, Pattern blacklist,
-            int threshold) {
-        TreeSet<String> ret = new TreeSet<>(); // TreeSet so it's nicely ordered by name.
-
-        for (Map.Entry<String, Set<String>> e : classUses.entrySet()) {
-            if (e.getValue().size() >= threshold) {
-                if (blacklist == null || !blacklist.matcher(e.getKey()).matches()) {
-                    ret.add(e.getKey());
-                }
-            }
-        }
-
-        return ret;
-    }
-
-    private static void updateClassUse(String pkg, Map<String, Set<String>> classUses,
-            Set<String> classes) {
-        for (String className : classes) {
-            Set<String> old = classUses.get(className);
-            if (old == null) {
-                classUses.put(className, new HashSet<String>());
-            }
-            classUses.get(className).add(pkg);
-        }
-    }
-
-    private static Set<String> getBootClassPathClasses(Map<String, String> source) {
-        Set<String> ret = new HashSet<>();
-        for (Map.Entry<String, String> e : source.entrySet()) {
-            if (e.getValue() == null) {
-                ret.add(e.getKey());
-            }
-        }
-        return ret;
-    }
-
-    private static void saveSet(Set<String> result, File f) {
-        try {
-            PrintWriter out = new PrintWriter(f);
-            for (String s : result) {
-                out.println(s);
-            }
-            out.close();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java
deleted file mode 100644
index 3ec0a4c..0000000
--- a/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-public class ComputeThresholdXAction extends ComputeThresholdAction {
-
-    public ComputeThresholdXAction(String name, DumpTableModel dataTableModel,
-            String blacklist) {
-        super(name, dataTableModel, 1, blacklist);
-    }
-
-    @Override
-    public void run() {
-        String value = Main.getUI().showInputDialog("Threshold?");
-
-        if (value != null) {
-            try {
-                threshold = Integer.parseInt(value);
-                super.run();
-            } catch (Exception exc) {
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java
deleted file mode 100644
index 35a8f26..0000000
--- a/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.ddmlib.IDevice;
-
-/**
- * Marks an action as being device-specific. The user must set the device through the specified
- * method if the device selection changes.
- *
- * Implementors must tolerate a null device (for example, with a no-op). This includes calling
- * any methods before setDevice has been called.
- */
-public interface DeviceSpecific {
-
-    /**
-     * Set the device that should be used. Note that there is no restriction on calling other
-     * methods of the implementor before a setDevice call. Neither is device guaranteed to be
-     * non-null.
-     *
-     * @param device The device to use going forward.
-     */
-    public void setDevice(IDevice device);
-}
diff --git a/tools/preload2/src/com/android/preload/actions/ExportAction.java b/tools/preload2/src/com/android/preload/actions/ExportAction.java
deleted file mode 100644
index 848a568..0000000
--- a/tools/preload2/src/com/android/preload/actions/ExportAction.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.preload.DumpDataIO;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-import java.awt.event.ActionEvent;
-import java.io.File;
-import java.io.PrintWriter;
-
-public class ExportAction extends AbstractThreadedAction {
-    private File lastSaveFile;
-    private DumpTableModel dataTableModel;
-
-    public ExportAction(DumpTableModel dataTableModel) {
-        super("Export data");
-        this.dataTableModel = dataTableModel;
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        lastSaveFile = Main.getUI().showSaveDialog();
-        if (lastSaveFile != null) {
-            super.actionPerformed(e);
-        }
-    }
-
-    @Override
-    public void run() {
-        Main.getUI().showWaitDialog();
-
-        String serialized = DumpDataIO.serialize(dataTableModel.getData());
-
-        if (serialized != null) {
-            try {
-                PrintWriter out = new PrintWriter(lastSaveFile);
-                out.println(serialized);
-                out.close();
-
-                Main.getUI().hideWaitDialog();
-            } catch (Exception e) {
-                Main.getUI().hideWaitDialog();
-                Main.getUI().showMessageDialog("Failed writing: " + e.getMessage());
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ImportAction.java b/tools/preload2/src/com/android/preload/actions/ImportAction.java
deleted file mode 100644
index bfeeb83..0000000
--- a/tools/preload2/src/com/android/preload/actions/ImportAction.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.preload.DumpData;
-import com.android.preload.DumpDataIO;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.awt.event.ActionEvent;
-import java.io.File;
-import java.util.Collection;
-
-import javax.swing.AbstractAction;
-
-public class ImportAction extends AbstractThreadedAction {
-    private File[] lastOpenFiles;
-    private DumpTableModel dataTableModel;
-
-    public ImportAction(DumpTableModel dataTableModel) {
-        super("Import data");
-        this.dataTableModel = dataTableModel;
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        lastOpenFiles = Main.getUI().showOpenDialog(true);
-        if (lastOpenFiles != null) {
-            super.actionPerformed(e);
-        }
-    }
-
-    @Override
-    public void run() {
-        Main.getUI().showWaitDialog();
-
-        try {
-            for (File f : lastOpenFiles) {
-                try {
-                    Collection<DumpData> data = DumpDataIO.deserialize(f);
-
-                    for (DumpData d : data) {
-                        dataTableModel.addData(d);
-                    }
-                } catch (Exception e) {
-                    Main.getUI().showMessageDialog("Failed reading: " + e.getMessage());
-                }
-            }
-        } finally {
-            Main.getUI().hideWaitDialog();
-        }
-
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ReloadListAction.java b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java
deleted file mode 100644
index 29f0557..0000000
--- a/tools/preload2/src/com/android/preload/actions/ReloadListAction.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-import com.android.preload.ClientUtils;
-
-import java.util.Arrays;
-import java.util.Comparator;
-
-import javax.swing.DefaultListModel;
-
-public class ReloadListAction extends AbstractThreadedDeviceSpecificAction {
-
-    private ClientUtils clientUtils;
-    private final DefaultListModel<Client> clientListModel;
-
-    public ReloadListAction(ClientUtils utils, IDevice device,
-            DefaultListModel<Client> clientListModel) {
-        super("Reload", device);
-        this.clientUtils = utils;
-        this.clientListModel = clientListModel;
-    }
-
-    @Override
-    public void run() {
-        Client[] clients = clientUtils.findAllClients(device);
-        if (clients != null) {
-            Arrays.sort(clients, new ClientComparator());
-        }
-        clientListModel.removeAllElements();
-        for (Client c : clients) {
-            clientListModel.addElement(c);
-        }
-    }
-
-    private static class ClientComparator implements Comparator<Client> {
-
-        @Override
-        public int compare(Client o1, Client o2) {
-            String s1 = o1.getClientData().getClientDescription();
-            String s2 = o2.getClientData().getClientDescription();
-
-            if (s1 == null || s2 == null) {
-                // Not good, didn't get all data?
-                return (s1 == null) ? -1 : 1;
-            }
-
-            return s1.compareTo(s2);
-        }
-
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
deleted file mode 100644
index 29464fc..0000000
--- a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.ddmlib.IDevice;
-import com.android.preload.DeviceUtils;
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.awt.event.ActionEvent;
-import java.util.Date;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import javax.swing.AbstractAction;
-
-public class RunMonkeyAction extends AbstractAction implements DeviceSpecific {
-
-    private final static String DEFAULT_MONKEY_PACKAGES =
-            "com.android.calendar,com.android.gallery3d";
-
-    private IDevice device;
-    private DumpTableModel dataTableModel;
-
-    public RunMonkeyAction(IDevice device, DumpTableModel dataTableModel) {
-        super("Run monkey");
-        this.device = device;
-        this.dataTableModel = dataTableModel;
-    }
-
-    @Override
-    public void setDevice(IDevice device) {
-        this.device = device;
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        String packages = Main.getUI().showInputDialog("Please enter packages name to run with"
-                + " the monkey, or leave empty for default.");
-        if (packages == null) {
-            return;
-        }
-        if (packages.isEmpty()) {
-            packages = DEFAULT_MONKEY_PACKAGES;
-        }
-        Runnable r = new RunMonkeyRunnable(packages);
-        if (Main.getUI().isSingleThreaded()) {
-            r.run();
-        } else {
-            new Thread(r).start();
-        }
-    }
-
-    private class RunMonkeyRunnable implements Runnable {
-
-        private String packages;
-        private final static int ITERATIONS = 1000;
-
-        public RunMonkeyRunnable(String packages) {
-            this.packages = packages;
-        }
-
-        @Override
-        public void run() {
-            Main.getUI().showWaitDialog();
-
-            try {
-                String pkgs[] = packages.split(",");
-
-                for (String pkg : pkgs) {
-                    Main.getUI().updateWaitDialog("Running monkey on " + pkg);
-
-                    try {
-                        // Stop running app.
-                        forceStop(pkg);
-
-                        // Little bit of breather here.
-                        try {
-                            Thread.sleep(1000);
-                        } catch (Exception e) {
-                        }
-
-                        DeviceUtils.doShell(device, "monkey -p " + pkg + " " + ITERATIONS, 1,
-                                TimeUnit.MINUTES);
-
-                        Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
-                        Map<String, String> data = Main.findAndGetClassData(device, pkg);
-                        DumpData dumpData = new DumpData(pkg, data, new Date());
-                        dataTableModel.addData(dumpData);
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                    } finally {
-                        // Stop running app.
-                        forceStop(pkg);
-                    }
-                }
-            } finally {
-                Main.getUI().hideWaitDialog();
-            }
-        }
-
-        private void forceStop(String packageName) {
-            // Stop running app.
-            DeviceUtils.doShell(device, "force-stop " + packageName, 5, TimeUnit.SECONDS);
-            DeviceUtils.doShell(device, "kill " + packageName, 5, TimeUnit.SECONDS);
-            DeviceUtils.doShell(device, "kill `pid " + packageName + "`", 5, TimeUnit.SECONDS);
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java
deleted file mode 100644
index d74b8a3..0000000
--- a/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-import com.android.preload.ClientUtils;
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.util.Date;
-import java.util.Map;
-
-public class ScanAllPackagesAction extends AbstractThreadedDeviceSpecificAction {
-
-    private ClientUtils clientUtils;
-    private DumpTableModel dataTableModel;
-
-    public ScanAllPackagesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
-        super("Scan all packages", device);
-        this.clientUtils = utils;
-        this.dataTableModel = dataTableModel;
-    }
-
-    @Override
-    public void run() {
-        Main.getUI().showWaitDialog();
-
-        try {
-            Client[] clients = clientUtils.findAllClients(device);
-            for (Client c : clients) {
-                String pkg = c.getClientData().getClientDescription();
-                Main.getUI().showWaitDialog();
-                Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
-
-                try {
-                    Map<String, String> data = Main.getClassDataRetriever().getClassData(c);
-                    DumpData dumpData = new DumpData(pkg, data, new Date());
-                    dataTableModel.addData(dumpData);
-                } catch (Exception e) {
-                    e.printStackTrace();
-                }
-            }
-        } finally {
-            Main.getUI().hideWaitDialog();
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java
deleted file mode 100644
index 98492bd..0000000
--- a/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-import com.android.preload.ClientUtils;
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.util.Date;
-import java.util.Map;
-
-public class ScanPackageAction extends AbstractThreadedDeviceSpecificAction {
-
-    private ClientUtils clientUtils;
-    private DumpTableModel dataTableModel;
-
-    public ScanPackageAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
-        super("Scan package", device);
-        this.clientUtils = utils;
-        this.dataTableModel = dataTableModel;
-    }
-
-    @Override
-    public void run() {
-        Main.getUI().showWaitDialog();
-
-        try {
-            Client client = Main.getUI().getSelectedClient();
-            if (client != null) {
-                work(client);
-            } else {
-                Client[] clients = clientUtils.findAllClients(device);
-                if (clients.length > 0) {
-                    ClientWrapper[] clientWrappers = new ClientWrapper[clients.length];
-                    for (int i = 0; i < clientWrappers.length; i++) {
-                        clientWrappers[i] = new ClientWrapper(clients[i]);
-                    }
-                    Main.getUI().hideWaitDialog();
-
-                    ClientWrapper ret = Main.getUI().showChoiceDialog("Choose a package to scan",
-                            "Choose package",
-                            clientWrappers);
-                    if (ret != null) {
-                        work(ret.client);
-                    }
-                }
-            }
-        } finally {
-            Main.getUI().hideWaitDialog();
-        }
-    }
-
-    private void work(Client c) {
-        String pkg = c.getClientData().getClientDescription();
-        Main.getUI().showWaitDialog();
-        Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
-
-        try {
-            Map<String, String> data = Main.findAndGetClassData(device, pkg);
-            DumpData dumpData = new DumpData(pkg, data, new Date());
-            dataTableModel.addData(dumpData);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    private static class ClientWrapper {
-        private Client client;
-
-        public ClientWrapper(Client c) {
-            client = c;
-        }
-
-        @Override
-        public String toString() {
-            return client.getClientData().getClientDescription() + " (pid "
-                    + client.getClientData().getPid() + ")";
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ShowDataAction.java b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java
deleted file mode 100644
index 2bb175f..0000000
--- a/tools/preload2/src/com/android/preload/actions/ShowDataAction.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.awt.BorderLayout;
-import java.awt.event.ActionEvent;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.swing.AbstractAction;
-import javax.swing.JFrame;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
-
-public class ShowDataAction extends AbstractAction {
-    private DumpTableModel dataTableModel;
-
-    public ShowDataAction(DumpTableModel dataTableModel) {
-        super("Show data");
-        this.dataTableModel = dataTableModel;
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        // TODO(agampe): Auto-generated method stub
-        int selRow = Main.getUI().getSelectedDataTableRow();
-        if (selRow != -1) {
-            DumpData data = dataTableModel.getData().get(selRow);
-            Map<String, Set<String>> inv = data.invertData();
-
-            StringBuilder builder = new StringBuilder();
-
-            // First bootclasspath.
-            add(builder, "Boot classpath:", inv.get(null));
-
-            // Now everything else.
-            for (String k : inv.keySet()) {
-                if (k != null) {
-                    builder.append("==================\n\n");
-                    add(builder, k, inv.get(k));
-                }
-            }
-
-            JFrame newFrame = new JFrame(data.getPackageName() + " " + data.getDate());
-            newFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
-            newFrame.getContentPane().add(new JScrollPane(new JTextArea(builder.toString())),
-                    BorderLayout.CENTER);
-            newFrame.setSize(800, 600);
-            newFrame.setLocationRelativeTo(null);
-            newFrame.setVisible(true);
-        }
-    }
-
-    private void add(StringBuilder builder, String head, Set<String> set) {
-        builder.append(head);
-        builder.append('\n');
-        addSet(builder, set);
-        builder.append('\n');
-    }
-
-    private void addSet(StringBuilder builder, Set<String> set) {
-        if (set == null) {
-            builder.append("  NONE\n");
-            return;
-        }
-        List<String> sorted = new ArrayList<>(set);
-        Collections.sort(sorted);
-        for (String s : sorted) {
-            builder.append(s);
-            builder.append('\n');
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java b/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java
deleted file mode 100644
index 9b97f11..0000000
--- a/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.actions;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-import com.android.preload.ClientUtils;
-import com.android.preload.DeviceUtils;
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.awt.event.ActionEvent;
-import java.io.File;
-import java.util.Date;
-import java.util.Map;
-
-public class WritePreloadedClassesAction extends AbstractThreadedDeviceSpecificAction {
-    private File preloadedClassFile;
-
-    public WritePreloadedClassesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
-        super("Write preloaded classes action", device);
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        File[] files = Main.getUI().showOpenDialog(true);
-        if (files != null && files.length > 0) {
-            preloadedClassFile = files[0];
-            super.actionPerformed(e);
-        }
-    }
-
-    @Override
-    public void run() {
-        Main.getUI().showWaitDialog();
-        try {
-            // Write the new file with a 5-minute timeout
-            DeviceUtils.overwritePreloaded(device, preloadedClassFile, 5 * 60);
-        } catch (Exception e) {
-            System.err.println(e);
-        } finally {
-            Main.getUI().hideWaitDialog();
-        }
-    }
-}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java
deleted file mode 100644
index f04360f..0000000
--- a/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.classdataretrieval;
-
-import com.android.ddmlib.Client;
-
-import java.util.Map;
-
-/**
- * Retrieve a class-to-classloader map for loaded classes from the client.
- */
-public interface ClassDataRetriever {
-
-    public Map<String, String> getClassData(Client client);
-}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java
deleted file mode 100644
index 8d797ee..0000000
--- a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.classdataretrieval.hprof;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.ClientData.IHprofDumpHandler;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class GeneralHprofDumpHandler implements IHprofDumpHandler {
-
-    private List<IHprofDumpHandler> handlers = new ArrayList<>();
-
-    public void addHandler(IHprofDumpHandler h) {
-      synchronized (handlers) {
-        handlers.add(h);
-      }
-    }
-
-    public void removeHandler(IHprofDumpHandler h) {
-      synchronized (handlers) {
-        handlers.remove(h);
-      }
-    }
-
-    private List<IHprofDumpHandler> getIterationList() {
-      synchronized (handlers) {
-        return new ArrayList<>(handlers);
-      }
-    }
-
-    @Override
-    public void onEndFailure(Client arg0, String arg1) {
-      List<IHprofDumpHandler> iterList = getIterationList();
-      for (IHprofDumpHandler h : iterList) {
-        h.onEndFailure(arg0, arg1);
-      }
-    }
-
-    @Override
-    public void onSuccess(String arg0, Client arg1) {
-      List<IHprofDumpHandler> iterList = getIterationList();
-      for (IHprofDumpHandler h : iterList) {
-        h.onSuccess(arg0, arg1);
-      }
-    }
-
-    @Override
-    public void onSuccess(byte[] arg0, Client arg1) {
-      List<IHprofDumpHandler> iterList = getIterationList();
-      for (IHprofDumpHandler h : iterList) {
-        h.onSuccess(arg0, arg1);
-      }
-    }
-  }
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
deleted file mode 100644
index 84ec8b7..0000000
--- a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.classdataretrieval.hprof;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.ClientData;
-import com.android.ddmlib.ClientData.IHprofDumpHandler;
-import com.android.preload.classdataretrieval.ClassDataRetriever;
-import com.android.preload.ui.NullProgressMonitor;
-import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Queries;
-import com.android.tools.perflib.heap.Snapshot;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-public class Hprof implements ClassDataRetriever {
-
-    private static GeneralHprofDumpHandler hprofHandler;
-
-    public static void init() {
-        synchronized(Hprof.class) {
-            if (hprofHandler == null) {
-                ClientData.setHprofDumpHandler(hprofHandler = new GeneralHprofDumpHandler());
-            }
-        }
-    }
-
-    public static File doHprof(Client client, int timeout) {
-        GetHprof gh = new GetHprof(client, timeout);
-        return gh.get();
-    }
-
-    /**
-     * Return a map of class names to class-loader names derived from the hprof dump.
-     *
-     * @param hprofLocalFile
-     */
-    public static Map<String, String> analyzeHprof(File hprofLocalFile) throws Exception {
-        Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprofLocalFile));
-
-        Map<String, Set<ClassObj>> classes = Queries.classes(snapshot, null);
-        Map<String, String> retValue = new HashMap<String, String>();
-        for (Map.Entry<String, Set<ClassObj>> e : classes.entrySet()) {
-            for (ClassObj c : e.getValue()) {
-                String cl = c.getClassLoader() == null ? null : c.getClassLoader().toString();
-                String cName = c.getClassName();
-                int aDepth = 0;
-                while (cName.endsWith("[]")) {
-                    cName = cName.substring(0, cName.length()-2);
-                    aDepth++;
-                }
-                String newName = transformPrimitiveClass(cName);
-                if (aDepth > 0) {
-                    // Need to use kind-a descriptor syntax. If it was transformed, it is primitive.
-                    if (newName.equals(cName)) {
-                        newName = "L" + newName + ";";
-                    }
-                    for (int i = 0; i < aDepth; i++) {
-                        newName = "[" + newName;
-                    }
-                }
-                retValue.put(newName, cl);
-            }
-        }
-
-        // Free up memory.
-        snapshot.dispose();
-
-        return retValue;
-    }
-
-    private static Map<String, String> primitiveMapping;
-
-    static {
-        primitiveMapping = new HashMap<>();
-        primitiveMapping.put("boolean", "Z");
-        primitiveMapping.put("byte", "B");
-        primitiveMapping.put("char", "C");
-        primitiveMapping.put("double", "D");
-        primitiveMapping.put("float", "F");
-        primitiveMapping.put("int", "I");
-        primitiveMapping.put("long", "J");
-        primitiveMapping.put("short", "S");
-        primitiveMapping.put("void", "V");
-    }
-
-    private static String transformPrimitiveClass(String name) {
-        String rep = primitiveMapping.get(name);
-        if (rep != null) {
-            return rep;
-        }
-        return name;
-    }
-
-    private static class GetHprof implements IHprofDumpHandler {
-
-        private File target;
-        private long timeout;
-        private Client client;
-
-        public GetHprof(Client client, long timeout) {
-            this.client = client;
-            this.timeout = timeout;
-        }
-
-        public File get() {
-            synchronized (this) {
-                hprofHandler.addHandler(this);
-                client.dumpHprof();
-                if (target == null) {
-                    try {
-                        wait(timeout);
-                    } catch (Exception e) {
-                        System.out.println(e);
-                    }
-                }
-            }
-
-            hprofHandler.removeHandler(this);
-            return target;
-        }
-
-        private void wakeUp() {
-            synchronized (this) {
-                notifyAll();
-            }
-        }
-
-        @Override
-        public void onEndFailure(Client arg0, String arg1) {
-            System.out.println("GetHprof.onEndFailure");
-            if (client == arg0) {
-                wakeUp();
-            }
-        }
-
-        private static File createTargetFile() {
-            try {
-                return File.createTempFile("ddms", ".hprof");
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        @Override
-        public void onSuccess(String arg0, Client arg1) {
-            System.out.println("GetHprof.onSuccess");
-            if (client == arg1) {
-                try {
-                    target = createTargetFile();
-                    arg1.getDevice().getSyncService().pullFile(arg0,
-                            target.getAbsoluteFile().toString(), new NullProgressMonitor());
-                } catch (Exception e) {
-                    if (target != null) {
-                        target.delete();
-                    }
-                    e.printStackTrace();
-                    target = null;
-                }
-                wakeUp();
-            }
-        }
-
-        @Override
-        public void onSuccess(byte[] arg0, Client arg1) {
-            System.out.println("GetHprof.onSuccess");
-            if (client == arg1) {
-                try {
-                    target = createTargetFile();
-                    BufferedOutputStream out =
-                            new BufferedOutputStream(new FileOutputStream(target));
-                    out.write(arg0);
-                    out.close();
-                } catch (Exception e) {
-                    if (target != null) {
-                        target.delete();
-                    }
-                    e.printStackTrace();
-                    target = null;
-                }
-                wakeUp();
-            }
-        }
-    }
-
-    private int timeout;
-
-    public Hprof(int timeout) {
-        this.timeout = timeout;
-    }
-
-    @Override
-    public Map<String, String> getClassData(Client client) {
-        File hprofLocalFile = Hprof.doHprof(client, timeout);
-        if (hprofLocalFile == null) {
-            throw new RuntimeException("Failed getting dump...");
-        }
-        System.out.println("Dump file is " + hprofLocalFile);
-
-        try {
-            return analyzeHprof(hprofLocalFile);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        } finally {
-            hprofLocalFile.delete();
-        }
-    }
-}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java
deleted file mode 100644
index dbd4c89..0000000
--- a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.classdataretrieval.jdwp;
-
-import com.android.ddmlib.Client;
-import com.android.preload.classdataretrieval.ClassDataRetriever;
-
-import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
-import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
-import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
-import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
-import org.apache.harmony.jpda.tests.jdwp.share.JDWPTestCase;
-import org.apache.harmony.jpda.tests.jdwp.share.JDWPUnitDebuggeeWrapper;
-import org.apache.harmony.jpda.tests.share.JPDALogWriter;
-import org.apache.harmony.jpda.tests.share.JPDATestOptions;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class JDWPClassDataRetriever extends JDWPTestCase implements ClassDataRetriever {
-
-    private final Client client;
-
-    public JDWPClassDataRetriever() {
-        this(null);
-    }
-
-    public JDWPClassDataRetriever(Client client) {
-        this.client = client;
-    }
-
-
-    @Override
-    protected String getDebuggeeClassName() {
-        return "<unset>";
-    }
-
-    @Override
-    public Map<String, String> getClassData(Client client) {
-        return new JDWPClassDataRetriever(client).retrieve();
-    }
-
-    private Map<String, String> retrieve() {
-        if (client == null) {
-            throw new IllegalStateException();
-        }
-
-        settings = createTestOptions("localhost:" + String.valueOf(client.getDebuggerListenPort()));
-        settings.setDebuggeeSuspend("n");
-
-        logWriter = new JPDALogWriter(System.out, "", false);
-
-        try {
-            internalSetUp();
-
-            return retrieveImpl();
-        } catch (Exception e) {
-            e.printStackTrace();
-            return null;
-        } finally {
-            internalTearDown();
-        }
-    }
-
-    private Map<String, String> retrieveImpl() {
-        try {
-            // Suspend the app.
-            {
-                CommandPacket packet = new CommandPacket(
-                        JDWPCommands.VirtualMachineCommandSet.CommandSetID,
-                        JDWPCommands.VirtualMachineCommandSet.SuspendCommand);
-                ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
-                if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
-                    return null;
-                }
-            }
-
-            // List all classes.
-            CommandPacket packet = new CommandPacket(
-                    JDWPCommands.VirtualMachineCommandSet.CommandSetID,
-                    JDWPCommands.VirtualMachineCommandSet.AllClassesCommand);
-            ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
-
-            if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
-                return null;
-            }
-
-            int classCount = reply.getNextValueAsInt();
-            System.out.println("Runtime reported " + classCount + " classes.");
-
-            Map<Long, String> classes = new HashMap<Long, String>();
-            Map<Long, String> arrayClasses = new HashMap<Long, String>();
-
-            for (int i = 0; i < classCount; i++) {
-                byte refTypeTag = reply.getNextValueAsByte();
-                long typeID = reply.getNextValueAsReferenceTypeID();
-                String signature = reply.getNextValueAsString();
-                /* int status = */ reply.getNextValueAsInt();
-
-                switch (refTypeTag) {
-                    case JDWPConstants.TypeTag.CLASS:
-                    case JDWPConstants.TypeTag.INTERFACE:
-                        classes.put(typeID, signature);
-                        break;
-
-                    case JDWPConstants.TypeTag.ARRAY:
-                        arrayClasses.put(typeID, signature);
-                        break;
-                }
-            }
-
-            Map<String, String> result = new HashMap<String, String>();
-
-            // Parse all classes.
-            for (Map.Entry<Long, String> entry : classes.entrySet()) {
-                long typeID = entry.getKey();
-                String signature = entry.getValue();
-
-                if (!checkClass(typeID, signature, result)) {
-                    System.err.println("Issue investigating " + signature);
-                }
-            }
-
-            // For arrays, look at the leaf component type.
-            for (Map.Entry<Long, String> entry : arrayClasses.entrySet()) {
-                long typeID = entry.getKey();
-                String signature = entry.getValue();
-
-                if (!checkArrayClass(typeID, signature, result)) {
-                    System.err.println("Issue investigating " + signature);
-                }
-            }
-
-            return result;
-        } finally {
-            // Resume the app.
-            {
-                CommandPacket packet = new CommandPacket(
-                        JDWPCommands.VirtualMachineCommandSet.CommandSetID,
-                        JDWPCommands.VirtualMachineCommandSet.ResumeCommand);
-                /* ReplyPacket reply = */ debuggeeWrapper.vmMirror.performCommand(packet);
-            }
-        }
-    }
-
-    private boolean checkClass(long typeID, String signature, Map<String, String> result) {
-        CommandPacket packet = new CommandPacket(
-                JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
-                JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
-        packet.setNextValueAsReferenceTypeID(typeID);
-        ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
-        if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
-            return false;
-        }
-
-        long classLoaderID = reply.getNextValueAsObjectID();
-
-        // TODO: Investigate the classloader to have a better string?
-        String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
-
-        result.put(getClassName(signature), classLoaderString);
-
-        return true;
-    }
-
-    private boolean checkArrayClass(long typeID, String signature, Map<String, String> result) {
-        // Classloaders of array classes are the same as the component class'.
-        CommandPacket packet = new CommandPacket(
-                JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
-                JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
-        packet.setNextValueAsReferenceTypeID(typeID);
-        ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
-        if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
-            return false;
-        }
-
-        long classLoaderID = reply.getNextValueAsObjectID();
-
-        // TODO: Investigate the classloader to have a better string?
-        String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
-
-        // For array classes, we *need* the signature directly.
-        result.put(signature, classLoaderString);
-
-        return true;
-    }
-
-    private static String getClassName(String signature) {
-        String withoutLAndSemicolon = signature.substring(1, signature.length() - 1);
-        return withoutLAndSemicolon.replace('/', '.');
-    }
-
-
-    private static JPDATestOptions createTestOptions(String address) {
-        JPDATestOptions options = new JPDATestOptions();
-        options.setAttachConnectorKind();
-        options.setTimeout(1000);
-        options.setWaitingTime(1000);
-        options.setTransportAddress(address);
-        return options;
-    }
-
-    @Override
-    protected JDWPUnitDebuggeeWrapper createDebuggeeWrapper() {
-        return new PreloadDebugeeWrapper(settings, logWriter);
-    }
-}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java
deleted file mode 100644
index b9df6d0..0000000
--- a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.classdataretrieval.jdwp;
-
-import org.apache.harmony.jpda.tests.framework.LogWriter;
-import org.apache.harmony.jpda.tests.jdwp.share.JDWPManualDebuggeeWrapper;
-import org.apache.harmony.jpda.tests.share.JPDATestOptions;
-
-import java.io.IOException;
-
-public class PreloadDebugeeWrapper extends JDWPManualDebuggeeWrapper {
-
-    public PreloadDebugeeWrapper(JPDATestOptions options, LogWriter writer) {
-        super(options, writer);
-    }
-
-    @Override
-    protected Process launchProcess(String cmdLine) throws IOException {
-        return null;
-    }
-
-    @Override
-    protected void WaitForProcessExit(Process process) {
-    }
-
-}
diff --git a/tools/preload2/src/com/android/preload/ui/IUI.java b/tools/preload2/src/com/android/preload/ui/IUI.java
deleted file mode 100644
index 9371463..0000000
--- a/tools/preload2/src/com/android/preload/ui/IUI.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.android.preload.ui;
-
-import com.android.ddmlib.Client;
-import java.io.File;
-import java.util.List;
-import javax.swing.Action;
-import javax.swing.ListModel;
-import javax.swing.table.TableModel;
-
-/**
- * UI abstraction for the tool. This allows a graphical mode, command line mode,
- * or silent mode.
- */
-public interface IUI {
-
-    void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
-            List<Action> actions);
-
-    void ready();
-
-    boolean isSingleThreaded();
-
-    Client getSelectedClient();
-
-    int getSelectedDataTableRow();
-
-    void showWaitDialog();
-
-    void updateWaitDialog(String s);
-
-    void hideWaitDialog();
-
-    void showMessageDialog(String s);
-
-    boolean showConfirmDialog(String title, String message);
-
-    String showInputDialog(String message);
-
-    <T> T showChoiceDialog(String title, String message, T[] choices);
-
-    File showSaveDialog();
-
-    File[] showOpenDialog(boolean multi);
-
-}
diff --git a/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java
deleted file mode 100644
index f45aad0..0000000
--- a/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.ui;
-
-import com.android.ddmlib.SyncService.ISyncProgressMonitor;
-
-public class NullProgressMonitor implements ISyncProgressMonitor {
-
-    @Override
-    public void advance(int arg0) {}
-
-    @Override
-    public boolean isCanceled() {
-        return false;
-    }
-
-    @Override
-    public void start(int arg0) {}
-
-    @Override
-    public void startSubTask(String arg0) {}
-
-    @Override
-    public void stop() {}
-}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/ui/SequenceUI.java b/tools/preload2/src/com/android/preload/ui/SequenceUI.java
deleted file mode 100644
index dc6a4f3..0000000
--- a/tools/preload2/src/com/android/preload/ui/SequenceUI.java
+++ /dev/null
@@ -1,222 +0,0 @@
-package com.android.preload.ui;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.ClientData;
-import java.io.File;
-import java.util.LinkedList;
-import java.util.List;
-import javax.swing.Action;
-import javax.swing.ListModel;
-import javax.swing.table.TableModel;
-
-public class SequenceUI implements IUI {
-
-    private ListModel<Client> clientListModel;
-    @SuppressWarnings("unused")
-    private TableModel dataTableModel;
-    private List<Action> actions;
-
-    private List<Object> sequence = new LinkedList<>();
-
-    public SequenceUI() {
-    }
-
-    @Override
-    public boolean isSingleThreaded() {
-        return true;
-    }
-
-    @Override
-    public void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
-            List<Action> actions) {
-        this.clientListModel = clientListModel;
-        this.dataTableModel = dataTableModel;
-        this.actions = actions;
-    }
-
-    public SequenceUI action(Action a) {
-        sequence.add(a);
-        return this;
-    }
-
-    public SequenceUI action(Class<? extends Action> actionClass) {
-        for (Action a : actions) {
-            if (actionClass.equals(a.getClass())) {
-                sequence.add(a);
-                return this;
-            }
-        }
-        throw new IllegalArgumentException("No action of class " + actionClass + " found.");
-    }
-
-    public SequenceUI confirmYes() {
-        sequence.add(Boolean.TRUE);
-        return this;
-    }
-
-    public SequenceUI confirmNo() {
-        sequence.add(Boolean.FALSE);
-        return this;
-    }
-
-    public SequenceUI input(String input) {
-        sequence.add(input);
-        return this;
-    }
-
-    public SequenceUI input(File... f) {
-        sequence.add(f);
-        return this;
-    }
-
-    public SequenceUI output(File f) {
-        sequence.add(f);
-        return this;
-    }
-
-    public SequenceUI tableRow(int i) {
-        sequence.add(i);
-        return this;
-    }
-
-    private class ClientSelector {
-        private String pkg;
-
-        public ClientSelector(String pkg) {
-            this.pkg = pkg;
-        }
-
-        public Client getClient() {
-            for (int i = 0; i < clientListModel.getSize(); i++) {
-                ClientData cd = clientListModel.getElementAt(i).getClientData();
-                if (cd != null) {
-                    String s = cd.getClientDescription();
-                    if (pkg.equals(s)) {
-                        return clientListModel.getElementAt(i);
-                    }
-                }
-            }
-            throw new RuntimeException("Didn't find client " + pkg);
-        }
-    }
-
-    public SequenceUI client(String pkg) {
-        sequence.add(new ClientSelector(pkg));
-        return this;
-    }
-
-    public SequenceUI choice(String pattern) {
-        sequence.add(pattern);
-        return this;
-    }
-
-    @Override
-    public void ready() {
-        // Run the actions.
-        // No iterator or foreach loop as the sequence will be emptied while running.
-        try {
-            while (!sequence.isEmpty()) {
-                Object next = sequence.remove(0);
-                if (next instanceof Action) {
-                    ((Action)next).actionPerformed(null);
-                } else {
-                    throw new IllegalStateException("Didn't expect a non-action: " + next);
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace(System.out);
-        }
-
-        // Now shut down.
-        System.exit(0);
-    }
-
-    @Override
-    public Client getSelectedClient() {
-        Object next = sequence.remove(0);
-        if (next instanceof ClientSelector) {
-            return ((ClientSelector)next).getClient();
-        }
-        throw new IllegalStateException("Unexpected: " + next);
-    }
-
-    @Override
-    public int getSelectedDataTableRow() {
-        Object next = sequence.remove(0);
-        if (next instanceof Integer) {
-            return ((Integer)next).intValue();
-        }
-        throw new IllegalStateException("Unexpected: " + next);
-    }
-
-    @Override
-    public void showWaitDialog() {
-    }
-
-    @Override
-    public void updateWaitDialog(String s) {
-        System.out.println(s);
-    }
-
-    @Override
-    public void hideWaitDialog() {
-    }
-
-    @Override
-    public void showMessageDialog(String s) {
-        System.out.println(s);
-    }
-
-    @Override
-    public boolean showConfirmDialog(String title, String message) {
-        Object next = sequence.remove(0);
-        if (next instanceof Boolean) {
-            return ((Boolean)next).booleanValue();
-        }
-        throw new IllegalStateException("Unexpected: " + next);
-    }
-
-    @Override
-    public String showInputDialog(String message) {
-        Object next = sequence.remove(0);
-        if (next instanceof String) {
-            return (String)next;
-        }
-        throw new IllegalStateException("Unexpected: " + next);
-    }
-
-    @Override
-    public <T> T showChoiceDialog(String title, String message, T[] choices) {
-        Object next = sequence.remove(0);
-        if (next instanceof String) {
-            String s = (String)next;
-            for (T t : choices) {
-                if (t.toString().contains(s)) {
-                    return t;
-                }
-            }
-            return null;
-        }
-        throw new IllegalStateException("Unexpected: " + next);
-    }
-
-    @Override
-    public File showSaveDialog() {
-        Object next = sequence.remove(0);
-        if (next instanceof File) {
-            System.out.println(next);
-            return (File)next;
-        }
-        throw new IllegalStateException("Unexpected: " + next);
-    }
-
-    @Override
-    public File[] showOpenDialog(boolean multi) {
-        Object next = sequence.remove(0);
-        if (next instanceof File[]) {
-            return (File[])next;
-        }
-        throw new IllegalStateException("Unexpected: " + next);
-    }
-
-}
diff --git a/tools/preload2/src/com/android/preload/ui/SwingUI.java b/tools/preload2/src/com/android/preload/ui/SwingUI.java
deleted file mode 100644
index cab3744..0000000
--- a/tools/preload2/src/com/android/preload/ui/SwingUI.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.preload.ui;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.ClientData;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.io.File;
-import java.util.List;
-
-import javax.swing.Action;
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.JDialog;
-import javax.swing.JFileChooser;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
-import javax.swing.JProgressBar;
-import javax.swing.JScrollPane;
-import javax.swing.JTable;
-import javax.swing.JToolBar;
-import javax.swing.ListModel;
-import javax.swing.SwingUtilities;
-import javax.swing.table.TableModel;
-
-public class SwingUI extends JFrame implements IUI {
-
-    private JList<Client> clientList;
-    private JTable dataTable;
-
-    // Shared file chooser, means the directory is retained.
-    private JFileChooser jfc;
-
-    public SwingUI() {
-        super("Preloaded-classes computation");
-    }
-
-    @Override
-    public boolean isSingleThreaded() {
-        return false;
-    }
-
-    @Override
-    public void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
-            List<Action> actions) {
-        getContentPane().add(new JScrollPane(clientList = new JList<Client>(clientListModel)),
-                BorderLayout.WEST);
-        clientList.setCellRenderer(new ClientListCellRenderer());
-        // clientList.addListSelectionListener(listener);
-
-        dataTable = new JTable(dataTableModel);
-        getContentPane().add(new JScrollPane(dataTable), BorderLayout.CENTER);
-
-        JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL);
-        for (Action a : actions) {
-            if (a == null) {
-                toolbar.addSeparator();
-            } else {
-                toolbar.add(a);
-            }
-        }
-        getContentPane().add(toolbar, BorderLayout.PAGE_START);
-
-        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-        setBounds(100, 100, 800, 600);
-
-        setVisible(true);
-    }
-
-    @Override
-    public void ready() {
-    }
-
-    @Override
-    public Client getSelectedClient() {
-        return clientList.getSelectedValue();
-    }
-
-    @Override
-    public int getSelectedDataTableRow() {
-        return dataTable.getSelectedRow();
-    }
-
-    private JDialog currentWaitDialog = null;
-
-    @Override
-    public void showWaitDialog() {
-        if (currentWaitDialog == null) {
-            currentWaitDialog = new JDialog(this, "Please wait...", true);
-            currentWaitDialog.getContentPane().add(new JLabel("Please be patient."),
-                    BorderLayout.CENTER);
-            JProgressBar progress = new JProgressBar(JProgressBar.HORIZONTAL);
-            progress.setIndeterminate(true);
-            currentWaitDialog.getContentPane().add(progress, BorderLayout.SOUTH);
-            currentWaitDialog.setSize(200, 100);
-            currentWaitDialog.setLocationRelativeTo(null);
-            showWaitDialogLater();
-        }
-    }
-
-    private void showWaitDialogLater() {
-        SwingUtilities.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                if (currentWaitDialog != null) {
-                    currentWaitDialog.setVisible(true); // This is blocking.
-                }
-            }
-        });
-    }
-
-    @Override
-    public void updateWaitDialog(String s) {
-        if (currentWaitDialog != null) {
-            ((JLabel) currentWaitDialog.getContentPane().getComponent(0)).setText(s);
-            Dimension prefSize = currentWaitDialog.getPreferredSize();
-            Dimension curSize = currentWaitDialog.getSize();
-            if (prefSize.width > curSize.width || prefSize.height > curSize.height) {
-                currentWaitDialog.setSize(Math.max(prefSize.width, curSize.width),
-                        Math.max(prefSize.height, curSize.height));
-                currentWaitDialog.invalidate();
-            }
-        }
-    }
-
-    @Override
-    public void hideWaitDialog() {
-        if (currentWaitDialog != null) {
-            currentWaitDialog.setVisible(false);
-            currentWaitDialog = null;
-        }
-    }
-
-    @Override
-    public void showMessageDialog(String s) {
-        // Hide the wait dialog...
-        if (currentWaitDialog != null) {
-            currentWaitDialog.setVisible(false);
-        }
-
-        try {
-            JOptionPane.showMessageDialog(this, s);
-        } finally {
-            // And reshow it afterwards...
-            if (currentWaitDialog != null) {
-                showWaitDialogLater();
-            }
-        }
-    }
-
-    @Override
-    public boolean showConfirmDialog(String title, String message) {
-        // Hide the wait dialog...
-        if (currentWaitDialog != null) {
-            currentWaitDialog.setVisible(false);
-        }
-
-        try {
-            return JOptionPane.showConfirmDialog(this, title, message, JOptionPane.YES_NO_OPTION)
-                    == JOptionPane.YES_OPTION;
-        } finally {
-            // And reshow it afterwards...
-            if (currentWaitDialog != null) {
-                showWaitDialogLater();
-            }
-        }
-    }
-
-    @Override
-    public String showInputDialog(String message) {
-        // Hide the wait dialog...
-        if (currentWaitDialog != null) {
-            currentWaitDialog.setVisible(false);
-        }
-
-        try {
-            return JOptionPane.showInputDialog(message);
-        } finally {
-            // And reshow it afterwards...
-            if (currentWaitDialog != null) {
-                showWaitDialogLater();
-            }
-        }
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public <T> T showChoiceDialog(String title, String message, T[] choices) {
-        // Hide the wait dialog...
-        if (currentWaitDialog != null) {
-            currentWaitDialog.setVisible(false);
-        }
-
-        try{
-            return (T)JOptionPane.showInputDialog(this,
-                    title,
-                    message,
-                    JOptionPane.QUESTION_MESSAGE,
-                    null,
-                    choices,
-                    choices[0]);
-        } finally {
-            // And reshow it afterwards...
-            if (currentWaitDialog != null) {
-                showWaitDialogLater();
-            }
-        }
-    }
-
-    @Override
-    public File showSaveDialog() {
-        // Hide the wait dialog...
-        if (currentWaitDialog != null) {
-            currentWaitDialog.setVisible(false);
-        }
-
-        try{
-            if (jfc == null) {
-                jfc = new JFileChooser();
-            }
-
-            int ret = jfc.showSaveDialog(this);
-            if (ret == JFileChooser.APPROVE_OPTION) {
-                return jfc.getSelectedFile();
-            } else {
-                return null;
-            }
-        } finally {
-            // And reshow it afterwards...
-            if (currentWaitDialog != null) {
-                showWaitDialogLater();
-            }
-        }
-    }
-
-    @Override
-    public File[] showOpenDialog(boolean multi) {
-        // Hide the wait dialog...
-        if (currentWaitDialog != null) {
-            currentWaitDialog.setVisible(false);
-        }
-
-        try{
-            if (jfc == null) {
-                jfc = new JFileChooser();
-            }
-
-            jfc.setMultiSelectionEnabled(multi);
-            int ret = jfc.showOpenDialog(this);
-            if (ret == JFileChooser.APPROVE_OPTION) {
-                return jfc.getSelectedFiles();
-            } else {
-                return null;
-            }
-        } finally {
-            // And reshow it afterwards...
-            if (currentWaitDialog != null) {
-                showWaitDialogLater();
-            }
-        }
-    }
-
-    private class ClientListCellRenderer extends DefaultListCellRenderer {
-
-        @Override
-        public Component getListCellRendererComponent(JList<?> list, Object value, int index,
-                boolean isSelected, boolean cellHasFocus) {
-            ClientData cd = ((Client) value).getClientData();
-            String s = cd.getClientDescription() + " (pid " + cd.getPid() + ")";
-            return super.getListCellRendererComponent(list, s, index, isSelected, cellHasFocus);
-        }
-    }
-}
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
index 9ab374a..6a534b9 100644
--- a/wifi/java/android/net/wifi/RttManager.java
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -211,6 +211,7 @@
         /** Draft 11mc version supported, including major and minor version. e.g, draft 4.3 is 43 */
         public int mcVersion;
 
+        @NonNull
         @Override
         public String toString() {
             StringBuffer sb = new StringBuffer();
@@ -1130,6 +1131,7 @@
          */
         public int preamble;
 
+        @NonNull
         @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 2aed2de0..70e5160 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2563,6 +2563,7 @@
 
         private final CloseGuard mCloseGuard = CloseGuard.get();
         private final WifiConfiguration mConfig;
+        private boolean mClosed = false;
 
         /** @hide */
         @VisibleForTesting
@@ -2578,8 +2579,13 @@
         @Override
         public void close() {
             try {
-                stopLocalOnlyHotspot();
-                mCloseGuard.close();
+                synchronized (mLock) {
+                    if (!mClosed) {
+                        mClosed = true;
+                        stopLocalOnlyHotspot();
+                        mCloseGuard.close();
+                    }
+                }
             } catch (Exception e) {
                 Log.e(TAG, "Failed to stop Local Only Hotspot.");
             }
diff --git a/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java b/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java
index 9b2fdc8..a94a610 100644
--- a/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java
+++ b/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java
@@ -16,8 +16,8 @@
 
 package android.net.wifi;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
-
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -39,7 +39,7 @@
 
     public WifiNetworkConnectionStatistics() { }
 
-
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sbuf = new StringBuilder();
diff --git a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
index 25dcdd8..4fa8b48 100644
--- a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
+++ b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
@@ -16,6 +16,8 @@
 
 package android.net.wifi.hotspot2;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.net.wifi.WifiSsid;
@@ -171,7 +173,7 @@
     }
 
     @Override
-    public boolean equals(Object thatObject) {
+    public boolean equals(@Nullable Object thatObject) {
         if (this == thatObject) {
             return true;
         }
@@ -196,6 +198,7 @@
                 mNetworkAccessIdentifier, mMethodList, mIcon);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "OsuProvider{mOsuSsid=" + mOsuSsid
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
index 339233b..058b488 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.Nullable;
 import android.annotation.SystemApi;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
@@ -245,7 +246,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) {
             return true;
         }
diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
index 166af6c..3ca0c17 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
@@ -21,6 +21,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
@@ -443,7 +444,7 @@
     };
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) {
             return true;
         }