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 > 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 à <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> 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’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> ®istered) {
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;
}