summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp5
-rw-r--r--Android.mk8
-rw-r--r--apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java43
-rw-r--r--api/current.txt243
-rw-r--r--api/system-current.txt33
-rw-r--r--api/test-current.txt8
-rw-r--r--cmds/statsd/Android.mk2
-rw-r--r--cmds/statsd/src/StatsService.cpp12
-rw-r--r--cmds/statsd/src/condition/SimpleConditionTracker.cpp23
-rw-r--r--cmds/statsd/src/dimension.cpp8
-rw-r--r--cmds/statsd/src/guardrail/StatsdStats.cpp174
-rw-r--r--cmds/statsd/src/guardrail/StatsdStats.h5
-rw-r--r--cmds/statsd/src/packages/UidMap.cpp6
-rw-r--r--cmds/statsd/src/packages/UidMap.h4
-rw-r--r--cmds/statsd/src/stats_log.proto16
-rw-r--r--cmds/statsd/src/statsd_config.proto24
-rw-r--r--cmds/statsd/src/storage/StorageManager.cpp56
-rw-r--r--cmds/statsd/statsd.rc1
-rw-r--r--cmds/statsd/tests/ConfigManager_test.cpp2
-rw-r--r--cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp2
-rw-r--r--core/java/android/app/ActivityView.java37
-rw-r--r--core/java/android/app/SystemServiceRegistry.java4
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java46
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl4
-rw-r--r--core/java/android/app/job/JobInfo.java27
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/content/Intent.java71
-rw-r--r--core/java/android/content/pm/CrossProfileApps.java (renamed from core/java/android/content/pm/crossprofile/CrossProfileApps.java)15
-rw-r--r--core/java/android/content/pm/ICrossProfileApps.aidl (renamed from core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl)2
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/content/pm/PackageManagerInternal.java5
-rw-r--r--core/java/android/content/pm/PackageParser.java5
-rw-r--r--core/java/android/content/pm/dex/DexMetadataHelper.java230
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java19
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java14
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java2
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java10
-rw-r--r--core/java/android/net/INetworkPolicyListener.aidl3
-rw-r--r--core/java/android/net/INetworkPolicyManager.aidl1
-rw-r--r--core/java/android/net/NetworkPolicyManager.java12
-rw-r--r--core/java/android/provider/Settings.java408
-rw-r--r--core/java/android/provider/SettingsValidators.java169
-rw-r--r--core/java/android/security/keystore/KeychainSnapshot.java84
-rw-r--r--core/java/android/security/keystore/RecoveryController.java (renamed from core/java/android/security/keystore/RecoveryManager.java)32
-rw-r--r--core/java/android/security/keystore/RecoveryControllerException.java2
-rw-r--r--core/java/android/security/keystore/RecoverySession.java12
-rw-r--r--core/java/android/service/autofill/AutofillFieldClassificationService.java284
-rw-r--r--core/java/android/service/autofill/CharSequenceTransformation.java28
-rw-r--r--core/java/android/service/autofill/IAutofillFieldClassificationService.aidl34
-rw-r--r--core/java/android/text/MeasuredParagraph.java10
-rw-r--r--core/java/android/text/MeasuredText.java197
-rw-r--r--core/java/android/text/StaticLayout.java34
-rw-r--r--core/java/android/util/DataUnit.java11
-rw-r--r--core/java/android/view/Choreographer.java3
-rw-r--r--core/java/android/view/IWindowSession.aidl40
-rw-r--r--core/java/android/view/SurfaceControl.aidl19
-rw-r--r--core/java/android/view/View.java79
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java33
-rw-r--r--core/java/android/view/autofill/AutofillManager.java91
-rw-r--r--core/java/android/view/autofill/IAutoFillManager.aidl5
-rw-r--r--core/java/android/widget/EditText.java4
-rw-r--r--core/java/android/widget/MediaControlView2.java (renamed from core/java/android/widget/MediaController2.java)127
-rw-r--r--core/java/android/widget/TextView.java30
-rw-r--r--core/java/android/widget/VideoView2.java149
-rw-r--r--core/java/com/android/internal/content/PackageHelper.java4
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java4
-rw-r--r--core/java/com/android/internal/widget/ILockSettings.aidl2
-rw-r--r--core/jni/android/graphics/ImageDecoder.cpp2
-rw-r--r--core/jni/android_os_HwParcel.cpp4
-rw-r--r--core/jni/android_text_MeasuredParagraph.cpp6
-rw-r--r--core/proto/android/server/forceappstandbytracker.proto9
-rw-r--r--core/res/AndroidManifest.xml9
-rw-r--r--core/res/res/values/attrs.xml2
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/public.xml1
-rw-r--r--core/res/res/values/strings.xml8
-rw-r--r--core/res/res/values/symbols.xml5
-rw-r--r--core/tests/coretests/apks/install-split-base/Android.mk10
-rw-r--r--core/tests/coretests/apks/install-split-base/AndroidManifest.xml29
-rw-r--r--core/tests/coretests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java23
-rw-r--r--core/tests/coretests/apks/install-split-feature-a/Android.mk14
-rw-r--r--core/tests/coretests/apks/install-split-feature-a/AndroidManifest.xml29
-rw-r--r--core/tests/coretests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java23
-rw-r--r--core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java (renamed from core/tests/coretests/src/android/content/pm/crossprofile/CrossProfileAppsTest.java)10
-rw-r--r--core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java208
-rw-r--r--core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java80
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java1
-rw-r--r--core/tests/coretests/src/android/provider/SettingsValidatorsTest.java69
-rw-r--r--core/tests/coretests/src/android/text/MeasuredParagraphTest.java4
-rw-r--r--graphics/java/android/graphics/Typeface.java4
-rw-r--r--media/java/android/media/update/ApiLoader.java20
-rw-r--r--media/java/android/media/update/MediaControlView2Provider.java (renamed from media/java/android/media/update/MediaController2Provider.java)9
-rw-r--r--media/java/android/media/update/StaticProvider.java12
-rw-r--r--media/java/android/media/update/VideoView2Provider.java16
-rw-r--r--media/java/android/media/update/ViewProvider.java4
-rw-r--r--native/graphics/jni/Android.bp7
-rw-r--r--packages/ExtServices/AndroidManifest.xml7
-rw-r--r--packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java81
-rw-r--r--packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java (renamed from core/java/android/service/autofill/EditDistanceScorer.java)12
-rw-r--r--packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java77
-rw-r--r--packages/SettingsLib/res/values/strings.xml4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java40
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java11
-rw-r--r--packages/VpnDialogs/res/values-hi/strings.xml2
-rw-r--r--proto/src/system_messages.proto3
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerService.java75
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java165
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java56
-rw-r--r--services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java279
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java76
-rw-r--r--services/backup/java/com/android/server/backup/TransportManager.java89
-rw-r--r--services/core/java/com/android/server/BatteryService.java8
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java10
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java9
-rw-r--r--services/core/java/com/android/server/ForceAppStandbyTracker.java164
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java57
-rw-r--r--services/core/java/com/android/server/LocationManagerService.java41
-rw-r--r--services/core/java/com/android/server/am/UserController.java12
-rwxr-xr-x[-rw-r--r--]services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java23
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java18
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java8
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiUtils.java26
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java4
-rw-r--r--services/core/java/com/android/server/hdmi/VolumeControlAction.java4
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerInternal.java9
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java31
-rw-r--r--services/core/java/com/android/server/job/controllers/ConnectivityController.java120
-rw-r--r--services/core/java/com/android/server/job/controllers/JobStatus.java57
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java20
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java2
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java16
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java8
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java19
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java222
-rw-r--r--services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java16
-rw-r--r--services/core/java/com/android/server/net/watchlist/PrivacyUtils.java103
-rw-r--r--services/core/java/com/android/server/net/watchlist/ReportEncoder.java126
-rw-r--r--services/core/java/com/android/server/net/watchlist/WatchlistConfig.java245
-rw-r--r--services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java131
-rw-r--r--services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java31
-rw-r--r--services/core/java/com/android/server/net/watchlist/WatchlistSettings.java202
-rw-r--r--services/core/java/com/android/server/pm/CrossProfileAppsService.java (renamed from services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsService.java)2
-rw-r--r--services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java (renamed from services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java)6
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java83
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java17
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java10
-rw-r--r--services/core/java/com/android/server/vr/VrManagerService.java36
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java16
-rw-r--r--services/core/java/com/android/server/wm/DockedStackDividerController.java5
-rw-r--r--services/core/java/com/android/server/wm/DragDropController.java150
-rw-r--r--services/core/java/com/android/server/wm/Session.java30
-rw-r--r--services/core/java/com/android/server/wm/TapExcludeRegionHolder.java56
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioner.java21
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioningController.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskStack.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java17
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java41
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java15
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java103
-rw-r--r--services/java/com/android/server/SystemServer.java10
-rw-r--r--services/print/java/com/android/server/print/PrintManagerService.java33
-rw-r--r--services/robotests/src/com/android/server/backup/TransportManagerTest.java68
-rw-r--r--services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_config_test1.xml27
-rw-r--r--services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml29
-rw-r--r--services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test2.xml4
-rw-r--r--services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java49
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java75
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobStoreTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java169
-rw-r--r--services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java76
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/net/watchlist/PrivacyUtilsTests.java105
-rw-r--r--services/tests/servicestests/src/com/android/server/net/watchlist/ReportUtilsTests.java119
-rw-r--r--services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistConfigTests.java147
-rw-r--r--services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java101
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java (renamed from services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java)7
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java22
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java75
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java90
-rw-r--r--telephony/java/android/telephony/ims/feature/MMTelFeature.java71
-rw-r--r--telephony/java/android/telephony/ims/internal/SmsImplBase.java17
-rw-r--r--telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java271
-rw-r--r--telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl8
-rw-r--r--telephony/java/com/android/ims/internal/IImsSmsListener.aidl28
-rw-r--r--telephony/java/com/android/internal/telephony/IccCardConstants.java14
-rw-r--r--tests/UiBench/src/com/android/test/uibench/leanback/BrowseFragment.java1
-rw-r--r--tests/UiBench/src/com/android/test/uibench/leanback/TestHelper.java9
-rw-r--r--tools/aapt2/ResourceTable.cpp23
-rw-r--r--tools/aapt2/ResourceTable.h8
-rw-r--r--tools/aapt2/Resources.proto21
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.cpp66
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize.cpp47
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize_test.cpp105
-rwxr-xr-xtools/fonts/fontchain_linter.py3
-rw-r--r--wifi/java/android/net/wifi/ISoftApCallback.aidl44
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl18
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java135
-rw-r--r--wifi/tests/src/android/net/wifi/WifiManagerTest.java153
214 files changed, 7464 insertions, 2038 deletions
diff --git a/Android.bp b/Android.bp
index ae4f97411fd4..19c0580e21d2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -117,7 +117,7 @@ java_library {
"core/java/android/content/ISyncServiceAdapter.aidl",
"core/java/android/content/ISyncStatusObserver.aidl",
"core/java/android/content/om/IOverlayManager.aidl",
- "core/java/android/content/pm/crossprofile/ICrossProfileApps.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",
@@ -242,6 +242,7 @@ java_library {
"core/java/android/security/IKeystoreService.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",
@@ -500,6 +501,7 @@ java_library {
"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/IImsSmsListener.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",
@@ -547,6 +549,7 @@ java_library {
"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",
diff --git a/Android.mk b/Android.mk
index 73c1ccdd05fa..298b85ddf084 100644
--- a/Android.mk
+++ b/Android.mk
@@ -72,12 +72,6 @@ non_base_dirs := \
../opt/net/voip/src/java/android/net/rtp \
../opt/net/voip/src/java/android/net/sip \
-framework_base_android_test_base_src_files := \
- $(call all-java-files-under, test-base/src/junit)
-
-framework_base_android_test_runner_src_files := \
- $(call all-java-files-under, test-runner/src/junit)
-
# Find all files in specific directories (relative to frameworks/base)
# to document and check apis
files_to_check_apis := \
@@ -126,8 +120,6 @@ framework_docs_LOCAL_SRC_FILES := \
# These are relative to frameworks/base
framework_docs_LOCAL_API_CHECK_SRC_FILES := \
- $(framework_base_android_test_base_src_files) \
- $(framework_base_android_test_runner_src_files) \
$(files_to_check_apis) \
$(common_src_files) \
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 93a0fc314b7f..6975609d990a 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -29,6 +29,7 @@ import android.graphics.Typeface;
import android.text.Layout;
import android.text.style.TextAppearanceSpan;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,7 +53,7 @@ public class StaticLayoutPerfTest {
private static final boolean NO_STYLE_TEXT = false;
private static final boolean STYLE_TEXT = true;
- private final Random mRandom = new Random(31415926535L);
+ private Random mRandom;
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final int ALPHABET_LENGTH = ALPHABET.length();
@@ -98,6 +99,11 @@ public class StaticLayoutPerfTest {
return ssb;
}
+ @Before
+ public void setUp() {
+ mRandom = new Random(0);
+ }
+
@Test
public void testCreate_FixedText_NoStyle_Greedy_NoHyphenation() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -190,8 +196,11 @@ public class StaticLayoutPerfTest {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final MeasuredText text = MeasuredText.build(
- generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
+ final MeasuredText text = new MeasuredText.Builder(
+ generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+ .build();
state.resumeTiming();
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -206,8 +215,11 @@ public class StaticLayoutPerfTest {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final MeasuredText text = MeasuredText.build(
- generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
+ final MeasuredText text = new MeasuredText.Builder(
+ generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .build();
state.resumeTiming();
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -222,8 +234,11 @@ public class StaticLayoutPerfTest {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final MeasuredText text = MeasuredText.build(
- generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
+ final MeasuredText text = new MeasuredText.Builder(
+ generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+ .build();
state.resumeTiming();
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -238,8 +253,11 @@ public class StaticLayoutPerfTest {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final MeasuredText text = MeasuredText.build(
- generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
+ final MeasuredText text = new MeasuredText.Builder(
+ generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .build();
state.resumeTiming();
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -254,8 +272,11 @@ public class StaticLayoutPerfTest {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final MeasuredText text = MeasuredText.build(
- generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR);
+ final MeasuredText text = new MeasuredText.Builder(
+ generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+ .build();
state.resumeTiming();
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
diff --git a/api/current.txt b/api/current.txt
index 847671ead669..c065fe98bce4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -211,6 +211,7 @@ package android {
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
field public static final int accessibilityFlags = 16843652; // 0x1010384
+ field public static final int accessibilityHeading = 16844160; // 0x1010580
field public static final int accessibilityLiveRegion = 16843758; // 0x10103ee
field public static final int accessibilityPaneTitle = 16844156; // 0x101057c
field public static final int accessibilityTraversalAfter = 16843986; // 0x10104d2
@@ -6991,6 +6992,7 @@ package android.app.job {
method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
+ method public android.app.job.JobInfo.Builder setIsPrefetch(boolean);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -10687,6 +10689,13 @@ package android.content.pm {
field public int reqTouchScreen;
}
+ public class CrossProfileApps {
+ method public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(android.os.UserHandle);
+ method public java.lang.CharSequence getProfileSwitchingLabel(android.os.UserHandle);
+ method public java.util.List<android.os.UserHandle> getTargetUserProfiles();
+ method public void startMainActivity(android.content.ComponentName, android.os.UserHandle);
+ }
+
public final class FeatureGroupInfo implements android.os.Parcelable {
ctor public FeatureGroupInfo();
ctor public FeatureGroupInfo(android.content.pm.FeatureGroupInfo);
@@ -11481,17 +11490,6 @@ package android.content.pm {
}
-package android.content.pm.crossprofile {
-
- public class CrossProfileApps {
- method public android.graphics.drawable.Drawable getProfileSwitchingIcon(android.os.UserHandle);
- method public java.lang.CharSequence getProfileSwitchingLabel(android.os.UserHandle);
- method public java.util.List<android.os.UserHandle> getTargetUserProfiles();
- method public void startMainActivity(android.content.ComponentName, android.os.UserHandle);
- }
-
-}
-
package android.content.res {
public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable {
@@ -15729,6 +15727,7 @@ package android.hardware.camera2 {
field public static final int CONTROL_AE_MODE_ON_ALWAYS_FLASH = 3; // 0x3
field public static final int CONTROL_AE_MODE_ON_AUTO_FLASH = 2; // 0x2
field public static final int CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4; // 0x4
+ field public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5; // 0x5
field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL = 2; // 0x2
field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0; // 0x0
field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1; // 0x1
@@ -42320,10 +42319,10 @@ package android.text {
}
public class MeasuredText implements android.text.Spanned {
- method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic);
- method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int);
method public char charAt(int);
+ method public int getBreakStrategy();
method public int getEnd();
+ method public int getHyphenationFrequency();
method public android.text.TextPaint getPaint();
method public int getParagraphCount();
method public int getParagraphEnd(int);
@@ -42340,6 +42339,15 @@ package android.text {
method public java.lang.CharSequence subSequence(int, int);
}
+ public static final class MeasuredText.Builder {
+ ctor public MeasuredText.Builder(java.lang.CharSequence, android.text.TextPaint);
+ method public android.text.MeasuredText build();
+ method public android.text.MeasuredText.Builder setBreakStrategy(int);
+ method public android.text.MeasuredText.Builder setHyphenationFrequency(int);
+ method public android.text.MeasuredText.Builder setRange(int, int);
+ method public android.text.MeasuredText.Builder setTextDirection(android.text.TextDirectionHeuristic);
+ }
+
public abstract interface NoCopySpan {
}
@@ -44076,6 +44084,18 @@ package android.util {
field public static final deprecated boolean RELEASE = true;
}
+ public class DataUnit extends java.lang.Enum {
+ method public long toBytes(long);
+ method public static android.util.DataUnit valueOf(java.lang.String);
+ method public static final android.util.DataUnit[] values();
+ enum_constant public static final android.util.DataUnit GIBIBYTES;
+ enum_constant public static final android.util.DataUnit GIGABYTES;
+ enum_constant public static final android.util.DataUnit KIBIBYTES;
+ enum_constant public static final android.util.DataUnit KILOBYTES;
+ enum_constant public static final android.util.DataUnit MEBIBYTES;
+ enum_constant public static final android.util.DataUnit MEGABYTES;
+ }
+
public class DebugUtils {
method public static boolean isObjectSelected(java.lang.Object);
}
@@ -48196,6 +48216,7 @@ package android.view.accessibility {
method public boolean isEnabled();
method public boolean isFocusable();
method public boolean isFocused();
+ method public boolean isHeading();
method public boolean isImportantForAccessibility();
method public boolean isLongClickable();
method public boolean isMultiLine();
@@ -48239,6 +48260,7 @@ package android.view.accessibility {
method public void setError(java.lang.CharSequence);
method public void setFocusable(boolean);
method public void setFocused(boolean);
+ method public void setHeading(boolean);
method public void setHintText(java.lang.CharSequence);
method public void setImportantForAccessibility(boolean);
method public void setInputType(int);
@@ -48372,7 +48394,7 @@ package android.view.accessibility {
method public int getColumnSpan();
method public int getRowIndex();
method public int getRowSpan();
- method public boolean isHeading();
+ method public deprecated boolean isHeading();
method public boolean isSelected();
method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
@@ -52597,6 +52619,7 @@ package android.widget {
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
+ method public boolean isAccessibilityHeading();
method public boolean isAllCaps();
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
@@ -52619,6 +52642,7 @@ package android.widget {
method protected void onTextChanged(java.lang.CharSequence, int, int, int);
method public boolean onTextContextMenuItem(int);
method public void removeTextChangedListener(android.text.TextWatcher);
+ method public void setAccessibilityHeading(boolean);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
method public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int);
@@ -72420,197 +72444,6 @@ package javax.xml.xpath {
}
-package junit.framework {
-
- public class Assert {
- ctor protected Assert();
- method public static void assertEquals(java.lang.String, java.lang.Object, java.lang.Object);
- method public static void assertEquals(java.lang.Object, java.lang.Object);
- method public static void assertEquals(java.lang.String, java.lang.String, java.lang.String);
- method public static void assertEquals(java.lang.String, java.lang.String);
- method public static void assertEquals(java.lang.String, double, double, double);
- method public static void assertEquals(double, double, double);
- method public static void assertEquals(java.lang.String, float, float, float);
- method public static void assertEquals(float, float, float);
- method public static void assertEquals(java.lang.String, long, long);
- method public static void assertEquals(long, long);
- method public static void assertEquals(java.lang.String, boolean, boolean);
- method public static void assertEquals(boolean, boolean);
- method public static void assertEquals(java.lang.String, byte, byte);
- method public static void assertEquals(byte, byte);
- method public static void assertEquals(java.lang.String, char, char);
- method public static void assertEquals(char, char);
- method public static void assertEquals(java.lang.String, short, short);
- method public static void assertEquals(short, short);
- method public static void assertEquals(java.lang.String, int, int);
- method public static void assertEquals(int, int);
- method public static void assertFalse(java.lang.String, boolean);
- method public static void assertFalse(boolean);
- method public static void assertNotNull(java.lang.Object);
- method public static void assertNotNull(java.lang.String, java.lang.Object);
- method public static void assertNotSame(java.lang.String, java.lang.Object, java.lang.Object);
- method public static void assertNotSame(java.lang.Object, java.lang.Object);
- method public static void assertNull(java.lang.Object);
- method public static void assertNull(java.lang.String, java.lang.Object);
- method public static void assertSame(java.lang.String, java.lang.Object, java.lang.Object);
- method public static void assertSame(java.lang.Object, java.lang.Object);
- method public static void assertTrue(java.lang.String, boolean);
- method public static void assertTrue(boolean);
- method public static void fail(java.lang.String);
- method public static void fail();
- method public static void failNotEquals(java.lang.String, java.lang.Object, java.lang.Object);
- method public static void failNotSame(java.lang.String, java.lang.Object, java.lang.Object);
- method public static void failSame(java.lang.String);
- method public static java.lang.String format(java.lang.String, java.lang.Object, java.lang.Object);
- }
-
- public class AssertionFailedError extends java.lang.AssertionError {
- ctor public AssertionFailedError();
- ctor public AssertionFailedError(java.lang.String);
- }
-
- public class ComparisonFailure extends junit.framework.AssertionFailedError {
- ctor public ComparisonFailure(java.lang.String, java.lang.String, java.lang.String);
- method public java.lang.String getActual();
- method public java.lang.String getExpected();
- }
-
- public abstract interface Protectable {
- method public abstract void protect() throws java.lang.Throwable;
- }
-
- public abstract interface Test {
- method public abstract int countTestCases();
- method public abstract void run(junit.framework.TestResult);
- }
-
- public abstract class TestCase extends junit.framework.Assert implements junit.framework.Test {
- ctor public TestCase();
- ctor public TestCase(java.lang.String);
- method public int countTestCases();
- method protected junit.framework.TestResult createResult();
- method public java.lang.String getName();
- method public junit.framework.TestResult run();
- method public void run(junit.framework.TestResult);
- method public void runBare() throws java.lang.Throwable;
- method protected void runTest() throws java.lang.Throwable;
- method public void setName(java.lang.String);
- method protected void setUp() throws java.lang.Exception;
- method protected void tearDown() throws java.lang.Exception;
- }
-
- public class TestFailure {
- ctor public TestFailure(junit.framework.Test, java.lang.Throwable);
- method public java.lang.String exceptionMessage();
- method public junit.framework.Test failedTest();
- method public boolean isFailure();
- method public java.lang.Throwable thrownException();
- method public java.lang.String trace();
- field protected junit.framework.Test fFailedTest;
- field protected java.lang.Throwable fThrownException;
- }
-
- public abstract interface TestListener {
- method public abstract void addError(junit.framework.Test, java.lang.Throwable);
- method public abstract void addFailure(junit.framework.Test, junit.framework.AssertionFailedError);
- method public abstract void endTest(junit.framework.Test);
- method public abstract void startTest(junit.framework.Test);
- }
-
- public class TestResult {
- ctor public TestResult();
- method public synchronized void addError(junit.framework.Test, java.lang.Throwable);
- method public synchronized void addFailure(junit.framework.Test, junit.framework.AssertionFailedError);
- method public synchronized void addListener(junit.framework.TestListener);
- method public void endTest(junit.framework.Test);
- method public synchronized int errorCount();
- method public synchronized java.util.Enumeration<junit.framework.TestFailure> errors();
- method public synchronized int failureCount();
- method public synchronized java.util.Enumeration<junit.framework.TestFailure> failures();
- method public synchronized void removeListener(junit.framework.TestListener);
- method protected void run(junit.framework.TestCase);
- method public synchronized int runCount();
- method public void runProtected(junit.framework.Test, junit.framework.Protectable);
- method public synchronized boolean shouldStop();
- method public void startTest(junit.framework.Test);
- method public synchronized void stop();
- method public synchronized boolean wasSuccessful();
- field protected java.util.Vector<junit.framework.TestFailure> fErrors;
- field protected java.util.Vector<junit.framework.TestFailure> fFailures;
- field protected java.util.Vector<junit.framework.TestListener> fListeners;
- field protected int fRunTests;
- }
-
- public class TestSuite implements junit.framework.Test {
- ctor public TestSuite();
- ctor public TestSuite(java.lang.Class<?>);
- ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>, java.lang.String);
- ctor public TestSuite(java.lang.String);
- ctor public TestSuite(java.lang.Class<?>...);
- ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>[], java.lang.String);
- method public void addTest(junit.framework.Test);
- method public void addTestSuite(java.lang.Class<? extends junit.framework.TestCase>);
- method public int countTestCases();
- method public static junit.framework.Test createTest(java.lang.Class<?>, java.lang.String);
- method public java.lang.String getName();
- method public static java.lang.reflect.Constructor<?> getTestConstructor(java.lang.Class<?>) throws java.lang.NoSuchMethodException;
- method public void run(junit.framework.TestResult);
- method public void runTest(junit.framework.Test, junit.framework.TestResult);
- method public void setName(java.lang.String);
- method public junit.framework.Test testAt(int);
- method public int testCount();
- method public java.util.Enumeration<junit.framework.Test> tests();
- method public static junit.framework.Test warning(java.lang.String);
- }
-
-}
-
-package junit.runner {
-
- public abstract class BaseTestRunner implements junit.framework.TestListener {
- ctor public BaseTestRunner();
- method public synchronized void addError(junit.framework.Test, java.lang.Throwable);
- method public synchronized void addFailure(junit.framework.Test, junit.framework.AssertionFailedError);
- method protected void clearStatus();
- method public java.lang.String elapsedTimeAsString(long);
- method public synchronized void endTest(junit.framework.Test);
- method public java.lang.String extractClassName(java.lang.String);
- method public static java.lang.String getFilteredTrace(java.lang.Throwable);
- method public static java.lang.String getFilteredTrace(java.lang.String);
- method public deprecated junit.runner.TestSuiteLoader getLoader();
- method public static java.lang.String getPreference(java.lang.String);
- method public static int getPreference(java.lang.String, int);
- method protected static java.util.Properties getPreferences();
- method public junit.framework.Test getTest(java.lang.String);
- method public static deprecated boolean inVAJava();
- method protected java.lang.Class<?> loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
- method protected java.lang.String processArguments(java.lang.String[]);
- method protected abstract void runFailed(java.lang.String);
- method public static void savePreferences() throws java.io.IOException;
- method public void setLoading(boolean);
- method public void setPreference(java.lang.String, java.lang.String);
- method protected static void setPreferences(java.util.Properties);
- method protected static boolean showStackRaw();
- method public synchronized void startTest(junit.framework.Test);
- method public abstract void testEnded(java.lang.String);
- method public abstract void testFailed(int, junit.framework.Test, java.lang.Throwable);
- method public abstract void testStarted(java.lang.String);
- method public static java.lang.String truncate(java.lang.String);
- method protected boolean useReloadingTestSuiteLoader();
- field public static final java.lang.String SUITE_METHODNAME = "suite";
- }
-
- public abstract interface TestSuiteLoader {
- method public abstract java.lang.Class load(java.lang.String) throws java.lang.ClassNotFoundException;
- method public abstract java.lang.Class reload(java.lang.Class) throws java.lang.ClassNotFoundException;
- }
-
- public class Version {
- method public static java.lang.String id();
- }
-
-}
-
package org.apache.http.conn {
public deprecated class ConnectTimeoutException extends java.io.InterruptedIOException {
diff --git a/api/system-current.txt b/api/system-current.txt
index a29d3a09b60c..f35984aaf8d1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -790,7 +790,9 @@ package android.content {
field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
field public static final java.lang.String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
field public static final java.lang.String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
- field public static final java.lang.String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
+ field public static final java.lang.String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.intent.action.SIM_APPLICATION_STATE_CHANGED";
+ field public static final java.lang.String ACTION_SIM_CARD_STATE_CHANGED = "android.intent.action.SIM_CARD_STATE_CHANGED";
+ field public static final deprecated java.lang.String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
field public static final java.lang.String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
field public static final java.lang.String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
@@ -3856,6 +3858,28 @@ package android.security.keystore {
}
+package android.service.autofill {
+
+ public abstract class AutofillFieldClassificationService extends android.app.Service {
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public java.util.List<java.lang.String> onGetAvailableAlgorithms();
+ method public java.lang.String onGetDefaultAlgorithm();
+ method public android.service.autofill.AutofillFieldClassificationService.Scores onGetScores(java.lang.String, android.os.Bundle, java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillFieldClassificationService";
+ }
+
+ public static final class AutofillFieldClassificationService.Scores implements android.os.Parcelable {
+ ctor public AutofillFieldClassificationService.Scores(java.lang.String, int, int);
+ ctor public AutofillFieldClassificationService.Scores(android.os.Parcel);
+ method public int describeContents();
+ method public java.lang.String getAlgorithm();
+ method public float[][] getScores();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.AutofillFieldClassificationService.Scores> CREATOR;
+ }
+
+}
+
package android.service.notification {
public final class Adjustment implements android.os.Parcelable {
@@ -4357,6 +4381,8 @@ package android.telephony {
public class SubscriptionManager {
method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
+ method public void setSubscriptionOverrideCongested(int, boolean, long);
+ method public void setSubscriptionOverrideUnmetered(int, boolean, long);
method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
field public static final java.lang.String ACTION_MANAGE_SUBSCRIPTION_PLANS = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS";
field public static final java.lang.String ACTION_REFRESH_SUBSCRIPTION_PLANS = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS";
@@ -4438,6 +4464,8 @@ package android.telephony {
method public deprecated boolean getDataEnabled();
method public deprecated boolean getDataEnabled(int);
method public boolean getEmergencyCallbackMode();
+ method public int getSimApplicationState();
+ method public int getSimCardState();
method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
method public android.os.Bundle getVisualVoicemailSettings();
method public int getVoiceActivationState();
@@ -4471,6 +4499,7 @@ package android.telephony {
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final java.lang.String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE";
field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL";
field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING";
field public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2; // 0x2
@@ -4478,6 +4507,8 @@ package android.telephony {
field public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3; // 0x3
field public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4; // 0x4
field public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0; // 0x0
+ field public static final int SIM_STATE_LOADED = 10; // 0xa
+ field public static final int SIM_STATE_PRESENT = 11; // 0xb
}
public abstract class VisualVoicemailService extends android.app.Service {
diff --git a/api/test-current.txt b/api/test-current.txt
index 6369bb4e1674..acc819e602d1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -563,11 +563,6 @@ package android.service.autofill {
method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception;
}
- public final class EditDistanceScorer {
- method public static android.service.autofill.EditDistanceScorer getInstance();
- method public float getScore(android.view.autofill.AutofillValue, java.lang.String);
- }
-
public final class FillResponse implements android.os.Parcelable {
method public int getFlags();
}
@@ -966,6 +961,9 @@ package android.view {
public final class Choreographer {
method public static long getFrameDelay();
+ method public void postCallback(int, java.lang.Runnable, java.lang.Object);
+ method public void postCallbackDelayed(int, java.lang.Runnable, java.lang.Object, long);
+ method public void removeCallbacks(int, java.lang.Runnable, java.lang.Object);
method public static void setFrameDelay(long);
field public static final int CALLBACK_ANIMATION = 1; // 0x1
}
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 9bddbcbe405b..5eff54887be1 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -134,7 +134,7 @@ LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_INIT_RC := statsd.rc
+#LOCAL_INIT_RC := statsd.rc
include $(BUILD_EXECUTABLE)
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 0ed1c1f11832..ca097d0623ca 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -185,6 +185,7 @@ status_t StatsService::dump(int fd, const Vector<String16>& args) {
*/
void StatsService::dump_impl(FILE* out) {
mConfigManager->Dump(out);
+ StatsdStats::getInstance().dumpStats(out);
}
/**
@@ -296,9 +297,8 @@ void StatsService::print_cmd_help(FILE* out) {
fprintf(out, " NAME The name of the configuration\n");
fprintf(out, "\n");
fprintf(out, "\n");
- fprintf(out, "usage: adb shell cmd stats print-stats [reset]\n");
+ fprintf(out, "usage: adb shell cmd stats print-stats\n");
fprintf(out, " Prints some basic stats.\n");
- fprintf(out, " reset: 1 to reset the statsd stats. default=0.\n");
}
status_t StatsService::cmd_trigger_broadcast(FILE* out, Vector<String8>& args) {
@@ -487,14 +487,8 @@ status_t StatsService::cmd_print_stats(FILE* out, const Vector<String8>& args) {
fprintf(out, "Config %s uses %zu bytes\n", key.ToString().c_str(),
mProcessor->GetMetricsSize(key));
}
- fprintf(out, "Detailed statsd stats in logcat...\n");
StatsdStats& statsdStats = StatsdStats::getInstance();
- bool reset = false;
- if (args.size() > 1) {
- reset = strtol(args[1].string(), NULL, 10);
- }
- vector<uint8_t> output;
- statsdStats.dumpStats(&output, reset);
+ statsdStats.dumpStats(out);
return NO_ERROR;
}
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index 25257213a5d0..7a1bb0c71373 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -19,6 +19,7 @@
#include "SimpleConditionTracker.h"
#include "guardrail/StatsdStats.h"
+#include "dimension.h"
#include <log/logprint.h>
@@ -171,12 +172,12 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou
// We get a new output key.
newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse;
if (matchStart && mInitialValue != ConditionState::kTrue) {
- mSlicedConditionState[outputKey] = 1;
+ mSlicedConditionState.insert(std::make_pair(outputKey, 1));
changed = true;
} else if (mInitialValue != ConditionState::kFalse) {
// it's a stop and we don't have history about it.
// If the default condition is not false, it means this stop is valuable to us.
- mSlicedConditionState[outputKey] = 0;
+ mSlicedConditionState.insert(std::make_pair(outputKey, 0));
changed = true;
}
} else {
@@ -341,7 +342,23 @@ void SimpleConditionTracker::isConditionMet(
conditionState = conditionState |
(startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse);
} else {
- conditionState = conditionState | mInitialValue;
+ // For unseen key, check whether the require dimensions are subset of sliced condition
+ // output.
+ bool seenDimension = false;
+ for (const auto& slice : mSlicedConditionState) {
+ if (IsSubDimension(slice.first.getDimensionsValue(),
+ key.getDimensionsValue())) {
+ seenDimension = true;
+ conditionState = conditionState |
+ (slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse);
+ }
+ if (conditionState == ConditionState::kTrue) {
+ break;
+ }
+ }
+ if (!seenDimension) {
+ conditionState = conditionState | mInitialValue;
+ }
}
}
conditionCache[mIndex] = conditionState;
diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp
index 09499b6406e7..bb7a044fa8ce 100644
--- a/cmds/statsd/src/dimension.cpp
+++ b/cmds/statsd/src/dimension.cpp
@@ -331,13 +331,15 @@ bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub
case DimensionsValue::ValueCase::kValueFloat:
return dimension.value_float() == sub.value_float();
case DimensionsValue::ValueCase::kValueTuple: {
- if (dimension.value_tuple().dimensions_value_size() < sub.value_tuple().dimensions_value_size()) {
+ if (dimension.value_tuple().dimensions_value_size() <
+ sub.value_tuple().dimensions_value_size()) {
return false;
}
bool allSub = true;
- for (int i = 0; i < sub.value_tuple().dimensions_value_size(); ++i) {
+ for (int i = 0; allSub && i < sub.value_tuple().dimensions_value_size(); ++i) {
bool isSub = false;
- for (int j = 0; !isSub && j < dimension.value_tuple().dimensions_value_size(); ++j) {
+ for (int j = 0; !isSub &&
+ j < dimension.value_tuple().dimensions_value_size(); ++j) {
isSub |= IsSubDimension(dimension.value_tuple().dimensions_value(j),
sub.value_tuple().dimensions_value(i));
}
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index 4bd4f19db0b4..63bde7d82372 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -355,73 +355,135 @@ void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key,
}
}
-void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
+void StatsdStats::dumpStats(FILE* out) const {
lock_guard<std::mutex> lock(mLock);
+ time_t t = mStartTimeSec;
+ struct tm* tm = localtime(&t);
+ char timeBuffer[80];
+ strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %I:%M%p\n", tm);
+ fprintf(out, "Stats collection start second: %s\n", timeBuffer);
+ fprintf(out, "%lu Config in icebox: \n", (unsigned long)mIceBox.size());
+ for (const auto& configStats : mIceBox) {
+ fprintf(out,
+ "Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
+ "#matcher=%d, #alert=%d, valid=%d\n",
+ configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(),
+ configStats.deletion_time_sec(), configStats.metric_count(),
+ configStats.condition_count(), configStats.matcher_count(),
+ configStats.alert_count(), configStats.is_valid());
+
+ for (const auto& broadcastTime : configStats.broadcast_sent_time_sec()) {
+ fprintf(out, "\tbroadcast time: %d\n", broadcastTime);
+ }
+
+ for (const auto& dataDropTime : configStats.data_drop_time_sec()) {
+ fprintf(out, "\tdata drop time: %d\n", dataDropTime);
+ }
+ }
+ fprintf(out, "%lu Active Configs\n", (unsigned long)mConfigStats.size());
+ for (auto& pair : mConfigStats) {
+ auto& key = pair.first;
+ auto& configStats = pair.second;
+
+ fprintf(out,
+ "Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
+ "#matcher=%d, #alert=%d, valid=%d\n",
+ configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(),
+ configStats.deletion_time_sec(), configStats.metric_count(),
+ configStats.condition_count(), configStats.matcher_count(),
+ configStats.alert_count(), configStats.is_valid());
+ for (const auto& broadcastTime : configStats.broadcast_sent_time_sec()) {
+ fprintf(out, "\tbroadcast time: %d\n", broadcastTime);
+ }
+
+ for (const auto& dataDropTime : configStats.data_drop_time_sec()) {
+ fprintf(out, "\tdata drop time: %d\n", dataDropTime);
+ }
+
+ for (const auto& dumpTime : configStats.dump_report_time_sec()) {
+ fprintf(out, "\tdump report time: %d\n", dumpTime);
+ }
+
+ // Add matcher stats
+ auto matcherIt = mMatcherStats.find(key);
+ if (matcherIt != mMatcherStats.end()) {
+ const auto& matcherStats = matcherIt->second;
+ for (const auto& stats : matcherStats) {
+ fprintf(out, "matcher %lld matched %d times\n", (long long)stats.first,
+ stats.second);
+ }
+ }
+ // Add condition stats
+ auto conditionIt = mConditionStats.find(key);
+ if (conditionIt != mConditionStats.end()) {
+ const auto& conditionStats = conditionIt->second;
+ for (const auto& stats : conditionStats) {
+ fprintf(out, "condition %lld max output tuple size %d\n", (long long)stats.first,
+ stats.second);
+ }
+ }
+ // Add metrics stats
+ auto metricIt = mMetricsStats.find(key);
+ if (metricIt != mMetricsStats.end()) {
+ const auto& conditionStats = metricIt->second;
+ for (const auto& stats : conditionStats) {
+ fprintf(out, "metrics %lld max output tuple size %d\n", (long long)stats.first,
+ stats.second);
+ }
+ }
+ // Add anomaly detection alert stats
+ auto alertIt = mAlertStats.find(key);
+ if (alertIt != mAlertStats.end()) {
+ const auto& alertStats = alertIt->second;
+ for (const auto& stats : alertStats) {
+ fprintf(out, "alert %lld declared %d times\n", (long long)stats.first,
+ stats.second);
+ }
+ }
+ }
+ fprintf(out, "********Pushed Atom stats***********\n");
+ const size_t atomCounts = mPushedAtomStats.size();
+ for (size_t i = 2; i < atomCounts; i++) {
+ if (mPushedAtomStats[i] > 0) {
+ fprintf(out, "Atom %lu->%d\n", (unsigned long)i, mPushedAtomStats[i]);
+ }
+ }
- if (DEBUG) {
- time_t t = mStartTimeSec;
- struct tm* tm = localtime(&t);
- char timeBuffer[80];
- strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %I:%M%p", tm);
- VLOG("=================StatsdStats dump begins====================");
- VLOG("Stats collection start second: %s", timeBuffer);
+ fprintf(out, "********Pulled Atom stats***********\n");
+ for (const auto& pair : mPulledAtomStats) {
+ fprintf(out, "Atom %d->%ld, %ld, %ld\n", (int)pair.first, (long)pair.second.totalPull,
+ (long)pair.second.totalPullFromCache, (long)pair.second.minPullIntervalSec);
+ }
+
+ if (mAnomalyAlarmRegisteredStats > 0) {
+ fprintf(out, "********AnomalyAlarmStats stats***********\n");
+ fprintf(out, "Anomaly alarm registrations: %d\n", mAnomalyAlarmRegisteredStats);
}
+
+ fprintf(out,
+ "UID map stats: bytes=%d, snapshots=%d, changes=%d, snapshots lost=%d, changes "
+ "lost=%d\n",
+ mUidMapStats.bytes_used(), mUidMapStats.snapshots(), mUidMapStats.changes(),
+ mUidMapStats.dropped_snapshots(), mUidMapStats.dropped_changes());
+}
+
+void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
+ lock_guard<std::mutex> lock(mLock);
+
ProtoOutputStream proto;
proto.write(FIELD_TYPE_INT32 | FIELD_ID_BEGIN_TIME, mStartTimeSec);
proto.write(FIELD_TYPE_INT32 | FIELD_ID_END_TIME, (int32_t)time(nullptr));
- VLOG("%lu Config in icebox: ", (unsigned long)mIceBox.size());
for (const auto& configStats : mIceBox) {
const int numBytes = configStats.ByteSize();
vector<char> buffer(numBytes);
configStats.SerializeToArray(&buffer[0], numBytes);
proto.write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_CONFIG_STATS, &buffer[0],
buffer.size());
-
- // surround the whole block with DEBUG, so that compiler can strip out the code
- // in production.
- if (DEBUG) {
- VLOG("*****ICEBOX*****");
- VLOG("Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
- "#matcher=%d, #alert=%d, #valid=%d",
- configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(),
- configStats.deletion_time_sec(), configStats.metric_count(),
- configStats.condition_count(), configStats.matcher_count(),
- configStats.alert_count(), configStats.is_valid());
-
- for (const auto& broadcastTime : configStats.broadcast_sent_time_sec()) {
- VLOG("\tbroadcast time: %d", broadcastTime);
- }
-
- for (const auto& dataDropTime : configStats.data_drop_time_sec()) {
- VLOG("\tdata drop time: %d", dataDropTime);
- }
- }
}
for (auto& pair : mConfigStats) {
auto& configStats = pair.second;
- if (DEBUG) {
- VLOG("********Active Configs***********");
- VLOG("Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
- "#matcher=%d, #alert=%d, #valid=%d",
- configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(),
- configStats.deletion_time_sec(), configStats.metric_count(),
- configStats.condition_count(), configStats.matcher_count(),
- configStats.alert_count(), configStats.is_valid());
- for (const auto& broadcastTime : configStats.broadcast_sent_time_sec()) {
- VLOG("\tbroadcast time: %d", broadcastTime);
- }
-
- for (const auto& dataDropTime : configStats.data_drop_time_sec()) {
- VLOG("\tdata drop time: %d", dataDropTime);
- }
-
- for (const auto& dumpTime : configStats.dump_report_time_sec()) {
- VLOG("\tdump report time: %d", dumpTime);
- }
- }
-
addSubStatsToConfigLocked(pair.first, configStats);
const int numBytes = configStats.ByteSize();
@@ -437,7 +499,6 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
configStats.clear_alert_stats();
}
- VLOG("********Pushed Atom stats***********");
const size_t atomCounts = mPushedAtomStats.size();
for (size_t i = 2; i < atomCounts; i++) {
if (mPushedAtomStats[i] > 0) {
@@ -446,34 +507,24 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i);
proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]);
proto.end(token);
-
- VLOG("Atom %lu->%d\n", (unsigned long)i, mPushedAtomStats[i]);
}
}
- VLOG("********Pulled Atom stats***********");
for (const auto& pair : mPulledAtomStats) {
android::os::statsd::writePullerStatsToStream(pair, &proto);
- VLOG("Atom %d->%ld, %ld, %ld\n", (int) pair.first, (long) pair.second.totalPull, (long) pair.second.totalPullFromCache, (long) pair.second.minPullIntervalSec);
}
if (mAnomalyAlarmRegisteredStats > 0) {
- VLOG("********AnomalyAlarmStats stats***********");
long long token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ANOMALY_ALARM_STATS);
proto.write(FIELD_TYPE_INT32 | FIELD_ID_ANOMALY_ALARMS_REGISTERED,
mAnomalyAlarmRegisteredStats);
proto.end(token);
- VLOG("Anomaly alarm registrations: %d", mAnomalyAlarmRegisteredStats);
}
const int numBytes = mUidMapStats.ByteSize();
vector<char> buffer(numBytes);
mUidMapStats.SerializeToArray(&buffer[0], numBytes);
proto.write(FIELD_TYPE_MESSAGE | FIELD_ID_UIDMAP_STATS, &buffer[0], buffer.size());
- VLOG("UID map stats: bytes=%d, snapshots=%d, changes=%d, snapshots lost=%d, changes "
- "lost=%d",
- mUidMapStats.bytes_used(), mUidMapStats.snapshots(), mUidMapStats.changes(),
- mUidMapStats.dropped_snapshots(), mUidMapStats.dropped_changes());
output->clear();
size_t bufferSize = proto.size();
@@ -493,7 +544,6 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
}
VLOG("reset=%d, returned proto size %lu", reset, (unsigned long)bufferSize);
- VLOG("=================StatsdStats dump ends====================");
}
} // namespace statsd
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 52ab253b6721..9178daa4fe12 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -189,6 +189,11 @@ public:
*/
void dumpStats(std::vector<uint8_t>* buffer, bool reset);
+ /**
+ * Output statsd stats in human readable format to [out] file.
+ */
+ void dumpStats(FILE* out) const;
+
typedef struct {
long totalPull;
long totalPullFromCache;
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index dcb8eed1479d..eefb7dc046ec 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -106,9 +106,9 @@ void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
t->set_uid(uid[j]);
}
mBytesUsed += snapshot->ByteSize();
+ ensureBytesUsedBelowLimit();
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
StatsdStats::getInstance().setUidMapSnapshots(mOutput.snapshots_size());
- ensureBytesUsedBelowLimit();
getListenerListCopyLocked(&broadcastList);
}
// To avoid invoking callback while holding the internal lock. we get a copy of the listener
@@ -142,9 +142,9 @@ void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const i
log->set_uid(uid);
log->set_version(versionCode);
mBytesUsed += log->ByteSize();
+ ensureBytesUsedBelowLimit();
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
- ensureBytesUsedBelowLimit();
auto range = mMap.equal_range(int(uid));
bool found = false;
@@ -222,9 +222,9 @@ void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const i
log->set_app(app);
log->set_uid(uid);
mBytesUsed += log->ByteSize();
+ ensureBytesUsedBelowLimit();
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
- ensureBytesUsedBelowLimit();
auto range = mMap.equal_range(int(uid));
for (auto it = range.first; it != range.second; ++it) {
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 4e37977dbf3b..3304f6c976aa 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -152,9 +152,9 @@ private:
// until the memory consumed by mOutput is below the specified limit.
void ensureBytesUsedBelowLimit();
- // Override used for testing the max memory allowed by uid map. -1 means we use the value
+ // Override used for testing the max memory allowed by uid map. 0 means we use the value
// specified in StatsdStats.h with the rest of the guardrails.
- size_t maxBytesOverride = -1;
+ size_t maxBytesOverride = 0;
// Cache the size of mOutput;
size_t mBytesUsed;
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 393f795a97fe..bb2193fb442b 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -58,9 +58,9 @@ message CountBucketInfo {
message CountMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 3;
+ optional DimensionsValue dimensions_in_condition = 2;
- repeated CountBucketInfo bucket_info = 2;
+ repeated CountBucketInfo bucket_info = 3;
}
message DurationBucketInfo {
@@ -74,9 +74,9 @@ message DurationBucketInfo {
message DurationMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 3;
+ optional DimensionsValue dimensions_in_condition = 2;
- repeated DurationBucketInfo bucket_info = 2;
+ repeated DurationBucketInfo bucket_info = 3;
}
message ValueBucketInfo {
@@ -90,9 +90,9 @@ message ValueBucketInfo {
message ValueMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 3;
+ optional DimensionsValue dimensions_in_condition = 2;
- repeated ValueBucketInfo bucket_info = 2;
+ repeated ValueBucketInfo bucket_info = 3;
}
message GaugeBucketInfo {
@@ -106,9 +106,9 @@ message GaugeBucketInfo {
message GaugeMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 3;
+ optional DimensionsValue dimensions_in_condition = 2;
- repeated GaugeBucketInfo bucket_info = 2;
+ repeated GaugeBucketInfo bucket_info = 3;
}
message UidMapping {
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index ae8a835f0d12..afb0bc4bcbc5 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -180,11 +180,11 @@ message CountMetric {
optional FieldMatcher dimensions_in_what = 4;
- optional FieldMatcher dimensions_in_condition = 5;
+ optional FieldMatcher dimensions_in_condition = 7;
- optional TimeUnit bucket = 6;
+ optional TimeUnit bucket = 5;
- repeated MetricConditionLink links = 7;
+ repeated MetricConditionLink links = 6;
}
message DurationMetric {
@@ -205,9 +205,9 @@ message DurationMetric {
optional FieldMatcher dimensions_in_what = 6;
- optional FieldMatcher dimensions_in_condition = 7;
+ optional FieldMatcher dimensions_in_condition = 8;
- optional TimeUnit bucket = 8;
+ optional TimeUnit bucket = 7;
}
message GaugeMetric {
@@ -221,11 +221,11 @@ message GaugeMetric {
optional FieldMatcher dimensions_in_what = 5;
- optional FieldMatcher dimensions_in_condition = 6;
+ optional FieldMatcher dimensions_in_condition = 8;
- optional TimeUnit bucket = 7;
+ optional TimeUnit bucket = 6;
- repeated MetricConditionLink links = 8;
+ repeated MetricConditionLink links = 7;
}
message ValueMetric {
@@ -239,14 +239,14 @@ message ValueMetric {
optional FieldMatcher dimensions_in_what = 5;
- optional FieldMatcher dimensions_in_condition = 6;
+ optional FieldMatcher dimensions_in_condition = 9;
- optional TimeUnit bucket = 7;
+ optional TimeUnit bucket = 6;
- repeated MetricConditionLink links = 8;
+ repeated MetricConditionLink links = 7;
enum AggregationType { SUM = 1; }
- optional AggregationType aggregation_type = 9 [default = SUM];
+ optional AggregationType aggregation_type = 8 [default = SUM];
}
message Alert {
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index c542db2312ea..1d75e20d7eaa 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -111,20 +111,21 @@ void StorageManager::sendBroadcast(const char* path,
int index = 0;
int uid = 0;
- string configName;
+ int64_t configID = 0;
char* substr = strtok(name, "-");
// Timestamp lives at index 2 but we skip parsing it as it's not needed.
while (substr != nullptr && index < 2) {
- if (index) {
+ if (index == 0) {
uid = atoi(substr);
- } else {
- configName = substr;
+ } else if (index == 1) {
+ configID = StrToInt64(substr);
}
index++;
+ substr = strtok(nullptr, "-");
}
if (index < 2) continue;
- sendBroadcast(ConfigKey(uid, StrToInt64(configName)));
+ sendBroadcast(ConfigKey(uid, configID));
}
}
@@ -143,19 +144,23 @@ void StorageManager::appendConfigMetricsReport(const char* path, ProtoOutputStre
int index = 0;
int uid = 0;
- string configName;
+ int64_t configID = 0;
+ int64_t timestamp = 0;
char* substr = strtok(name, "-");
- // Timestamp lives at index 2 but we skip parsing it as it's not needed.
- while (substr != nullptr && index < 2) {
- if (index) {
+ while (substr != nullptr && index < 3) {
+ if (index == 0) {
uid = atoi(substr);
- } else {
- configName = substr;
+ } else if (index == 1) {
+ configID = StrToInt64(substr);
+ } else if (index == 2) {
+ timestamp = atoi(substr);
}
index++;
+ substr = strtok(nullptr, "-");
}
- if (index < 2) continue;
- string file_name = StringPrintf("%s/%s", path, name);
+ if (index < 3) continue;
+ string file_name = StringPrintf("%s/%d-%lld-%lld", STATS_SERVICE_DIR, uid,
+ (long long)configID, (long long)timestamp);
int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
if (fd != -1) {
string content;
@@ -186,29 +191,32 @@ void StorageManager::readConfigFromDisk(map<ConfigKey, StatsdConfig>& configsMap
int index = 0;
int uid = 0;
- string configName;
+ int64_t configID = 0;
+ int64_t timestamp = 0;
char* substr = strtok(name, "-");
- // Timestamp lives at index 2 but we skip parsing it as it's not needed.
- while (substr != nullptr && index < 2) {
- if (index) {
+ while (substr != nullptr && index < 3) {
+ if (index == 0) {
uid = atoi(substr);
- } else {
- configName = substr;
+ } else if (index == 1) {
+ configID = StrToInt64(substr);
+ } else if (index == 2) {
+ timestamp = atoi(substr);
}
index++;
+ substr = strtok(nullptr, "-");
}
- if (index < 2) continue;
+ if (index < 3) continue;
- string file_name = StringPrintf("%s/%s", STATS_SERVICE_DIR, name);
- VLOG("full file %s", file_name.c_str());
+ string file_name = StringPrintf("%s/%d-%lld-%lld", STATS_SERVICE_DIR, uid,
+ (long long)configID, (long long)timestamp);
int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
if (fd != -1) {
string content;
if (android::base::ReadFdToString(fd, &content)) {
StatsdConfig config;
if (config.ParseFromString(content)) {
- configsMap[ConfigKey(uid, StrToInt64(configName))] = config;
- VLOG("map key uid=%d|name=%s", uid, name);
+ configsMap[ConfigKey(uid, configID)] = config;
+ VLOG("map key uid=%d|configID=%lld", uid, (long long)configID);
}
}
close(fd);
diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc
index c260ae1aa6c8..920273b5dfaa 100644
--- a/cmds/statsd/statsd.rc
+++ b/cmds/statsd/statsd.rc
@@ -16,6 +16,7 @@ service statsd /system/bin/statsd
class main
user statsd
group statsd log
+ writepid /dev/cpuset/system-background/tasks
on post-fs-data
# Create directory for statsd
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
index cc02f34519cd..62bdba406de2 100644
--- a/cmds/statsd/tests/ConfigManager_test.cpp
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -91,7 +91,7 @@ TEST(ConfigManagerTest, TestAddUpdateRemove) {
{
InSequence s;
- manager->Startup();
+ manager->StartupForTest();
// Add another one
EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("zzz")),
diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
index 178dd71354c4..7512abc262b4 100644
--- a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
@@ -46,7 +46,7 @@ StatsdConfig CreateStatsdConfig() {
auto isSyncingPredicate = CreateIsSyncingPredicate();
*isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions() =
CreateDimensions(
- android::util::SYNC_STATE_CHANGED, {1 /* uid field */});
+ android::util::SYNC_STATE_CHANGED, {1 /* uid field */, 2 /* name field*/});
auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
*isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 9f1e98399dce..ac6cba138eed 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -34,6 +34,7 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import dalvik.system.CloseGuard;
@@ -58,6 +59,8 @@ public class ActivityView extends ViewGroup {
private StateCallback mActivityViewCallback;
private IInputForwarder mInputForwarder;
+ // Temp container to store view coordinates on screen.
+ private final int[] mLocationOnScreen = new int[2];
private final CloseGuard mGuard = CloseGuard.get();
private boolean mOpened; // Protected by mGuard.
@@ -198,11 +201,30 @@ public class ActivityView extends ViewGroup {
performRelease();
}
+ /**
+ * Triggers an update of {@link ActivityView}'s location on screen to properly set touch exclude
+ * regions and avoid focus switches by touches on this view.
+ */
+ public void onLocationChanged() {
+ updateLocation();
+ }
+
@Override
public void onLayout(boolean changed, int l, int t, int r, int b) {
mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
}
+ /** Send current location and size to the WM to set tap exclude region for this view. */
+ private void updateLocation() {
+ try {
+ getLocationOnScreen(mLocationOnScreen);
+ WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
+ mLocationOnScreen[0], mLocationOnScreen[1], getWidth(), getHeight());
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
return injectInputEvent(event) || super.onTouchEvent(event);
@@ -241,6 +263,7 @@ public class ActivityView extends ViewGroup {
} else {
mVirtualDisplay.setSurface(surfaceHolder.getSurface());
}
+ updateLocation();
}
@Override
@@ -248,6 +271,7 @@ public class ActivityView extends ViewGroup {
if (mVirtualDisplay != null) {
mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
}
+ updateLocation();
}
@Override
@@ -257,6 +281,7 @@ public class ActivityView extends ViewGroup {
if (mVirtualDisplay != null) {
mVirtualDisplay.setSurface(null);
}
+ cleanTapExcludeRegion();
}
}
@@ -290,6 +315,7 @@ public class ActivityView extends ViewGroup {
if (mInputForwarder != null) {
mInputForwarder = null;
}
+ cleanTapExcludeRegion();
final boolean displayReleased;
if (mVirtualDisplay != null) {
@@ -313,6 +339,17 @@ public class ActivityView extends ViewGroup {
mOpened = false;
}
+ /** Report to server that tap exclude region on hosting display should be cleared. */
+ private void cleanTapExcludeRegion() {
+ // Update tap exclude region with an empty rect to clean the state on server.
+ try {
+ WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
+ 0 /* left */, 0 /* top */, 0 /* width */, 0 /* height */);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
/** Get density of the hosting display. */
private int getBaseDisplayDensity() {
final WindowManager wm = mContext.getSystemService(WindowManager.class);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6eafcc437447..33277eae0520 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -38,12 +38,12 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
+import android.content.pm.CrossProfileApps;
+import android.content.pm.ICrossProfileApps;
import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
-import android.content.pm.crossprofile.CrossProfileApps;
-import android.content.pm.crossprofile.ICrossProfileApps;
import android.content.res.Resources;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9329d56a8de9..0be55642d4cf 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9197,4 +9197,50 @@ public class DevicePolicyManager {
throw re.rethrowFromSystemServer();
}
}
+
+ /**
+ * Allows/disallows printing.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @param enabled whether printing should be allowed or not.
+ * @throws SecurityException if {@code admin} is neither device, nor profile owner.
+ * @hide
+ */
+ public void setPrintingEnabled(@NonNull ComponentName admin, boolean enabled) {
+ try {
+ mService.setPrintingEnabled(admin, enabled);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether printing is enabled for current user.
+ *
+ * @return {@code true} iff printing is enabled.
+ * @hide
+ */
+ public boolean isPrintingEnabled() {
+ try {
+ return mService.isPrintingEnabled();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns error message to be displayed when printing is disabled.
+ *
+ * Used only by PrintService.
+ * @return Localized error message.
+ * @throws SecurityException if caller is not system.
+ * @hide
+ */
+ public CharSequence getPrintingDisabledReason() {
+ try {
+ return mService.getPrintingDisabledReason();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index eac7f7ed4b3e..d2a2be7bbcb5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -396,4 +396,8 @@ interface IDevicePolicyManager {
void setEndUserSessionMessage(in ComponentName admin, in CharSequence endUserSessionMessage);
CharSequence getStartUserSessionMessage(in ComponentName admin);
CharSequence getEndUserSessionMessage(in ComponentName admin);
+
+ void setPrintingEnabled(in ComponentName admin, boolean enabled);
+ boolean isPrintingEnabled();
+ CharSequence getPrintingDisabledReason();
}
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 7c40b4eaf375..cba9dcc3c4cc 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -253,6 +253,11 @@ public class JobInfo implements Parcelable {
/**
* @hide
*/
+ public static final int FLAG_IS_PREFETCH = 1 << 2;
+
+ /**
+ * @hide
+ */
public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
/**
@@ -1364,6 +1369,28 @@ public class JobInfo implements Parcelable {
}
/**
+ * Setting this to true indicates that this job is designed to prefetch
+ * content that will make a material improvement to the experience of
+ * the specific user of this device. For example, fetching top headlines
+ * of interest to the current user.
+ * <p>
+ * The system may use this signal to relax the network constraints you
+ * originally requested, such as allowing a
+ * {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over a metered
+ * network when there is a surplus of metered data available. The system
+ * may also use this signal in combination with end user usage patterns
+ * to ensure data is prefetched before the user launches your app.
+ */
+ public Builder setIsPrefetch(boolean isPrefetch) {
+ if (isPrefetch) {
+ mFlags |= FLAG_IS_PREFETCH;
+ } else {
+ mFlags &= (~FLAG_IS_PREFETCH);
+ }
+ return this;
+ }
+
+ /**
* Set whether or not to persist this job across device reboots.
*
* @param isPersisted True to indicate that the job will be written to
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f69e76445aa7..265f7c7425db 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4146,7 +4146,7 @@ public abstract class Context {
/**
* Use with {@link #getSystemService(String)} to retrieve a
- * {@link android.content.pm.crossprofile.CrossProfileApps} for cross profile operations.
+ * {@link android.content.pm.CrossProfileApps} for cross profile operations.
*
* @see #getSystemService(String)
*/
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6e9970980e96..f05a4d1aa9c7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3516,12 +3516,83 @@ public class Intent implements Parcelable, Cloneable {
* For more details see TelephonyIntents.ACTION_SIM_STATE_CHANGED. This is here
* because TelephonyIntents is an internal class.
* @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED} or
+ * {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
*/
+ @Deprecated
@SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
/**
+ * Broadcast Action: The sim card state has changed.
+ * The intent will have the following extra values:</p>
+ * <dl>
+ * <dt>{@link android.telephony.TelephonyManager.EXTRA_SIM_STATE}</dt>
+ * <dd>The sim card state. One of:
+ * <dl>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_ABSENT}</dt>
+ * <dd>SIM card not found</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_CARD_IO_ERROR}</dt>
+ * <dd>SIM card IO error</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_CARD_RESTRICTED}</dt>
+ * <dd>SIM card is restricted</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_PRESENT}</dt>
+ * <dd>SIM card is present</dd>
+ * </dl>
+ * </dd>
+ * </dl>
+ *
+ * <p class="note">Requires the READ_PRIVILEGED_PHONE_STATE permission.
+ *
+ * <p class="note">The current state can also be queried using
+ * {@link android.telephony.TelephonyManager.getSimCardState()}
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SIM_CARD_STATE_CHANGED =
+ "android.intent.action.SIM_CARD_STATE_CHANGED";
+
+ /**
+ * Broadcast Action: The sim application state has changed.
+ * The intent will have the following extra values:</p>
+ * <dl>
+ * <dt>{@link android.telephony.TelephonyManager.EXTRA_SIM_STATE}</dt>
+ * <dd>The sim application state. One of:
+ * <dl>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_NOT_READY}</dt>
+ * <dd>SIM card applications not ready</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED}</dt>
+ * <dd>SIM card PIN locked</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED}</dt>
+ * <dd>SIM card PUK locked</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_NETWORK_LOCKED}</dt>
+ * <dd>SIM card network locked</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_PERM_DISABLED}</dt>
+ * <dd>SIM card permanently disabled due to PUK failures</dd>
+ * <dt>{@link android.telephony.TelephonyManager.SIM_STATE_LOADED}</dt>
+ * <dd>SIM card data loaded</dd>
+ * </dl>
+ * </dd>
+ * </dl>
+ *
+ * <p class="note">Requires the READ_PRIVILEGED_PHONE_STATE permission.
+ *
+ * <p class="note">The current state can also be queried using
+ * {@link android.telephony.TelephonyManager.getSimApplicationState()}
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SIM_APPLICATION_STATE_CHANGED =
+ "android.intent.action.SIM_APPLICATION_STATE_CHANGED";
+
+ /**
* Broadcast Action: indicate that the phone service state has changed.
* The intent will have the following extra values:</p>
* <p>
diff --git a/core/java/android/content/pm/crossprofile/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 414c13894f80..7d5d6090788c 100644
--- a/core/java/android/content/pm/crossprofile/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.content.pm.crossprofile;
+package android.content.pm;
import android.annotation.NonNull;
import android.content.ComponentName;
@@ -57,13 +57,14 @@ public class CrossProfileApps {
* action {@link android.content.Intent#ACTION_MAIN}, category
* {@link android.content.Intent#CATEGORY_LAUNCHER}. Otherwise, SecurityException will
* be thrown.
- * @param user The UserHandle of the profile, must be one of the users returned by
+ * @param targetUser The UserHandle of the profile, must be one of the users returned by
* {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
* be thrown.
*/
- public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user) {
+ public void startMainActivity(@NonNull ComponentName component,
+ @NonNull UserHandle targetUser) {
try {
- mService.startActivityAsUser(mContext.getPackageName(), component, user);
+ mService.startActivityAsUser(mContext.getPackageName(), component, targetUser);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -114,7 +115,7 @@ public class CrossProfileApps {
}
/**
- * Return an icon that calling app can show to user for the semantic of profile switching --
+ * Return a drawable that calling app can show to user for the semantic of profile switching --
* launching its own activity in specified user profile. For example, it may return a briefcase
* icon if the given user handle is the managed profile one.
*
@@ -124,9 +125,9 @@ public class CrossProfileApps {
* @return an icon that calling app can show user for the semantic of launching its own
* activity in specified user profile.
*
- * @see #startMainActivity(ComponentName, UserHandle, Rect, Bundle)
+ * @see #startMainActivity(ComponentName, UserHandle)
*/
- public @NonNull Drawable getProfileSwitchingIcon(@NonNull UserHandle userHandle) {
+ public @NonNull Drawable getProfileSwitchingIconDrawable(@NonNull UserHandle userHandle) {
verifyCanAccessUser(userHandle);
final boolean isManagedProfile =
diff --git a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl
index 227f91f5d3e1..e79deb96838b 100644
--- a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl
+++ b/core/java/android/content/pm/ICrossProfileApps.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.content.pm.crossprofile;
+package android.content.pm;
import android.content.ComponentName;
import android.content.Intent;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 44b4a33976d0..bcf80eeaaee7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1297,6 +1297,15 @@ public abstract class PackageManager {
*/
public static final int INSTALL_FAILED_INSTANT_APP_INVALID = -116;
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the dex metadata file is invalid or
+ * if there was no matching apk file for a dex metadata file.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_BAD_DEX_METADATA = -117;
+
/** @hide */
@IntDef(flag = true, prefix = { "DELETE_" }, value = {
DELETE_KEEP_DATA,
@@ -5618,6 +5627,8 @@ public abstract class PackageManager {
case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION";
case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS";
case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED";
+ case INSTALL_FAILED_BAD_DEX_METADATA:
+ return "INSTALL_FAILED_BAD_DEX_METADATA";
default: return Integer.toString(status);
}
}
@@ -5662,6 +5673,7 @@ public abstract class PackageManager {
case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return PackageInstaller.STATUS_FAILURE_INVALID;
case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return PackageInstaller.STATUS_FAILURE_INVALID;
case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_BAD_DEX_METADATA: return PackageInstaller.STATUS_FAILURE_INVALID;
case INSTALL_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE;
case INSTALL_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
case INSTALL_FAILED_DUPLICATE_PERMISSION: return PackageInstaller.STATUS_FAILURE_CONFLICT;
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 2c45b8d8b30e..6f093ba8d005 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -436,6 +436,11 @@ public abstract class PackageManagerInternal {
*/
public abstract int getUidTargetSdkVersion(int uid);
+ /**
+ * Return the taget SDK version for the app with the given package name.
+ */
+ public abstract int getPackageTargetSdkVersion(String packageName);
+
/** Whether the binder caller can access instant apps. */
public abstract boolean canAccessInstantApps(int callingUid, int userId);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 6d6c02a47082..4a71467f4b36 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -240,6 +240,9 @@ public class PackageParser {
}
/** @hide */
+ public static final String APK_FILE_EXTENSION = ".apk";
+
+ /** @hide */
public static class NewPermissionInfo {
public final String name;
public final int sdkVersion;
@@ -613,7 +616,7 @@ public class PackageParser {
}
public static boolean isApkPath(String path) {
- return path.endsWith(".apk");
+ return path.endsWith(APK_FILE_EXTENSION);
}
/**
diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java
new file mode 100644
index 000000000000..c5f1c852c7b2
--- /dev/null
+++ b/core/java/android/content/pm/dex/DexMetadataHelper.java
@@ -0,0 +1,230 @@
+/**
+ * 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.
+ */
+
+package android.content.pm.dex;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA;
+import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
+
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.PackageParser.PackageParserException;
+import android.util.ArrayMap;
+import android.util.jar.StrictJarFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class used to compute and validate the location of dex metadata files.
+ *
+ * @hide
+ */
+public class DexMetadataHelper {
+ private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
+
+ private DexMetadataHelper() {}
+
+ /** Return true if the given file is a dex metadata file. */
+ public static boolean isDexMetadataFile(File file) {
+ return isDexMetadataPath(file.getName());
+ }
+
+ /** Return true if the given path is a dex metadata path. */
+ private static boolean isDexMetadataPath(String path) {
+ return path.endsWith(DEX_METADATA_FILE_EXTENSION);
+ }
+
+ /**
+ * Return the size (in bytes) of all dex metadata files associated with the given package.
+ */
+ public static long getPackageDexMetadataSize(PackageLite pkg) {
+ long sizeBytes = 0;
+ Collection<String> dexMetadataList = DexMetadataHelper.getPackageDexMetadata(pkg).values();
+ for (String dexMetadata : dexMetadataList) {
+ sizeBytes += new File(dexMetadata).length();
+ }
+ return sizeBytes;
+ }
+
+ /**
+ * Search for the dex metadata file associated with the given target file.
+ * If it exists, the method returns the dex metadata file; otherwise it returns null.
+ *
+ * Note that this performs a loose matching suitable to be used in the InstallerSession logic.
+ * i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile}
+ * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk').
+ */
+ public static File findDexMetadataForFile(File targetFile) {
+ String dexMetadataPath = buildDexMetadataPathForFile(targetFile);
+ File dexMetadataFile = new File(dexMetadataPath);
+ return dexMetadataFile.exists() ? dexMetadataFile : null;
+ }
+
+ /**
+ * Return the dex metadata files for the given package as a map
+ * [code path -> dex metadata path].
+ *
+ * NOTE: involves I/O checks.
+ */
+ public static Map<String, String> getPackageDexMetadata(PackageParser.Package pkg) {
+ return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths());
+ }
+
+ /**
+ * Return the dex metadata files for the given package as a map
+ * [code path -> dex metadata path].
+ *
+ * NOTE: involves I/O checks.
+ */
+ private static Map<String, String> getPackageDexMetadata(PackageLite pkg) {
+ return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths());
+ }
+
+ /**
+ * Look up the dex metadata files for the given code paths building the map
+ * [code path -> dex metadata].
+ *
+ * For each code path (.apk) the method checks if a matching dex metadata file (.dm) exists.
+ * If it does it adds the pair to the returned map.
+ *
+ * Note that this method will do a strict
+ * matching based on the extension ('foo.dm' will only match 'foo.apk').
+ *
+ * This should only be used for code paths extracted from a package structure after the naming
+ * was enforced in the installer.
+ */
+ private static Map<String, String> buildPackageApkToDexMetadataMap(
+ List<String> codePaths) {
+ ArrayMap<String, String> result = new ArrayMap<>();
+ for (int i = codePaths.size() - 1; i >= 0; i--) {
+ String codePath = codePaths.get(i);
+ String dexMetadataPath = buildDexMetadataPathForApk(codePath);
+
+ if (Files.exists(Paths.get(dexMetadataPath))) {
+ result.put(codePath, dexMetadataPath);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Return the dex metadata path associated with the given code path.
+ * (replaces '.apk' extension with '.dm')
+ *
+ * @throws IllegalArgumentException if the code path is not an .apk.
+ */
+ public static String buildDexMetadataPathForApk(String codePath) {
+ if (!PackageParser.isApkPath(codePath)) {
+ throw new IllegalStateException(
+ "Corrupted package. Code path is not an apk " + codePath);
+ }
+ return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length())
+ + DEX_METADATA_FILE_EXTENSION;
+ }
+
+ /**
+ * Return the dex metadata path corresponding to the given {@code targetFile} using a loose
+ * matching.
+ * i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile}
+ * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk').
+ */
+ private static String buildDexMetadataPathForFile(File targetFile) {
+ return PackageParser.isApkFile(targetFile)
+ ? buildDexMetadataPathForApk(targetFile.getPath())
+ : targetFile.getPath() + DEX_METADATA_FILE_EXTENSION;
+ }
+
+ /**
+ * Validate the dex metadata files installed for the given package.
+ *
+ * @throws PackageParserException in case of errors.
+ */
+ public static void validatePackageDexMetadata(PackageParser.Package pkg)
+ throws PackageParserException {
+ Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values();
+ for (String dexMetadata : apkToDexMetadataList) {
+ validateDexMetadataFile(dexMetadata);
+ }
+ }
+
+ /**
+ * Validate that the given file is a dex metadata archive.
+ * This is just a sanity validation that the file is a zip archive.
+ *
+ * @throws PackageParserException if the file is not a .dm file.
+ */
+ private static void validateDexMetadataFile(String dmaPath) throws PackageParserException {
+ StrictJarFile jarFile = null;
+ try {
+ jarFile = new StrictJarFile(dmaPath, false, false);
+ } catch (IOException e) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "Error opening " + dmaPath, e);
+ } finally {
+ if (jarFile != null) {
+ try {
+ jarFile.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Validates that all dex metadata paths in the given list have a matching apk.
+ * (for any foo.dm there should be either a 'foo' of a 'foo.apk' file).
+ * If that's not the case it throws {@code IllegalStateException}.
+ *
+ * This is used to perform a basic sanity check during adb install commands.
+ * (The installer does not support stand alone .dm files)
+ */
+ public static void validateDexPaths(String[] paths) {
+ ArrayList<String> apks = new ArrayList<>();
+ for (int i = 0; i < paths.length; i++) {
+ if (PackageParser.isApkPath(paths[i])) {
+ apks.add(paths[i]);
+ }
+ }
+ ArrayList<String> unmatchedDmFiles = new ArrayList<>();
+ for (int i = 0; i < paths.length; i++) {
+ String dmPath = paths[i];
+ if (isDexMetadataPath(dmPath)) {
+ boolean valid = false;
+ for (int j = apks.size() - 1; j >= 0; j--) {
+ if (dmPath.equals(buildDexMetadataPathForFile(new File(apks.get(j))))) {
+ valid = true;
+ break;
+ }
+ }
+ if (!valid) {
+ unmatchedDmFiles.add(dmPath);
+ }
+ }
+ }
+ if (!unmatchedDmFiles.isEmpty()) {
+ throw new IllegalStateException("Unmatched .dm files: " + unmatchedDmFiles);
+ }
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index ce1fba798c0e..639795ab8080 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -183,7 +183,9 @@ public abstract class CameraDevice implements AutoCloseable {
TEMPLATE_RECORD,
TEMPLATE_VIDEO_SNAPSHOT,
TEMPLATE_ZERO_SHUTTER_LAG,
- TEMPLATE_MANUAL })
+ TEMPLATE_MANUAL,
+ TEMPLATE_MOTION_TRACKING_PREVIEW,
+ TEMPLATE_MOTION_TRACKING_BEST})
public @interface RequestTemplate {};
/**
@@ -424,14 +426,17 @@ public abstract class CameraDevice implements AutoCloseable {
* {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices. The
* {@code FULL FOV 640} entry means that the device will support a resolution that's 640 pixels
* wide, with the height set so that the resolution aspect ratio matches the MAXIMUM output
- * aspect ratio. So for a device with a 4:3 image sensor, this will be 640x480, and for a
- * device with a 16:9 sensor, this will be 640x360, and so on.
+ * aspect ratio, rounded down. So for a device with a 4:3 image sensor, this will be 640x480,
+ * and for a device with a 16:9 sensor, this will be 640x360, and so on. And the
+ * {@code MAX 30FPS} entry means the largest JPEG resolution on the device for which
+ * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration}
+ * returns a value less than or equal to 1/30s.
*
* <table>
* <tr><th colspan="7">MOTION_TRACKING-capability additional guaranteed configurations</th></tr>
* <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th><th rowspan="2">Sample use case(s)</th> </tr>
* <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr>
- * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code FULL FOV 640}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Live preview with a tracking YUV output and a maximum-resolution YUV for still captures.</td> </tr>
+ * <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code FULL FOV 640}</td> <td>{@code JPEG}</td><td id="rb">{@code MAX 30FPS}</td> <td>Preview with a tracking YUV output and a as-large-as-possible JPEG for still captures.</td> </tr>
* </table><br>
* </p>
*
@@ -899,12 +904,6 @@ public abstract class CameraDevice implements AutoCloseable {
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if the camera device has been closed
- *
- * @see #TEMPLATE_PREVIEW
- * @see #TEMPLATE_RECORD
- * @see #TEMPLATE_STILL_CAPTURE
- * @see #TEMPLATE_VIDEO_SNAPSHOT
- * @see #TEMPLATE_MANUAL
*/
@NonNull
public abstract CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType)
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 1c7f2896b167..2294ec56525f 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1359,6 +1359,20 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4;
+ /**
+ * <p>An external flash has been turned on.</p>
+ * <p>It informs the camera device that an external flash has been turned on, and that
+ * metering (and continuous focus if active) should be quickly recaculated to account
+ * for the external flash. Otherwise, this mode acts like ON.</p>
+ * <p>When the external flash is turned off, AE mode should be changed to one of the
+ * other available AE modes.</p>
+ * <p>If the camera device supports AE external flash mode, aeState must be
+ * FLASH_REQUIRED after the camera device finishes AE scan and it's too dark without
+ * flash.</p>
+ * @see CaptureRequest#CONTROL_AE_MODE
+ */
+ public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5;
+
//
// Enumeration values for CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index cf27c704f7e5..ce75fa52eff7 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1076,6 +1076,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <li>{@link #CONTROL_AE_MODE_ON_AUTO_FLASH ON_AUTO_FLASH}</li>
* <li>{@link #CONTROL_AE_MODE_ON_ALWAYS_FLASH ON_ALWAYS_FLASH}</li>
* <li>{@link #CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE ON_AUTO_FLASH_REDEYE}</li>
+ * <li>{@link #CONTROL_AE_MODE_ON_EXTERNAL_FLASH ON_EXTERNAL_FLASH}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES android.control.aeAvailableModes}</p>
@@ -1093,6 +1094,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see #CONTROL_AE_MODE_ON_AUTO_FLASH
* @see #CONTROL_AE_MODE_ON_ALWAYS_FLASH
* @see #CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
+ * @see #CONTROL_AE_MODE_ON_EXTERNAL_FLASH
*/
@PublicKey
public static final Key<Integer> CONTROL_AE_MODE =
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index b6b0c907a8f9..237a92d3ca9f 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -691,6 +691,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>{@link #CONTROL_AE_MODE_ON_AUTO_FLASH ON_AUTO_FLASH}</li>
* <li>{@link #CONTROL_AE_MODE_ON_ALWAYS_FLASH ON_ALWAYS_FLASH}</li>
* <li>{@link #CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE ON_AUTO_FLASH_REDEYE}</li>
+ * <li>{@link #CONTROL_AE_MODE_ON_EXTERNAL_FLASH ON_EXTERNAL_FLASH}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES android.control.aeAvailableModes}</p>
@@ -708,6 +709,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see #CONTROL_AE_MODE_ON_AUTO_FLASH
* @see #CONTROL_AE_MODE_ON_ALWAYS_FLASH
* @see #CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
+ * @see #CONTROL_AE_MODE_ON_EXTERNAL_FLASH
*/
@PublicKey
public static final Key<Integer> CONTROL_AE_MODE =
@@ -877,7 +879,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* </tr>
* </tbody>
* </table>
- * <p>When {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is AE_MODE_ON_*:</p>
+ * <p>When {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is AE_MODE_ON*:</p>
* <table>
* <thead>
* <tr>
@@ -998,10 +1000,13 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* </tr>
* </tbody>
* </table>
+ * <p>If the camera device supports AE external flash mode (ON_EXTERNAL_FLASH is included in
+ * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES android.control.aeAvailableModes}), aeState must be FLASH_REQUIRED after the camera device
+ * finishes AE scan and it's too dark without flash.</p>
* <p>For the above table, the camera device may skip reporting any state changes that happen
* without application intervention (i.e. mode switch, trigger, locking). Any state that
* can be skipped in that manner is called a transient state.</p>
- * <p>For example, for above AE modes (AE_MODE_ON_*), in addition to the state transitions
+ * <p>For example, for above AE modes (AE_MODE_ON*), in addition to the state transitions
* listed in above table, it is also legal for the camera device to skip one or more
* transient states between two results. See below table for examples:</p>
* <table>
@@ -1072,6 +1077,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
+ * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES
* @see CaptureRequest#CONTROL_AE_LOCK
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
index 005dd6e16880..10667aecd128 100644
--- a/core/java/android/net/INetworkPolicyListener.aidl
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -18,10 +18,9 @@ package android.net;
/** {@hide} */
oneway interface INetworkPolicyListener {
-
void onUidRulesChanged(int uid, int uidRules);
void onMeteredIfacesChanged(in String[] meteredIfaces);
void onRestrictBackgroundChanged(boolean restrictBackground);
void onUidPoliciesChanged(int uid, int uidPolicies);
-
+ void onSubscriptionOverride(int subId, int overrideMask, int overrideValue);
}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 7e37432fb06a..476e2f43649f 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -71,6 +71,7 @@ interface INetworkPolicyManager {
SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
String getSubscriptionPlansOwner(int subId);
+ void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long timeoutMillis, String callingPackage);
void factoryReset(String subscriber);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 9ef26a9f5a5b..2c5a021ec42a 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -113,6 +113,9 @@ public class NetworkPolicyManager {
*/
public static final String EXTRA_NETWORK_TEMPLATE = "android.net.NETWORK_TEMPLATE";
+ public static final int OVERRIDE_UNMETERED = 1 << 0;
+ public static final int OVERRIDE_CONGESTED = 1 << 1;
+
private final Context mContext;
private INetworkPolicyManager mService;
@@ -347,4 +350,13 @@ public class NetworkPolicyManager {
public static String resolveNetworkId(String ssid) {
return WifiInfo.removeDoubleQuotes(ssid);
}
+
+ /** {@hide} */
+ public static class Listener extends INetworkPolicyListener.Stub {
+ @Override public void onUidRulesChanged(int uid, int uidRules) { }
+ @Override public void onMeteredIfacesChanged(String[] meteredIfaces) { }
+ @Override public void onRestrictBackgroundChanged(boolean restrictBackground) { }
+ @Override public void onUidPoliciesChanged(int uid, int uidPolicies) { }
+ @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) { }
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 396c897c7047..3a4a52a021bf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -16,6 +16,14 @@
package android.provider;
+import static android.provider.SettingsValidators.ANY_STRING_VALIDATOR;
+import static android.provider.SettingsValidators.BOOLEAN_VALIDATOR;
+import static android.provider.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR;
+import static android.provider.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
+import static android.provider.SettingsValidators.PACKAGE_NAME_VALIDATOR;
+import static android.provider.SettingsValidators.URI_VALIDATOR;
+
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -65,6 +73,8 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.provider.SettingsValidators;
+import android.provider.SettingsValidators.Validator;
import android.speech.tts.TextToSpeech;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
@@ -76,7 +86,6 @@ import android.util.MemoryIntArray;
import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ILockSettings;
import java.io.IOException;
@@ -2119,11 +2128,6 @@ public final class Settings {
private static final float DEFAULT_FONT_SCALE = 1.0f;
- /** @hide */
- public static interface Validator {
- public boolean validate(String value);
- }
-
/**
* The content:// style URL for this table
*/
@@ -2228,41 +2232,6 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.CERT_PIN_UPDATE_METADATA_URL);
}
- private static final Validator sBooleanValidator =
- new DiscreteValueValidator(new String[] {"0", "1"});
-
- private static final Validator sNonNegativeIntegerValidator = new Validator() {
- @Override
- public boolean validate(String value) {
- try {
- return Integer.parseInt(value) >= 0;
- } catch (NumberFormatException e) {
- return false;
- }
- }
- };
-
- private static final Validator sUriValidator = new Validator() {
- @Override
- public boolean validate(String value) {
- try {
- Uri.decode(value);
- return true;
- } catch (IllegalArgumentException e) {
- return false;
- }
- }
- };
-
- private static final Validator sLenientIpAddressValidator = new Validator() {
- private static final int MAX_IPV6_LENGTH = 45;
-
- @Override
- public boolean validate(String value) {
- return value.length() <= MAX_IPV6_LENGTH;
- }
- };
-
/** @hide */
public static void getMovedToGlobalSettings(Set<String> outKeySet) {
outKeySet.addAll(MOVED_TO_GLOBAL);
@@ -2730,64 +2699,35 @@ public final class Settings {
putIntForUser(cr, SHOW_GTALK_SERVICE_STATUS, flag ? 1 : 0, userHandle);
}
- private static final class DiscreteValueValidator implements Validator {
- private final String[] mValues;
-
- public DiscreteValueValidator(String[] values) {
- mValues = values;
- }
-
- @Override
- public boolean validate(String value) {
- return ArrayUtils.contains(mValues, value);
- }
- }
-
- private static final class InclusiveIntegerRangeValidator implements Validator {
- private final int mMin;
- private final int mMax;
-
- public InclusiveIntegerRangeValidator(int min, int max) {
- mMin = min;
- mMax = max;
- }
-
- @Override
- public boolean validate(String value) {
- try {
- final int intValue = Integer.parseInt(value);
- return intValue >= mMin && intValue <= mMax;
- } catch (NumberFormatException e) {
- return false;
- }
- }
- }
-
- private static final class InclusiveFloatRangeValidator implements Validator {
- private final float mMin;
- private final float mMax;
-
- public InclusiveFloatRangeValidator(float min, float max) {
- mMin = min;
- mMax = max;
- }
+ /**
+ * @deprecated Use {@link android.provider.Settings.Global#STAY_ON_WHILE_PLUGGED_IN} instead
+ */
+ @Deprecated
+ public static final String STAY_ON_WHILE_PLUGGED_IN = Global.STAY_ON_WHILE_PLUGGED_IN;
+ private static final Validator STAY_ON_WHILE_PLUGGED_IN_VALIDATOR = new Validator() {
@Override
public boolean validate(String value) {
try {
- final float floatValue = Float.parseFloat(value);
- return floatValue >= mMin && floatValue <= mMax;
+ int val = Integer.parseInt(value);
+ return (val == 0)
+ || (val == BatteryManager.BATTERY_PLUGGED_AC)
+ || (val == BatteryManager.BATTERY_PLUGGED_USB)
+ || (val == BatteryManager.BATTERY_PLUGGED_WIRELESS)
+ || (val == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_USB))
+ || (val == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+ || (val == (BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+ || (val == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS));
} catch (NumberFormatException e) {
return false;
}
}
- }
-
- /**
- * @deprecated Use {@link android.provider.Settings.Global#STAY_ON_WHILE_PLUGGED_IN} instead
- */
- @Deprecated
- public static final String STAY_ON_WHILE_PLUGGED_IN = Global.STAY_ON_WHILE_PLUGGED_IN;
+ };
/**
* What happens when the user presses the end call button if they're not
@@ -2802,7 +2742,7 @@ public final class Settings {
public static final String END_BUTTON_BEHAVIOR = "end_button_behavior";
private static final Validator END_BUTTON_BEHAVIOR_VALIDATOR =
- new InclusiveIntegerRangeValidator(0, 3);
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 3);
/**
* END_BUTTON_BEHAVIOR value for "go home".
@@ -2828,7 +2768,7 @@ public final class Settings {
*/
public static final String ADVANCED_SETTINGS = "advanced_settings";
- private static final Validator ADVANCED_SETTINGS_VALIDATOR = sBooleanValidator;
+ private static final Validator ADVANCED_SETTINGS_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* ADVANCED_SETTINGS default value.
@@ -2929,7 +2869,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_USE_STATIC_IP = "wifi_use_static_ip";
- private static final Validator WIFI_USE_STATIC_IP_VALIDATOR = sBooleanValidator;
+ private static final Validator WIFI_USE_STATIC_IP_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* The static IP address.
@@ -2941,7 +2881,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_IP = "wifi_static_ip";
- private static final Validator WIFI_STATIC_IP_VALIDATOR = sLenientIpAddressValidator;
+ private static final Validator WIFI_STATIC_IP_VALIDATOR = LENIENT_IP_ADDRESS_VALIDATOR;
/**
* If using static IP, the gateway's IP address.
@@ -2953,7 +2893,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_GATEWAY = "wifi_static_gateway";
- private static final Validator WIFI_STATIC_GATEWAY_VALIDATOR = sLenientIpAddressValidator;
+ private static final Validator WIFI_STATIC_GATEWAY_VALIDATOR = LENIENT_IP_ADDRESS_VALIDATOR;
/**
* If using static IP, the net mask.
@@ -2965,7 +2905,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_NETMASK = "wifi_static_netmask";
- private static final Validator WIFI_STATIC_NETMASK_VALIDATOR = sLenientIpAddressValidator;
+ private static final Validator WIFI_STATIC_NETMASK_VALIDATOR = LENIENT_IP_ADDRESS_VALIDATOR;
/**
* If using static IP, the primary DNS's IP address.
@@ -2977,7 +2917,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_DNS1 = "wifi_static_dns1";
- private static final Validator WIFI_STATIC_DNS1_VALIDATOR = sLenientIpAddressValidator;
+ private static final Validator WIFI_STATIC_DNS1_VALIDATOR = LENIENT_IP_ADDRESS_VALIDATOR;
/**
* If using static IP, the secondary DNS's IP address.
@@ -2989,7 +2929,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_DNS2 = "wifi_static_dns2";
- private static final Validator WIFI_STATIC_DNS2_VALIDATOR = sLenientIpAddressValidator;
+ private static final Validator WIFI_STATIC_DNS2_VALIDATOR = LENIENT_IP_ADDRESS_VALIDATOR;
/**
* Determines whether remote devices may discover and/or connect to
@@ -3003,7 +2943,7 @@ public final class Settings {
"bluetooth_discoverability";
private static final Validator BLUETOOTH_DISCOVERABILITY_VALIDATOR =
- new InclusiveIntegerRangeValidator(0, 2);
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 2);
/**
* Bluetooth discoverability timeout. If this value is nonzero, then
@@ -3014,7 +2954,7 @@ public final class Settings {
"bluetooth_discoverability_timeout";
private static final Validator BLUETOOTH_DISCOVERABILITY_TIMEOUT_VALIDATOR =
- sNonNegativeIntegerValidator;
+ NON_NEGATIVE_INTEGER_VALIDATOR;
/**
* @deprecated Use {@link android.provider.Settings.Secure#LOCK_PATTERN_ENABLED}
@@ -3110,7 +3050,7 @@ public final class Settings {
@Deprecated
public static final String DIM_SCREEN = "dim_screen";
- private static final Validator DIM_SCREEN_VALIDATOR = sBooleanValidator;
+ private static final Validator DIM_SCREEN_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* The display color mode.
@@ -3130,7 +3070,8 @@ public final class Settings {
*/
public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
- private static final Validator SCREEN_OFF_TIMEOUT_VALIDATOR = sNonNegativeIntegerValidator;
+ private static final Validator SCREEN_OFF_TIMEOUT_VALIDATOR =
+ NON_NEGATIVE_INTEGER_VALIDATOR;
/**
* The screen backlight brightness between 0 and 255.
@@ -3138,7 +3079,7 @@ public final class Settings {
public static final String SCREEN_BRIGHTNESS = "screen_brightness";
private static final Validator SCREEN_BRIGHTNESS_VALIDATOR =
- new InclusiveIntegerRangeValidator(0, 255);
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 255);
/**
* The screen backlight brightness between 0 and 255.
@@ -3147,14 +3088,14 @@ public final class Settings {
public static final String SCREEN_BRIGHTNESS_FOR_VR = "screen_brightness_for_vr";
private static final Validator SCREEN_BRIGHTNESS_FOR_VR_VALIDATOR =
- new InclusiveIntegerRangeValidator(0, 255);
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 255);
/**
* Control whether to enable automatic brightness mode.
*/
public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
- private static final Validator SCREEN_BRIGHTNESS_MODE_VALIDATOR = sBooleanValidator;
+ private static final Validator SCREEN_BRIGHTNESS_MODE_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Adjustment to auto-brightness to make it generally more (>0.0 <1.0)
@@ -3164,7 +3105,7 @@ public final class Settings {
public static final String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj";
private static final Validator SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR =
- new InclusiveFloatRangeValidator(-1, 1);
+ new SettingsValidators.InclusiveFloatRangeValidator(-1, 1);
/**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
@@ -3203,7 +3144,7 @@ public final class Settings {
public static final String MODE_RINGER_STREAMS_AFFECTED = "mode_ringer_streams_affected";
private static final Validator MODE_RINGER_STREAMS_AFFECTED_VALIDATOR =
- sNonNegativeIntegerValidator;
+ NON_NEGATIVE_INTEGER_VALIDATOR;
/**
* Determines which streams are affected by mute. The
@@ -3213,7 +3154,7 @@ public final class Settings {
public static final String MUTE_STREAMS_AFFECTED = "mute_streams_affected";
private static final Validator MUTE_STREAMS_AFFECTED_VALIDATOR =
- sNonNegativeIntegerValidator;
+ NON_NEGATIVE_INTEGER_VALIDATOR;
/**
* Whether vibrate is on for different events. This is used internally,
@@ -3221,7 +3162,7 @@ public final class Settings {
*/
public static final String VIBRATE_ON = "vibrate_on";
- private static final Validator VIBRATE_ON_VALIDATOR = sBooleanValidator;
+ private static final Validator VIBRATE_ON_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* If 1, redirects the system vibrator to all currently attached input devices
@@ -3237,7 +3178,7 @@ public final class Settings {
*/
public static final String VIBRATE_INPUT_DEVICES = "vibrate_input_devices";
- private static final Validator VIBRATE_INPUT_DEVICES_VALIDATOR = sBooleanValidator;
+ private static final Validator VIBRATE_INPUT_DEVICES_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Ringer volume. This is used internally, changing this value will not
@@ -3316,7 +3257,7 @@ public final class Settings {
*/
public static final String MASTER_MONO = "master_mono";
- private static final Validator MASTER_MONO_VALIDATOR = sBooleanValidator;
+ private static final Validator MASTER_MONO_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Whether the notifications should use the ring volume (value of 1) or
@@ -3336,7 +3277,7 @@ public final class Settings {
public static final String NOTIFICATIONS_USE_RING_VOLUME =
"notifications_use_ring_volume";
- private static final Validator NOTIFICATIONS_USE_RING_VOLUME_VALIDATOR = sBooleanValidator;
+ private static final Validator NOTIFICATIONS_USE_RING_VOLUME_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Whether silent mode should allow vibration feedback. This is used
@@ -3352,7 +3293,7 @@ public final class Settings {
*/
public static final String VIBRATE_IN_SILENT = "vibrate_in_silent";
- private static final Validator VIBRATE_IN_SILENT_VALIDATOR = sBooleanValidator;
+ private static final Validator VIBRATE_IN_SILENT_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* The mapping of stream type (integer) to its setting.
@@ -3400,7 +3341,7 @@ public final class Settings {
*/
public static final String RINGTONE = "ringtone";
- private static final Validator RINGTONE_VALIDATOR = sUriValidator;
+ private static final Validator RINGTONE_VALIDATOR = URI_VALIDATOR;
/**
* A {@link Uri} that will point to the current default ringtone at any
@@ -3425,7 +3366,7 @@ public final class Settings {
*/
public static final String NOTIFICATION_SOUND = "notification_sound";
- private static final Validator NOTIFICATION_SOUND_VALIDATOR = sUriValidator;
+ private static final Validator NOTIFICATION_SOUND_VALIDATOR = URI_VALIDATOR;
/**
* A {@link Uri} that will point to the current default notification
@@ -3448,7 +3389,7 @@ public final class Settings {
*/
public static final String ALARM_ALERT = "alarm_alert";
- private static final Validator ALARM_ALERT_VALIDATOR = sUriValidator;
+ private static final Validator ALARM_ALERT_VALIDATOR = URI_VALIDATOR;
/**
* A {@link Uri} that will point to the current default alarm alert at
@@ -3470,31 +3411,21 @@ public final class Settings {
*/
public static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver";
- private static final Validator MEDIA_BUTTON_RECEIVER_VALIDATOR = new Validator() {
- @Override
- public boolean validate(String value) {
- try {
- ComponentName.unflattenFromString(value);
- return true;
- } catch (NullPointerException e) {
- return false;
- }
- }
- };
+ private static final Validator MEDIA_BUTTON_RECEIVER_VALIDATOR = COMPONENT_NAME_VALIDATOR;
/**
* Setting to enable Auto Replace (AutoText) in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_AUTO_REPLACE = "auto_replace";
- private static final Validator TEXT_AUTO_REPLACE_VALIDATOR = sBooleanValidator;
+ private static final Validator TEXT_AUTO_REPLACE_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Setting to enable Auto Caps in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_AUTO_CAPS = "auto_caps";
- private static final Validator TEXT_AUTO_CAPS_VALIDATOR = sBooleanValidator;
+ private static final Validator TEXT_AUTO_CAPS_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Setting to enable Auto Punctuate in text editors. 1 = On, 0 = Off. This
@@ -3502,19 +3433,19 @@ public final class Settings {
*/
public static final String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
- private static final Validator TEXT_AUTO_PUNCTUATE_VALIDATOR = sBooleanValidator;
+ private static final Validator TEXT_AUTO_PUNCTUATE_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Setting to showing password characters in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_SHOW_PASSWORD = "show_password";
- private static final Validator TEXT_SHOW_PASSWORD_VALIDATOR = sBooleanValidator;
+ private static final Validator TEXT_SHOW_PASSWORD_VALIDATOR = BOOLEAN_VALIDATOR;
public static final String SHOW_GTALK_SERVICE_STATUS =
"SHOW_GTALK_SERVICE_STATUS";
- private static final Validator SHOW_GTALK_SERVICE_STATUS_VALIDATOR = sBooleanValidator;
+ private static final Validator SHOW_GTALK_SERVICE_STATUS_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Name of activity to use for wallpaper on the home screen.
@@ -3543,6 +3474,8 @@ public final class Settings {
@Deprecated
public static final String AUTO_TIME = Global.AUTO_TIME;
+ private static final Validator AUTO_TIME_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#AUTO_TIME_ZONE}
* instead
@@ -3550,6 +3483,8 @@ public final class Settings {
@Deprecated
public static final String AUTO_TIME_ZONE = Global.AUTO_TIME_ZONE;
+ private static final Validator AUTO_TIME_ZONE_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* Display times as 12 or 24 hours
* 12
@@ -3559,7 +3494,7 @@ public final class Settings {
/** @hide */
public static final Validator TIME_12_24_VALIDATOR =
- new DiscreteValueValidator(new String[] {"12", "24", null});
+ new SettingsValidators.DiscreteValueValidator(new String[] {"12", "24", null});
/**
* Date format string
@@ -3592,7 +3527,7 @@ public final class Settings {
public static final String SETUP_WIZARD_HAS_RUN = "setup_wizard_has_run";
/** @hide */
- public static final Validator SETUP_WIZARD_HAS_RUN_VALIDATOR = sBooleanValidator;
+ public static final Validator SETUP_WIZARD_HAS_RUN_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Scaling factor for normal window animations. Setting to 0 will disable window
@@ -3631,7 +3566,7 @@ public final class Settings {
public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation";
/** @hide */
- public static final Validator ACCELEROMETER_ROTATION_VALIDATOR = sBooleanValidator;
+ public static final Validator ACCELEROMETER_ROTATION_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Default screen rotation when no other policy applies.
@@ -3645,7 +3580,7 @@ public final class Settings {
/** @hide */
public static final Validator USER_ROTATION_VALIDATOR =
- new InclusiveIntegerRangeValidator(0, 3);
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 3);
/**
* Control whether the rotation lock toggle in the System UI should be hidden.
@@ -3663,7 +3598,7 @@ public final class Settings {
/** @hide */
public static final Validator HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY_VALIDATOR =
- sBooleanValidator;
+ BOOLEAN_VALIDATOR;
/**
* Whether the phone vibrates when it is ringing due to an incoming call. This will
@@ -3678,7 +3613,7 @@ public final class Settings {
public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing";
/** @hide */
- public static final Validator VIBRATE_WHEN_RINGING_VALIDATOR = sBooleanValidator;
+ public static final Validator VIBRATE_WHEN_RINGING_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Whether the audible DTMF tones are played by the dialer when dialing. The value is
@@ -3687,7 +3622,7 @@ public final class Settings {
public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone";
/** @hide */
- public static final Validator DTMF_TONE_WHEN_DIALING_VALIDATOR = sBooleanValidator;
+ public static final Validator DTMF_TONE_WHEN_DIALING_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* CDMA only settings
@@ -3698,7 +3633,7 @@ public final class Settings {
public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type";
/** @hide */
- public static final Validator DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR = sBooleanValidator;
+ public static final Validator DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Whether the hearing aid is enabled. The value is
@@ -3708,7 +3643,7 @@ public final class Settings {
public static final String HEARING_AID = "hearing_aid";
/** @hide */
- public static final Validator HEARING_AID_VALIDATOR = sBooleanValidator;
+ public static final Validator HEARING_AID_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* CDMA only settings
@@ -3722,7 +3657,8 @@ public final class Settings {
public static final String TTY_MODE = "tty_mode";
/** @hide */
- public static final Validator TTY_MODE_VALIDATOR = new InclusiveIntegerRangeValidator(0, 3);
+ public static final Validator TTY_MODE_VALIDATOR =
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 3);
/**
* Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
@@ -3731,7 +3667,7 @@ public final class Settings {
public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
/** @hide */
- public static final Validator SOUND_EFFECTS_ENABLED_VALIDATOR = sBooleanValidator;
+ public static final Validator SOUND_EFFECTS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Whether the haptic feedback (long presses, ...) are enabled. The value is
@@ -3740,7 +3676,7 @@ public final class Settings {
public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled";
/** @hide */
- public static final Validator HAPTIC_FEEDBACK_ENABLED_VALIDATOR = sBooleanValidator;
+ public static final Validator HAPTIC_FEEDBACK_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* @deprecated Each application that shows web suggestions should have its own
@@ -3750,7 +3686,7 @@ public final class Settings {
public static final String SHOW_WEB_SUGGESTIONS = "show_web_suggestions";
/** @hide */
- public static final Validator SHOW_WEB_SUGGESTIONS_VALIDATOR = sBooleanValidator;
+ public static final Validator SHOW_WEB_SUGGESTIONS_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Whether the notification LED should repeatedly flash when a notification is
@@ -3760,7 +3696,7 @@ public final class Settings {
public static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse";
/** @hide */
- public static final Validator NOTIFICATION_LIGHT_PULSE_VALIDATOR = sBooleanValidator;
+ public static final Validator NOTIFICATION_LIGHT_PULSE_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Show pointer location on screen?
@@ -3771,7 +3707,7 @@ public final class Settings {
public static final String POINTER_LOCATION = "pointer_location";
/** @hide */
- public static final Validator POINTER_LOCATION_VALIDATOR = sBooleanValidator;
+ public static final Validator POINTER_LOCATION_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Show touch positions on screen?
@@ -3782,7 +3718,7 @@ public final class Settings {
public static final String SHOW_TOUCHES = "show_touches";
/** @hide */
- public static final Validator SHOW_TOUCHES_VALIDATOR = sBooleanValidator;
+ public static final Validator SHOW_TOUCHES_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Log raw orientation data from
@@ -3796,7 +3732,7 @@ public final class Settings {
"window_orientation_listener_log";
/** @hide */
- public static final Validator WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR = sBooleanValidator;
+ public static final Validator WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* @deprecated Use {@link android.provider.Settings.Global#POWER_SOUNDS_ENABLED}
@@ -3806,6 +3742,8 @@ public final class Settings {
@Deprecated
public static final String POWER_SOUNDS_ENABLED = Global.POWER_SOUNDS_ENABLED;
+ private static final Validator POWER_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#DOCK_SOUNDS_ENABLED}
* instead
@@ -3814,6 +3752,8 @@ public final class Settings {
@Deprecated
public static final String DOCK_SOUNDS_ENABLED = Global.DOCK_SOUNDS_ENABLED;
+ private static final Validator DOCK_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* Whether to play sounds when the keyguard is shown and dismissed.
* @hide
@@ -3821,7 +3761,7 @@ public final class Settings {
public static final String LOCKSCREEN_SOUNDS_ENABLED = "lockscreen_sounds_enabled";
/** @hide */
- public static final Validator LOCKSCREEN_SOUNDS_ENABLED_VALIDATOR = sBooleanValidator;
+ public static final Validator LOCKSCREEN_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Whether the lockscreen should be completely disabled.
@@ -3830,7 +3770,7 @@ public final class Settings {
public static final String LOCKSCREEN_DISABLED = "lockscreen.disabled";
/** @hide */
- public static final Validator LOCKSCREEN_DISABLED_VALIDATOR = sBooleanValidator;
+ public static final Validator LOCKSCREEN_DISABLED_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* @deprecated Use {@link android.provider.Settings.Global#LOW_BATTERY_SOUND}
@@ -3897,7 +3837,7 @@ public final class Settings {
public static final String SIP_RECEIVE_CALLS = "sip_receive_calls";
/** @hide */
- public static final Validator SIP_RECEIVE_CALLS_VALIDATOR = sBooleanValidator;
+ public static final Validator SIP_RECEIVE_CALLS_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Call Preference String.
@@ -3908,8 +3848,9 @@ public final class Settings {
public static final String SIP_CALL_OPTIONS = "sip_call_options";
/** @hide */
- public static final Validator SIP_CALL_OPTIONS_VALIDATOR = new DiscreteValueValidator(
- new String[] {"SIP_ALWAYS", "SIP_ADDRESS_ONLY"});
+ public static final Validator SIP_CALL_OPTIONS_VALIDATOR =
+ new SettingsValidators.DiscreteValueValidator(
+ new String[] {"SIP_ALWAYS", "SIP_ADDRESS_ONLY"});
/**
* One of the sip call options: Always use SIP with network access.
@@ -3918,7 +3859,7 @@ public final class Settings {
public static final String SIP_ALWAYS = "SIP_ALWAYS";
/** @hide */
- public static final Validator SIP_ALWAYS_VALIDATOR = sBooleanValidator;
+ public static final Validator SIP_ALWAYS_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* One of the sip call options: Only if destination is a SIP address.
@@ -3927,7 +3868,7 @@ public final class Settings {
public static final String SIP_ADDRESS_ONLY = "SIP_ADDRESS_ONLY";
/** @hide */
- public static final Validator SIP_ADDRESS_ONLY_VALIDATOR = sBooleanValidator;
+ public static final Validator SIP_ADDRESS_ONLY_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* @deprecated Use SIP_ALWAYS or SIP_ADDRESS_ONLY instead. Formerly used to indicate that
@@ -3940,7 +3881,7 @@ public final class Settings {
public static final String SIP_ASK_ME_EACH_TIME = "SIP_ASK_ME_EACH_TIME";
/** @hide */
- public static final Validator SIP_ASK_ME_EACH_TIME_VALIDATOR = sBooleanValidator;
+ public static final Validator SIP_ASK_ME_EACH_TIME_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* Pointer speed setting.
@@ -3954,7 +3895,7 @@ public final class Settings {
/** @hide */
public static final Validator POINTER_SPEED_VALIDATOR =
- new InclusiveFloatRangeValidator(-7, 7);
+ new SettingsValidators.InclusiveFloatRangeValidator(-7, 7);
/**
* Whether lock-to-app will be triggered by long-press on recents.
@@ -3963,7 +3904,7 @@ public final class Settings {
public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled";
/** @hide */
- public static final Validator LOCK_TO_APP_ENABLED_VALIDATOR = sBooleanValidator;
+ public static final Validator LOCK_TO_APP_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* I am the lolrus.
@@ -3995,7 +3936,7 @@ public final class Settings {
public static final String SHOW_BATTERY_PERCENT = "status_bar_show_battery_percent";
/** @hide */
- private static final Validator SHOW_BATTERY_PERCENT_VALIDATOR = sBooleanValidator;
+ private static final Validator SHOW_BATTERY_PERCENT_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* IMPORTANT: If you add a new public settings you also have to add it to
@@ -4179,7 +4120,8 @@ public final class Settings {
*/
public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
static {
- VALIDATORS.put(END_BUTTON_BEHAVIOR,END_BUTTON_BEHAVIOR_VALIDATOR);
+ VALIDATORS.put(STAY_ON_WHILE_PLUGGED_IN, STAY_ON_WHILE_PLUGGED_IN_VALIDATOR);
+ VALIDATORS.put(END_BUTTON_BEHAVIOR, END_BUTTON_BEHAVIOR_VALIDATOR);
VALIDATORS.put(WIFI_USE_STATIC_IP, WIFI_USE_STATIC_IP_VALIDATOR);
VALIDATORS.put(BLUETOOTH_DISCOVERABILITY, BLUETOOTH_DISCOVERABILITY_VALIDATOR);
VALIDATORS.put(BLUETOOTH_DISCOVERABILITY_TIMEOUT,
@@ -4201,6 +4143,8 @@ public final class Settings {
VALIDATORS.put(TEXT_AUTO_CAPS, TEXT_AUTO_CAPS_VALIDATOR);
VALIDATORS.put(TEXT_AUTO_PUNCTUATE, TEXT_AUTO_PUNCTUATE_VALIDATOR);
VALIDATORS.put(TEXT_SHOW_PASSWORD, TEXT_SHOW_PASSWORD_VALIDATOR);
+ VALIDATORS.put(AUTO_TIME, AUTO_TIME_VALIDATOR);
+ VALIDATORS.put(AUTO_TIME_ZONE, AUTO_TIME_ZONE_VALIDATOR);
VALIDATORS.put(SHOW_GTALK_SERVICE_STATUS, SHOW_GTALK_SERVICE_STATUS_VALIDATOR);
VALIDATORS.put(WALLPAPER_ACTIVITY, WALLPAPER_ACTIVITY_VALIDATOR);
VALIDATORS.put(TIME_12_24, TIME_12_24_VALIDATOR);
@@ -4211,6 +4155,8 @@ public final class Settings {
VALIDATORS.put(DTMF_TONE_WHEN_DIALING, DTMF_TONE_WHEN_DIALING_VALIDATOR);
VALIDATORS.put(SOUND_EFFECTS_ENABLED, SOUND_EFFECTS_ENABLED_VALIDATOR);
VALIDATORS.put(HAPTIC_FEEDBACK_ENABLED, HAPTIC_FEEDBACK_ENABLED_VALIDATOR);
+ VALIDATORS.put(POWER_SOUNDS_ENABLED, POWER_SOUNDS_ENABLED_VALIDATOR);
+ VALIDATORS.put(DOCK_SOUNDS_ENABLED, DOCK_SOUNDS_ENABLED_VALIDATOR);
VALIDATORS.put(SHOW_WEB_SUGGESTIONS, SHOW_WEB_SUGGESTIONS_VALIDATOR);
VALIDATORS.put(WIFI_USE_STATIC_IP, WIFI_USE_STATIC_IP_VALIDATOR);
VALIDATORS.put(END_BUTTON_BEHAVIOR, END_BUTTON_BEHAVIOR_VALIDATOR);
@@ -4335,6 +4281,8 @@ public final class Settings {
@Deprecated
public static final String BLUETOOTH_ON = Global.BLUETOOTH_ON;
+ private static final Validator BLUETOOTH_ON_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#DATA_ROAMING} instead
*/
@@ -4412,6 +4360,8 @@ public final class Settings {
@Deprecated
public static final String USB_MASS_STORAGE_ENABLED = Global.USB_MASS_STORAGE_ENABLED;
+ private static final Validator USB_MASS_STORAGE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#USE_GOOGLE_MAIL} instead
*/
@@ -4441,6 +4391,9 @@ public final class Settings {
public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON;
+ private static final Validator WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR =
+ BOOLEAN_VALIDATOR;
+
/**
* @deprecated Use
* {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} instead
@@ -5218,6 +5171,8 @@ public final class Settings {
@Deprecated
public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
+ private static final Validator BUGREPORT_IN_POWER_MENU_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#ADB_ENABLED} instead
*/
@@ -5280,6 +5235,8 @@ public final class Settings {
@Deprecated
public static final String BLUETOOTH_ON = Global.BLUETOOTH_ON;
+ private static final Validator BLUETOOTH_ON_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#DATA_ROAMING} instead
*/
@@ -5707,6 +5664,8 @@ public final class Settings {
@Deprecated
public static final String USB_MASS_STORAGE_ENABLED = Global.USB_MASS_STORAGE_ENABLED;
+ private static final Validator USB_MASS_STORAGE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#USE_GOOGLE_MAIL} instead
*/
@@ -6174,6 +6133,9 @@ public final class Settings {
public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON;
+ private static final Validator WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR =
+ BOOLEAN_VALIDATOR;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY}
* instead.
@@ -7805,12 +7767,16 @@ public final class Settings {
*/
public static final String AUTO_TIME = "auto_time";
+ private static final Validator AUTO_TIME_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* Value to specify if the user prefers the time zone
* to be automatically fetched from the network (NITZ). 1=yes, 0=no
*/
public static final String AUTO_TIME_ZONE = "auto_time_zone";
+ private static final Validator AUTO_TIME_ZONE_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* URI for the car dock "in" event sound.
* @hide
@@ -7841,6 +7807,8 @@ public final class Settings {
*/
public static final String DOCK_SOUNDS_ENABLED = "dock_sounds_enabled";
+ private static final Validator DOCK_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* Whether to play a sound for dock events, only when an accessibility service is on.
* @hide
@@ -7878,6 +7846,8 @@ public final class Settings {
*/
public static final String POWER_SOUNDS_ENABLED = "power_sounds_enabled";
+ private static final Validator POWER_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* URI for the "wireless charging started" sound.
* @hide
@@ -7891,6 +7861,8 @@ public final class Settings {
*/
public static final String CHARGING_SOUNDS_ENABLED = "charging_sounds_enabled";
+ private static final Validator CHARGING_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* Whether we keep the device on while the device is plugged in.
* Supported values are:
@@ -7904,6 +7876,30 @@ public final class Settings {
*/
public static final String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
+ private static final Validator STAY_ON_WHILE_PLUGGED_IN_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ int val = Integer.parseInt(value);
+ return (val == 0)
+ || (val == BatteryManager.BATTERY_PLUGGED_AC)
+ || (val == BatteryManager.BATTERY_PLUGGED_USB)
+ || (val == BatteryManager.BATTERY_PLUGGED_WIRELESS)
+ || (val == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_USB))
+ || (val == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+ || (val == (BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+ || (val == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS));
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
/**
* When the user has enable the option to have a "bug report" command
* in the power menu.
@@ -7911,6 +7907,8 @@ public final class Settings {
*/
public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
+ private static final Validator BUGREPORT_IN_POWER_MENU_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* Whether ADB is enabled.
*/
@@ -7934,6 +7932,8 @@ public final class Settings {
*/
public static final String BLUETOOTH_ON = "bluetooth_on";
+ private static final Validator BLUETOOTH_ON_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* CDMA Cell Broadcast SMS
* 0 = CDMA Cell Broadcast SMS disabled
@@ -8552,6 +8552,8 @@ public final class Settings {
*/
public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
+ private static final Validator USB_MASS_STORAGE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* If this setting is set (to anything), then all references
* to Gmail on the device must change to Google Mail.
@@ -8688,6 +8690,9 @@ public final class Settings {
public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
"wifi_networks_available_notification_on";
+ private static final Validator WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR =
+ BOOLEAN_VALIDATOR;
+
/**
* {@hide}
*/
@@ -8750,6 +8755,8 @@ public final class Settings {
*/
public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
+ private static final Validator SOFT_AP_TIMEOUT_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* Value to specify if Wi-Fi Wakeup feature is enabled.
*
@@ -8759,6 +8766,8 @@ public final class Settings {
@SystemApi
public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
+ private static final Validator WIFI_WAKEUP_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* Value to specify if Wi-Fi Wakeup is available.
*
@@ -8805,6 +8814,9 @@ public final class Settings {
public static final String NETWORK_RECOMMENDATIONS_ENABLED =
"network_recommendations_enabled";
+ private static final Validator NETWORK_RECOMMENDATIONS_ENABLED_VALIDATOR =
+ new SettingsValidators.DiscreteValueValidator(new String[] {"-1", "0", "1"});
+
/**
* Which package name to use for network recommendations. If null, network recommendations
* will neither be requested nor accepted.
@@ -8828,6 +8840,13 @@ public final class Settings {
@TestApi
public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
+ private static final Validator USE_OPEN_WIFI_PACKAGE_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ return (value == null) || PACKAGE_NAME_VALIDATOR.validate(value);
+ }
+ };
+
/**
* The number of milliseconds the {@link com.android.server.NetworkScoreService}
* will give a recommendation request to complete before returning a default response.
@@ -8907,6 +8926,9 @@ public final class Settings {
public static final String WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED =
"wifi_watchdog_poor_network_test_enabled";
+ private static final Validator WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED_VALIDATOR =
+ ANY_STRING_VALIDATOR;
+
/**
* Setting to turn on suspend optimizations at screen off on Wi-Fi. Enabled by default and
* needs to be set to 0 to disable it.
@@ -9442,11 +9464,16 @@ public final class Settings {
* @hide
*/
public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+
+ private static final Validator PRIVATE_DNS_MODE_VALIDATOR = ANY_STRING_VALIDATOR;
+
/**
* @hide
*/
public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
+ private static final Validator PRIVATE_DNS_SPECIFIER_VALIDATOR = ANY_STRING_VALIDATOR;
+
/** {@hide} */
public static final String
BLUETOOTH_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
@@ -9861,15 +9888,6 @@ public final class Settings {
public static final String FORCED_APP_STANDBY_ENABLED = "forced_app_standby_enabled";
/**
- * Whether or not to enable Forced App Standby on small battery devices.
- * Type: int (0 for false, 1 for true)
- * Default: 0
- * @hide
- */
- public static final String FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED
- = "forced_app_standby_for_small_battery_enabled";
-
- /**
* Whether or not Network Watchlist feature is enabled.
* Type: int (0 for false, 1 for true)
* Default: 0
@@ -10020,6 +10038,9 @@ public final class Settings {
*/
public static final String EMERGENCY_TONE = "emergency_tone";
+ private static final Validator EMERGENCY_TONE_VALIDATOR =
+ new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1", "2"});
+
/**
* CDMA only settings
* Whether the auto retry is enabled. The value is
@@ -10028,6 +10049,8 @@ public final class Settings {
*/
public static final String CALL_AUTO_RETRY = "call_auto_retry";
+ private static final Validator CALL_AUTO_RETRY_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* A setting that can be read whether the emergency affordance is currently needed.
* The value is a boolean (1 or 0).
@@ -10097,6 +10120,9 @@ public final class Settings {
*/
public static final String LOW_POWER_MODE_TRIGGER_LEVEL = "low_power_trigger_level";
+ private static final Validator LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR =
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 99);
+
/**
* If not 0, the activity manager will aggressively finish activities and
* processes as soon as they are no longer needed. If 0, the normal
@@ -10112,6 +10138,8 @@ public final class Settings {
*/
public static final String DOCK_AUDIO_MEDIA_ENABLED = "dock_audio_media_enabled";
+ private static final Validator DOCK_AUDIO_MEDIA_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
/**
* The surround sound formats AC3, DTS or IEC61937 are
* available for use if they are detected.
@@ -10158,6 +10186,9 @@ public final class Settings {
*/
public static final String ENCODED_SURROUND_OUTPUT = "encoded_surround_output";
+ private static final Validator ENCODED_SURROUND_OUTPUT_VALIDATOR =
+ new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1", "2"});
+
/**
* Persisted safe headphone volume management state by AudioService
* @hide
@@ -10698,6 +10729,41 @@ public final class Settings {
};
/**
+ * All settings in {@link SETTINGS_TO_BACKUP} array *must* have a non-null validator,
+ * otherwise they won't be restored.
+ *
+ * @hide
+ */
+ public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
+ static {
+ VALIDATORS.put(BUGREPORT_IN_POWER_MENU, BUGREPORT_IN_POWER_MENU_VALIDATOR);
+ VALIDATORS.put(STAY_ON_WHILE_PLUGGED_IN, STAY_ON_WHILE_PLUGGED_IN_VALIDATOR);
+ VALIDATORS.put(AUTO_TIME, AUTO_TIME_VALIDATOR);
+ VALIDATORS.put(AUTO_TIME_ZONE, AUTO_TIME_ZONE_VALIDATOR);
+ VALIDATORS.put(POWER_SOUNDS_ENABLED, POWER_SOUNDS_ENABLED_VALIDATOR);
+ VALIDATORS.put(DOCK_SOUNDS_ENABLED, DOCK_SOUNDS_ENABLED_VALIDATOR);
+ VALIDATORS.put(CHARGING_SOUNDS_ENABLED, CHARGING_SOUNDS_ENABLED_VALIDATOR);
+ VALIDATORS.put(USB_MASS_STORAGE_ENABLED, USB_MASS_STORAGE_ENABLED_VALIDATOR);
+ VALIDATORS.put(NETWORK_RECOMMENDATIONS_ENABLED,
+ NETWORK_RECOMMENDATIONS_ENABLED_VALIDATOR);
+ VALIDATORS.put(WIFI_WAKEUP_ENABLED, WIFI_WAKEUP_ENABLED_VALIDATOR);
+ VALIDATORS.put(WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR);
+ VALIDATORS.put(USE_OPEN_WIFI_PACKAGE, USE_OPEN_WIFI_PACKAGE_VALIDATOR);
+ VALIDATORS.put(WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
+ WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED_VALIDATOR);
+ VALIDATORS.put(EMERGENCY_TONE, EMERGENCY_TONE_VALIDATOR);
+ VALIDATORS.put(CALL_AUTO_RETRY, CALL_AUTO_RETRY_VALIDATOR);
+ VALIDATORS.put(DOCK_AUDIO_MEDIA_ENABLED, DOCK_AUDIO_MEDIA_ENABLED_VALIDATOR);
+ VALIDATORS.put(ENCODED_SURROUND_OUTPUT, ENCODED_SURROUND_OUTPUT_VALIDATOR);
+ VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL, LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
+ VALIDATORS.put(BLUETOOTH_ON, BLUETOOTH_ON_VALIDATOR);
+ VALIDATORS.put(PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_VALIDATOR);
+ VALIDATORS.put(PRIVATE_DNS_SPECIFIER, PRIVATE_DNS_SPECIFIER_VALIDATOR);
+ VALIDATORS.put(SOFT_AP_TIMEOUT_ENABLED, SOFT_AP_TIMEOUT_ENABLED_VALIDATOR);
+ }
+
+ /**
* Global settings that shouldn't be persisted.
*
* @hide
diff --git a/core/java/android/provider/SettingsValidators.java b/core/java/android/provider/SettingsValidators.java
new file mode 100644
index 000000000000..22ef3b6f346f
--- /dev/null
+++ b/core/java/android/provider/SettingsValidators.java
@@ -0,0 +1,169 @@
+/*
+ * 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.provider;
+
+import android.content.ComponentName;
+import android.net.Uri;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * This class provides both interface for validation and common validators
+ * used to ensure Settings have meaningful values.
+ *
+ * @hide
+ */
+public class SettingsValidators {
+
+ public static final Validator BOOLEAN_VALIDATOR =
+ new DiscreteValueValidator(new String[] {"0", "1"});
+
+ public static final Validator ANY_STRING_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ return true;
+ }
+ };
+
+ public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ return Integer.parseInt(value) >= 0;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
+ public static final Validator URI_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ Uri.decode(value);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+ };
+
+ public static final Validator COMPONENT_NAME_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ return ComponentName.unflattenFromString(value) != null;
+ }
+ };
+
+ public static final Validator PACKAGE_NAME_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ return value != null && isStringPackageName(value);
+ }
+
+ private boolean isStringPackageName(String value) {
+ // The name may contain uppercase or lowercase letters ('A' through 'Z'), numbers,
+ // and underscores ('_'). However, individual package name parts may only
+ // start with letters.
+ // (https://developer.android.com/guide/topics/manifest/manifest-element.html#package)
+ String[] subparts = value.split(".");
+ boolean isValidPackageName = true;
+ for (String subpart : subparts) {
+ isValidPackageName |= isSubpartValidForPackageName(subpart);
+ if (!isValidPackageName) break;
+ }
+ return isValidPackageName;
+ }
+
+ private boolean isSubpartValidForPackageName(String subpart) {
+ if (subpart.length() == 0) return false;
+ boolean isValidSubpart = Character.isLetter(subpart.charAt(0));
+ for (int i = 1; i < subpart.length(); i++) {
+ isValidSubpart |= (Character.isLetterOrDigit(subpart.charAt(i))
+ || (subpart.charAt(i) == '_'));
+ if (!isValidSubpart) break;
+ }
+ return isValidSubpart;
+ }
+ };
+
+ public static final Validator LENIENT_IP_ADDRESS_VALIDATOR = new Validator() {
+ private static final int MAX_IPV6_LENGTH = 45;
+
+ @Override
+ public boolean validate(String value) {
+ return value.length() <= MAX_IPV6_LENGTH;
+ }
+ };
+
+ public interface Validator {
+ boolean validate(String value);
+ }
+
+ public static final class DiscreteValueValidator implements Validator {
+ private final String[] mValues;
+
+ public DiscreteValueValidator(String[] values) {
+ mValues = values;
+ }
+
+ @Override
+ public boolean validate(String value) {
+ return ArrayUtils.contains(mValues, value);
+ }
+ }
+
+ public static final class InclusiveIntegerRangeValidator implements Validator {
+ private final int mMin;
+ private final int mMax;
+
+ public InclusiveIntegerRangeValidator(int min, int max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ @Override
+ public boolean validate(String value) {
+ try {
+ final int intValue = Integer.parseInt(value);
+ return intValue >= mMin && intValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+
+ public static final class InclusiveFloatRangeValidator implements Validator {
+ private final float mMin;
+ private final float mMax;
+
+ public InclusiveFloatRangeValidator(float min, float max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ @Override
+ public boolean validate(String value) {
+ try {
+ final float floatValue = Float.parseFloat(value);
+ return floatValue >= mMin && floatValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+}
diff --git a/core/java/android/security/keystore/KeychainSnapshot.java b/core/java/android/security/keystore/KeychainSnapshot.java
index 2029114f9060..e03dd4a62ca0 100644
--- a/core/java/android/security/keystore/KeychainSnapshot.java
+++ b/core/java/android/security/keystore/KeychainSnapshot.java
@@ -43,7 +43,14 @@ import java.util.List;
* @hide
*/
public final class KeychainSnapshot implements Parcelable {
+ private static final int DEFAULT_MAX_ATTEMPTS = 10;
+ private static final long DEFAULT_COUNTER_ID = 1L;
+
private int mSnapshotVersion;
+ private int mMaxAttempts = DEFAULT_MAX_ATTEMPTS;
+ private long mCounterId = DEFAULT_COUNTER_ID;
+ private byte[] mServerParams;
+ private byte[] mPublicKey;
private List<KeychainProtectionParams> mKeychainProtectionParams;
private List<WrappedApplicationKey> mEntryRecoveryData;
private byte[] mEncryptedRecoveryKeyBlob;
@@ -79,6 +86,37 @@ public final class KeychainSnapshot implements Parcelable {
}
/**
+ * Number of user secret guesses allowed during Keychain recovery.
+ */
+ public int getMaxAttempts() {
+ return mMaxAttempts;
+ }
+
+ /**
+ * CounterId which is rotated together with user secret.
+ */
+ public long getCounterId() {
+ return mCounterId;
+ }
+
+ /**
+ * Server parameters.
+ */
+ public @NonNull byte[] getServerParams() {
+ return mServerParams;
+ }
+
+ /**
+ * Public key used to encrypt {@code encryptedRecoveryKeyBlob}.
+ *
+ * See implementation for binary key format
+ */
+ // TODO: document key format.
+ public @NonNull byte[] getTrustedHardwarePublicKey() {
+ return mPublicKey;
+ }
+
+ /**
* UI and key derivation parameters. Note that combination of secrets may be used.
*/
public @NonNull List<KeychainProtectionParams> getKeychainProtectionParams() {
@@ -129,6 +167,50 @@ public final class KeychainSnapshot implements Parcelable {
}
/**
+ * Sets the number of user secret guesses allowed during Keychain recovery.
+ *
+ * @param maxAttempts The maximum number of guesses.
+ * @return This builder.
+ */
+ public Builder setMaxAttempts(int maxAttempts) {
+ mInstance.mMaxAttempts = maxAttempts;
+ return this;
+ }
+
+ /**
+ * Sets counter id.
+ *
+ * @param counterId The counter id.
+ * @return This builder.
+ */
+ public Builder setCounterId(long counterId) {
+ mInstance.mCounterId = counterId;
+ return this;
+ }
+
+ /**
+ * Sets server parameters.
+ *
+ * @param serverParams The server parameters
+ * @return This builder.
+ */
+ public Builder setServerParams(byte[] serverParams) {
+ mInstance.mServerParams = serverParams;
+ return this;
+ }
+
+ /**
+ * Sets public key used to encrypt recovery blob.
+ *
+ * @param publicKey The public key
+ * @return This builder.
+ */
+ public Builder setTrustedHardwarePublicKey(byte[] publicKey) {
+ mInstance.mPublicKey = publicKey;
+ return this;
+ }
+
+ /**
* Sets UI and key derivation parameters
*
* @param recoveryMetadata The UI and key derivation parameters
@@ -175,6 +257,8 @@ public final class KeychainSnapshot implements Parcelable {
Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData,
"entryRecoveryData");
Preconditions.checkNotNull(mInstance.mEncryptedRecoveryKeyBlob);
+ Preconditions.checkNotNull(mInstance.mServerParams);
+ Preconditions.checkNotNull(mInstance.mPublicKey);
return mInstance;
}
}
diff --git a/core/java/android/security/keystore/RecoveryManager.java b/core/java/android/security/keystore/RecoveryController.java
index a381a2cea4b7..87283cbd75ab 100644
--- a/core/java/android/security/keystore/RecoveryManager.java
+++ b/core/java/android/security/keystore/RecoveryController.java
@@ -31,12 +31,25 @@ import java.util.List;
import java.util.Map;
/**
- * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
- * recovered later.
+ * An assistant for generating {@link javax.crypto.SecretKey} instances that can be recovered by
+ * other Android devices belonging to the user. The exported keychain is protected by the user's
+ * lock screen.
+ *
+ * <p>The RecoveryController must be paired with a recovery agent. The recovery agent is responsible
+ * for transporting the keychain to remote trusted hardware. This hardware must prevent brute force
+ * attempts against the user's lock screen by limiting the number of allowed guesses (to, e.g., 10).
+ * After that number of incorrect guesses, the trusted hardware no longer allows access to the
+ * key chain.
+ *
+ * <p>For now only the recovery agent itself is able to create keys, so it is expected that the
+ * recovery agent is itself the system app.
+ *
+ * <p>A recovery agent requires the privileged permission
+ * {@code android.Manifest.permission#RECOVER_KEYSTORE}.
*
* @hide
*/
-public class RecoveryManager {
+public class RecoveryController {
private static final String TAG = "RecoveryController";
/** Key has been successfully synced. */
@@ -96,28 +109,28 @@ public class RecoveryManager {
private final ILockSettings mBinder;
- private RecoveryManager(ILockSettings binder) {
+ private RecoveryController(ILockSettings binder) {
mBinder = binder;
}
/**
* Gets a new instance of the class.
*/
- public static RecoveryManager getInstance() {
+ public static RecoveryController getInstance() {
ILockSettings lockSettings =
ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
- return new RecoveryManager(lockSettings);
+ return new RecoveryController(lockSettings);
}
/**
- * Initializes key recovery service for the calling application. RecoveryManager
+ * Initializes key recovery service for the calling application. RecoveryController
* randomly chooses one of the keys from the list and keeps it to use for future key export
* operations. Collection of all keys in the list must be signed by the provided {@code
* rootCertificateAlias}, which must also be present in the list of root certificates
- * preinstalled on the device. The random selection allows RecoveryManager to select
+ * preinstalled on the device. The random selection allows RecoveryController to select
* which of a set of remote recovery service devices will be used.
*
- * <p>In addition, RecoveryManager enforces a delay of three months between
+ * <p>In addition, RecoveryController enforces a delay of three months between
* consecutive initialization attempts, to limit the ability of an attacker to often switch
* remote recovery devices and significantly increase number of recovery attempts.
*
@@ -373,7 +386,6 @@ public class RecoveryManager {
* The method generates symmetric key for a session, which trusted remote device can use to
* return recovery key.
*
- * @param sessionId ID for recovery session.
* @param verifierPublicKey Encoded {@code java.security.cert.X509Certificate} with Public key
* used to create the recovery blob on the source device.
* Keystore will verify the certificate using root of trust.
diff --git a/core/java/android/security/keystore/RecoveryControllerException.java b/core/java/android/security/keystore/RecoveryControllerException.java
index 31fd4af9a7d1..5b806b75ebab 100644
--- a/core/java/android/security/keystore/RecoveryControllerException.java
+++ b/core/java/android/security/keystore/RecoveryControllerException.java
@@ -19,7 +19,7 @@ package android.security.keystore;
import java.security.GeneralSecurityException;
/**
- * Base exception for errors thrown by {@link RecoveryManager}.
+ * Base exception for errors thrown by {@link RecoveryController}.
*
* @hide
*/
diff --git a/core/java/android/security/keystore/RecoverySession.java b/core/java/android/security/keystore/RecoverySession.java
index f78551fdf194..ae8d91af3230 100644
--- a/core/java/android/security/keystore/RecoverySession.java
+++ b/core/java/android/security/keystore/RecoverySession.java
@@ -29,18 +29,18 @@ public class RecoverySession implements AutoCloseable {
private static final int SESSION_ID_LENGTH_BYTES = 16;
private final String mSessionId;
- private final RecoveryManager mRecoveryManager;
+ private final RecoveryController mRecoveryController;
- private RecoverySession(RecoveryManager recoveryManager, String sessionId) {
- mRecoveryManager = recoveryManager;
+ private RecoverySession(RecoveryController recoveryController, String sessionId) {
+ mRecoveryController = recoveryController;
mSessionId = sessionId;
}
/**
* A new session, started by {@code recoveryManager}.
*/
- static RecoverySession newInstance(RecoveryManager recoveryManager) {
- return new RecoverySession(recoveryManager, newSessionId());
+ static RecoverySession newInstance(RecoveryController recoveryController) {
+ return new RecoverySession(recoveryController, newSessionId());
}
/**
@@ -66,6 +66,6 @@ public class RecoverySession implements AutoCloseable {
@Override
public void close() {
- mRecoveryManager.closeSession(this);
+ mRecoveryController.closeSession(this);
}
}
diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java
new file mode 100644
index 000000000000..18f6dab9fc59
--- /dev/null
+++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java
@@ -0,0 +1,284 @@
+/*
+ * 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.service.autofill;
+
+import static android.view.autofill.AutofillManager.EXTRA_AVAILABLE_ALGORITHMS;
+import static android.view.autofill.AutofillManager.EXTRA_DEFAULT_ALGORITHM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A service that calculates field classification scores.
+ *
+ * <p>A field classification score is a {@code float} representing how well an
+ * {@link AutofillValue} filled matches a expected value predicted by an autofill service
+ * &mdash;a full-match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
+ *
+ * <p>The exact score depends on the algorithm used to calculate it&mdash; the service must provide
+ * at least one default algorithm (which is used when the algorithm is not specified or is invalid),
+ * but it could provide more (in which case the algorithm name should be specifiied by the caller
+ * when calculating the scores).
+ *
+ * {@hide}
+ */
+@SystemApi
+public abstract class AutofillFieldClassificationService extends Service {
+
+ private static final String TAG = "AutofillFieldClassificationService";
+
+ private static final int MSG_GET_AVAILABLE_ALGORITHMS = 1;
+ private static final int MSG_GET_DEFAULT_ALGORITHM = 2;
+ private static final int MSG_GET_SCORES = 3;
+
+ /**
+ * The {@link Intent} action that must be declared as handled by a service
+ * in its manifest for the system to recognize it as a quota providing service.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.autofill.AutofillFieldClassificationService";
+
+ /** {@hide} **/
+ public static final String EXTRA_SCORES = "scores";
+
+ private AutofillFieldClassificationServiceWrapper mWrapper;
+
+ private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
+ final int action = msg.what;
+ final Bundle data = new Bundle();
+ final RemoteCallback callback;
+ switch (action) {
+ case MSG_GET_AVAILABLE_ALGORITHMS:
+ callback = (RemoteCallback) msg.obj;
+ final List<String> availableAlgorithms = onGetAvailableAlgorithms();
+ String[] asArray = null;
+ if (availableAlgorithms != null) {
+ asArray = new String[availableAlgorithms.size()];
+ availableAlgorithms.toArray(asArray);
+ }
+ data.putStringArray(EXTRA_AVAILABLE_ALGORITHMS, asArray);
+ break;
+ case MSG_GET_DEFAULT_ALGORITHM:
+ callback = (RemoteCallback) msg.obj;
+ final String defaultAlgorithm = onGetDefaultAlgorithm();
+ data.putString(EXTRA_DEFAULT_ALGORITHM, defaultAlgorithm);
+ break;
+ case MSG_GET_SCORES:
+ final SomeArgs args = (SomeArgs) msg.obj;
+ callback = (RemoteCallback) args.arg1;
+ final String algorithmName = (String) args.arg2;
+ final Bundle algorithmArgs = (Bundle) args.arg3;
+ @SuppressWarnings("unchecked")
+ final List<AutofillValue> actualValues = ((List<AutofillValue>) args.arg4);
+ @SuppressWarnings("unchecked")
+ final String[] userDataValues = (String[]) args.arg5;
+ final Scores scores = onGetScores(algorithmName, algorithmArgs, actualValues,
+ Arrays.asList(userDataValues));
+ data.putParcelable(EXTRA_SCORES, scores);
+ break;
+ default:
+ Log.w(TAG, "Handling unknown message: " + action);
+ return;
+ }
+ callback.sendResult(data);
+ };
+
+ private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
+ mHandlerCallback, true);
+
+ /** @hide */
+ public AutofillFieldClassificationService() {
+
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mWrapper = new AutofillFieldClassificationServiceWrapper();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mWrapper;
+ }
+
+ /**
+ * Gets the name of all available algorithms.
+ *
+ * @throws UnsupportedOperationException if not implemented by service.
+ */
+ // TODO(b/70939974): rename to onGetAvailableAlgorithms if not removed
+ @NonNull
+ public List<String> onGetAvailableAlgorithms() {
+ throw new UnsupportedOperationException("Must be implemented by external service");
+ }
+
+ /**
+ * Gets the default algorithm that's used when an algorithm is not specified or is invalid.
+ *
+ * @throws UnsupportedOperationException if not implemented by service.
+ */
+ @NonNull
+ public String onGetDefaultAlgorithm() {
+ throw new UnsupportedOperationException("Must be implemented by external service");
+ }
+
+ /**
+ * Calculates field classification scores in a batch.
+ *
+ * <p>See {@link AutofillFieldClassificationService} for more info about field classification
+ * scores.
+ *
+ * @param algorithm name of the algorithm to be used to calculate the scores. If invalid, the
+ * default algorithm will be used instead.
+ * @param args optional arguments to be passed to the algorithm.
+ * @param actualValues values entered by the user.
+ * @param userDataValues values predicted from the user data.
+ * @return the calculated scores and the algorithm used.
+ *
+ * {@hide}
+ */
+ @Nullable
+ @SystemApi
+ public Scores onGetScores(@Nullable String algorithm,
+ @Nullable Bundle args, @NonNull List<AutofillValue> actualValues,
+ @NonNull List<String> userDataValues) {
+ throw new UnsupportedOperationException("Must be implemented by external service");
+ }
+
+ private final class AutofillFieldClassificationServiceWrapper
+ extends IAutofillFieldClassificationService.Stub {
+
+ @Override
+ public void getAvailableAlgorithms(RemoteCallback callback) throws RemoteException {
+ mHandlerCaller.obtainMessageO(MSG_GET_AVAILABLE_ALGORITHMS, callback).sendToTarget();
+ }
+
+ @Override
+ public void getDefaultAlgorithm(RemoteCallback callback) throws RemoteException {
+ mHandlerCaller.obtainMessageO(MSG_GET_DEFAULT_ALGORITHM, callback).sendToTarget();
+ }
+
+ @Override
+ public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
+ List<AutofillValue> actualValues, String[] userDataValues)
+ throws RemoteException {
+ // TODO(b/70939974): refactor to use PooledLambda
+ mHandlerCaller.obtainMessageOOOOO(MSG_GET_SCORES, callback, algorithmName,
+ algorithmArgs, actualValues, userDataValues).sendToTarget();
+ }
+ }
+
+
+ // TODO(b/70939974): it might be simpler to remove this class and return the float[][] directly,
+ // ignoring the request if the algorithm name is invalid.
+ /**
+ * Represents field classification scores used in a batch calculation.
+ *
+ * {@hide}
+ */
+ @SystemApi
+ public static final class Scores implements Parcelable {
+ private final String mAlgorithmName;
+ private final float[][] mScores;
+
+ /* @hide */
+ public Scores(String algorithmName, int size1, int size2) {
+ mAlgorithmName = algorithmName;
+ mScores = new float[size1][size2];
+ }
+
+ public Scores(Parcel parcel) {
+ mAlgorithmName = parcel.readString();
+ final int size1 = parcel.readInt();
+ final int size2 = parcel.readInt();
+ mScores = new float[size1][size2];
+ for (int i = 0; i < size1; i++) {
+ for (int j = 0; j < size2; j++) {
+ mScores[i][j] = parcel.readFloat();
+ }
+ }
+ }
+
+ /**
+ * Gets the name of algorithm used to calculate the score.
+ */
+ @NonNull
+ public String getAlgorithm() {
+ return mAlgorithmName;
+ }
+
+ /**
+ * Gets the resulting scores, with the 1st dimension representing actual values and the 2nd
+ * dimension values from {@link UserData}.
+ */
+ @NonNull
+ public float[][] getScores() {
+ return mScores;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mAlgorithmName);
+ int size1 = mScores.length;
+ int size2 = mScores[0].length;
+ parcel.writeInt(size1);
+ parcel.writeInt(size2);
+ for (int i = 0; i < size1; i++) {
+ for (int j = 0; j < size2; j++) {
+ parcel.writeFloat(mScores[i][j]);
+ }
+ }
+ }
+
+ public static final Creator<Scores> CREATOR = new Creator<Scores>() {
+
+ @Override
+ public Scores createFromParcel(Parcel parcel) {
+ return new Scores(parcel);
+ }
+
+ @Override
+ public Scores[] newArray(int size) {
+ return new Scores[size];
+ }
+
+ };
+ }
+}
diff --git a/core/java/android/service/autofill/CharSequenceTransformation.java b/core/java/android/service/autofill/CharSequenceTransformation.java
index 2413e97ba837..f52ac8505289 100644
--- a/core/java/android/service/autofill/CharSequenceTransformation.java
+++ b/core/java/android/service/autofill/CharSequenceTransformation.java
@@ -22,7 +22,6 @@ import android.annotation.NonNull;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.view.autofill.AutofillId;
@@ -31,6 +30,8 @@ import android.widget.TextView;
import com.android.internal.util.Preconditions;
+import java.util.LinkedHashMap;
+import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -62,7 +63,9 @@ import java.util.regex.Pattern;
public final class CharSequenceTransformation extends InternalTransformation implements
Transformation, Parcelable {
private static final String TAG = "CharSequenceTransformation";
- @NonNull private final ArrayMap<AutofillId, Pair<Pattern, String>> mFields;
+
+ // Must use LinkedHashMap to preserve insertion order.
+ @NonNull private final LinkedHashMap<AutofillId, Pair<Pattern, String>> mFields;
private CharSequenceTransformation(Builder builder) {
mFields = builder.mFields;
@@ -76,9 +79,9 @@ public final class CharSequenceTransformation extends InternalTransformation imp
final StringBuilder converted = new StringBuilder();
final int size = mFields.size();
if (sDebug) Log.d(TAG, size + " multiple fields on id " + childViewId);
- for (int i = 0; i < size; i++) {
- final AutofillId id = mFields.keyAt(i);
- final Pair<Pattern, String> field = mFields.valueAt(i);
+ for (Entry<AutofillId, Pair<Pattern, String>> entry : mFields.entrySet()) {
+ final AutofillId id = entry.getKey();
+ final Pair<Pattern, String> field = entry.getValue();
final String value = finder.findByAutofillId(id);
if (value == null) {
Log.w(TAG, "No value for id " + id);
@@ -107,8 +110,10 @@ public final class CharSequenceTransformation extends InternalTransformation imp
* Builder for {@link CharSequenceTransformation} objects.
*/
public static class Builder {
- @NonNull private final ArrayMap<AutofillId, Pair<Pattern, String>> mFields =
- new ArrayMap<>();
+
+ // Must use LinkedHashMap to preserve insertion order.
+ @NonNull private final LinkedHashMap<AutofillId, Pair<Pattern, String>> mFields =
+ new LinkedHashMap<>();
private boolean mDestroyed;
/**
@@ -186,12 +191,15 @@ public final class CharSequenceTransformation extends InternalTransformation imp
final Pattern[] regexs = new Pattern[size];
final String[] substs = new String[size];
Pair<Pattern, String> pair;
- for (int i = 0; i < size; i++) {
- ids[i] = mFields.keyAt(i);
- pair = mFields.valueAt(i);
+ int i = 0;
+ for (Entry<AutofillId, Pair<Pattern, String>> entry : mFields.entrySet()) {
+ ids[i] = entry.getKey();
+ pair = entry.getValue();
regexs[i] = pair.first;
substs[i] = pair.second;
+ i++;
}
+
parcel.writeParcelableArray(ids, flags);
parcel.writeSerializable(regexs);
parcel.writeStringArray(substs);
diff --git a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl
new file mode 100644
index 000000000000..d8e829d8f67c
--- /dev/null
+++ b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.view.autofill.AutofillValue;
+import java.util.List;
+
+/**
+ * Service used to calculate match scores for Autofill Field Classification.
+ *
+ * @hide
+ */
+oneway interface IAutofillFieldClassificationService {
+ void getAvailableAlgorithms(in RemoteCallback callback);
+ void getDefaultAlgorithm(in RemoteCallback callback);
+ void getScores(in RemoteCallback callback, String algorithmName, in Bundle algorithmArgs,
+ in List<AutofillValue> actualValues, in String[] userDataValues);
+}
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index c93e0365d58d..c7d4a4ac69aa 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -356,6 +356,7 @@ public class MeasuredParagraph {
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
@NonNull TextDirectionHeuristic textDir,
+ boolean computeHyphenation,
@Nullable MeasuredParagraph recycle) {
final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
mt.resetAndAnalyzeBidi(text, start, end, textDir);
@@ -365,7 +366,8 @@ public class MeasuredParagraph {
long nativeBuilderPtr = nInitBuilder();
try {
mt.bindNativeObject(
- nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+ nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
+ computeHyphenation));
} finally {
nFreeBuilder(nativeBuilderPtr);
}
@@ -394,7 +396,8 @@ public class MeasuredParagraph {
mt.mSpanEndCache.append(spanEnd);
}
}
- mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+ mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
+ computeHyphenation));
} finally {
nFreeBuilder(nativeBuilderPtr);
}
@@ -668,7 +671,8 @@ public class MeasuredParagraph {
@FloatRange(from = 0) float width);
private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr,
- @NonNull char[] text);
+ @NonNull char[] text,
+ boolean computeHyphenation);
private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 2c30360b81bc..b96b48954ee5 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -52,70 +52,147 @@ public class MeasuredText implements Spanned {
// The sorted paragraph end offsets.
private final @NonNull int[] mParagraphBreakPoints;
- /**
- * Build MeasuredText from the text.
- *
- * @param text The text to be measured.
- * @param paint The paint to be used for drawing.
- * @param textDir The text direction.
- * @return The measured text.
- */
- public static @NonNull MeasuredText build(@NonNull CharSequence text,
- @NonNull TextPaint paint,
- @NonNull TextDirectionHeuristic textDir) {
- return MeasuredText.build(text, paint, textDir, 0, text.length());
- }
+ // The break strategy for this measured text.
+ private final @Layout.BreakStrategy int mBreakStrategy;
+
+ // The hyphenation frequency for this measured text.
+ private final @Layout.HyphenationFrequency int mHyphenationFrequency;
/**
- * Build MeasuredText from the specific range of the text..
- *
- * @param text The text to be measured.
- * @param paint The paint to be used for drawing.
- * @param textDir The text direction.
- * @param start The inclusive start offset of the text.
- * @param end The exclusive start offset of the text.
- * @return The measured text.
+ * A Builder for MeasuredText
*/
- public static @NonNull MeasuredText build(@NonNull CharSequence text,
- @NonNull TextPaint paint,
- @NonNull TextDirectionHeuristic textDir,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end) {
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(paint);
- Preconditions.checkNotNull(textDir);
- Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
- Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
-
- final IntArray paragraphEnds = new IntArray();
- final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();
-
- int paraEnd = 0;
- for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
- paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
- if (paraEnd < 0) {
- // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
- paraEnd = end;
- } else {
- paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
- }
+ public static final class Builder {
+ // Mandatory parameters.
+ private final @NonNull CharSequence mText;
+ private final @NonNull TextPaint mPaint;
+
+ // Members to be updated by setters.
+ private @IntRange(from = 0) int mStart;
+ private @IntRange(from = 0) int mEnd;
+ private TextDirectionHeuristic mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
+ private @Layout.BreakStrategy int mBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
+ private @Layout.HyphenationFrequency int mHyphenationFrequency =
+ Layout.HYPHENATION_FREQUENCY_NORMAL;
+
+
+ /**
+ * Builder constructor
+ *
+ * @param text The text to be measured.
+ * @param paint The paint to be used for drawing.
+ */
+ public Builder(@NonNull CharSequence text, @NonNull TextPaint paint) {
+ Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(paint);
+
+ mText = text;
+ mPaint = paint;
+ mStart = 0;
+ mEnd = text.length();
+ }
- paragraphEnds.add(paraEnd);
- measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
- paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
+ /**
+ * Set the range of measuring target.
+ *
+ * @param start The measuring target start offset in the text.
+ * @param end The measuring target end offset in the text.
+ */
+ public @NonNull Builder setRange(@IntRange(from = 0) int start,
+ @IntRange(from = 0) int end) {
+ Preconditions.checkArgumentInRange(start, 0, mText.length(), "start");
+ Preconditions.checkArgumentInRange(end, 0, mText.length(), "end");
+ Preconditions.checkArgument(start <= end, "The range is reversed.");
+
+ mStart = start;
+ mEnd = end;
+ return this;
}
- return new MeasuredText(text, start, end, paint, textDir,
- measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]),
- paragraphEnds.toArray());
- }
+ /**
+ * Set the text direction heuristic
+ *
+ * The default value is {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
+ *
+ * @param textDir The text direction heuristic for resolving bidi behavior.
+ * @return this builder, useful for chaining.
+ */
+ public @NonNull Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
+ Preconditions.checkNotNull(textDir);
+ mTextDir = textDir;
+ return this;
+ }
+
+ /**
+ * Set the break strategy
+ *
+ * The default value is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}.
+ *
+ * @param breakStrategy The break strategy.
+ * @return this builder, useful for chaining.
+ */
+ public @NonNull Builder setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
+ mBreakStrategy = breakStrategy;
+ return this;
+ }
+
+ /**
+ * Set the hyphenation frequency
+ *
+ * The default value is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
+ *
+ * @param hyphenationFrequency The hyphenation frequency.
+ * @return this builder, useful for chaining.
+ */
+ public @NonNull Builder setHyphenationFrequency(
+ @Layout.HyphenationFrequency int hyphenationFrequency) {
+ mHyphenationFrequency = hyphenationFrequency;
+ return this;
+ }
- // Use MeasuredText.build instead.
+ /**
+ * Build the measured text
+ *
+ * @return the measured text.
+ */
+ public @NonNull MeasuredText build() {
+ final boolean needHyphenation = mBreakStrategy != Layout.BREAK_STRATEGY_SIMPLE
+ && mHyphenationFrequency != Layout.HYPHENATION_FREQUENCY_NONE;
+
+ final IntArray paragraphEnds = new IntArray();
+ final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();
+
+ int paraEnd = 0;
+ for (int paraStart = mStart; paraStart < mEnd; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(mText, LINE_FEED, paraStart, mEnd);
+ if (paraEnd < 0) {
+ // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
+ // end.
+ paraEnd = mEnd;
+ } else {
+ paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
+ }
+
+ paragraphEnds.add(paraEnd);
+ measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
+ mPaint, mText, paraStart, paraEnd, mTextDir, needHyphenation,
+ null /* no recycle */));
+ }
+
+ return new MeasuredText(mText, mStart, mEnd, mPaint, mTextDir, mBreakStrategy,
+ mHyphenationFrequency, measuredTexts.toArray(
+ new MeasuredParagraph[measuredTexts.size()]),
+ paragraphEnds.toArray());
+ }
+ };
+
+ // Use MeasuredText.Builder instead.
private MeasuredText(@NonNull CharSequence text,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
@NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir,
+ @Layout.BreakStrategy int breakStrategy,
+ @Layout.HyphenationFrequency int frequency,
@NonNull MeasuredParagraph[] measuredTexts,
@NonNull int[] paragraphBreakPoints) {
mText = text;
@@ -125,6 +202,8 @@ public class MeasuredText implements Spanned {
mMeasuredParagraphs = measuredTexts;
mParagraphBreakPoints = paragraphBreakPoints;
mTextDir = textDir;
+ mBreakStrategy = breakStrategy;
+ mHyphenationFrequency = frequency;
}
/**
@@ -190,6 +269,20 @@ public class MeasuredText implements Spanned {
return mMeasuredParagraphs[paraIndex];
}
+ /**
+ * Returns the break strategy for this text.
+ */
+ public @Layout.BreakStrategy int getBreakStrategy() {
+ return mBreakStrategy;
+ }
+
+ /**
+ * Returns the hyphenation frequency for this text.
+ */
+ public @Layout.HyphenationFrequency int getHyphenationFrequency() {
+ return mHyphenationFrequency;
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////////
// Spanned overrides
//
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 36bec863e7ac..70d648657d9d 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -653,26 +653,40 @@ public class StaticLayout extends Layout {
MeasuredText measured = null;
final Spanned spanned;
+ final boolean canUseMeasuredText;
if (source instanceof MeasuredText) {
measured = (MeasuredText) source;
- final CharSequence original = measured.getText();
- spanned = (original instanceof Spanned) ? (Spanned) original : null;
-
if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) {
// The buffer position has changed. Re-measure here.
- measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd);
+ canUseMeasuredText = false;
+ } else if (b.mBreakStrategy != measured.getBreakStrategy()
+ || b.mHyphenationFrequency != measured.getHyphenationFrequency()) {
+ // The computed hyphenation pieces may not be able to used. Re-measure it.
+ canUseMeasuredText = false;
} else {
// We can use measured information.
-
- // Overwrite with the one when emeasured.
- // TODO: Give an option for developer not to overwrite and measure again here?
- textDir = measured.getTextDir();
- paint = measured.getPaint();
+ canUseMeasuredText = true;
}
} else {
- measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd);
+ canUseMeasuredText = false;
+ }
+
+ if (!canUseMeasuredText) {
+ measured = new MeasuredText.Builder(source, paint)
+ .setRange(bufStart, bufEnd)
+ .setTextDirection(textDir)
+ .setBreakStrategy(b.mBreakStrategy)
+ .setHyphenationFrequency(b.mHyphenationFrequency)
+ .build();
spanned = (source instanceof Spanned) ? (Spanned) source : null;
+ } else {
+ final CharSequence original = measured.getText();
+ spanned = (original instanceof Spanned) ? (Spanned) original : null;
+ // Overwrite with the one when measured.
+ // TODO: Give an option for developer not to overwrite and measure again here?
+ textDir = measured.getTextDir();
+ paint = measured.getPaint();
}
try {
diff --git a/core/java/android/util/DataUnit.java b/core/java/android/util/DataUnit.java
index 3cc1bd899eeb..ea4266ecb790 100644
--- a/core/java/android/util/DataUnit.java
+++ b/core/java/android/util/DataUnit.java
@@ -20,12 +20,15 @@ import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
/**
- * Constants for common byte-related units. Note that both SI and IEC units are
- * supported, and you'll need to pick the correct one for your use-case.
+ * A {@code DataUnit} represents data sizes at a given unit of granularity and
+ * provides utility methods to convert across units.
+ * <p>
+ * Note that both SI units (powers of 10) and IEC units (powers of 2) are
+ * supported, and you'll need to pick the correct one for your use-case. For
+ * example, Wikipedia defines a "kilobyte" as an SI unit of 1000 bytes, and a
+ * "kibibyte" as an IEC unit of 1024 bytes.
* <p>
* This design is mirrored after {@link TimeUnit} and {@link ChronoUnit}.
- *
- * @hide
*/
public enum DataUnit {
KILOBYTES { @Override public long toBytes(long v) { return v * 1_000; } },
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 33fbf73b4371..1caea5773160 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -373,6 +373,7 @@ public final class Choreographer {
* @see #removeCallbacks
* @hide
*/
+ @TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
@@ -391,6 +392,7 @@ public final class Choreographer {
* @see #removeCallback
* @hide
*/
+ @TestApi
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
@@ -440,6 +442,7 @@ public final class Choreographer {
* @see #postCallbackDelayed
* @hide
*/
+ @TestApi
public void removeCallbacks(int callbackType, Runnable action, Object token) {
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index ed167c812be1..17b6ddca6394 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -29,6 +29,7 @@ import android.view.IWindowId;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.Surface;
+import android.view.SurfaceControl;
/**
* System private per-application interface to the window manager.
@@ -150,25 +151,32 @@ interface IWindowSession {
boolean performHapticFeedback(IWindow window, int effectId, boolean always);
/**
- * Allocate the drag's thumbnail surface. Also assigns a token that identifies
- * the drag to the OS and passes that as the return value. A return value of
- * null indicates failure.
- */
- IBinder prepareDrag(IWindow window, int flags,
- int thumbnailWidth, int thumbnailHeight, out Surface outSurface);
-
- /**
* Initiate the drag operation itself
- */
- boolean performDrag(IWindow window, IBinder dragToken, int touchSource,
+ *
+ * @param window Window which initiates drag operation.
+ * @param flags See {@code View#startDragAndDrop}
+ * @param surface Surface containing drag shadow image
+ * @param touchSource See {@code InputDevice#getSource()}
+ * @param touchX TODO (b/72072998): Fix the issue that the system server misuse the arguments as
+ * initial touch point while the framework passes drag shadow size.
+ * @param touchY TODO (b/72072998): Fix the issue that the system server misuse the arguments as
+ * initial touch point while the framework passes drag shadow size.
+ * @param thumbCenterX X coordinate for the position within the shadow image that should be
+ * underneath the touch point during the drag and drop operation.
+ * @param thumbCenterY Y coordinate for the position within the shadow image that should be
+ * underneath the touch point during the drag and drop operation.
+ * @param data Data transferred by drag and drop
+ * @return Token of drag operation which will be passed to cancelDragAndDrop.
+ */
+ IBinder performDrag(IWindow window, int flags, in SurfaceControl surface, int touchSource,
float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data);
- /**
+ /**
* Report the result of a drop action targeted to the given window.
* consumed is 'true' when the drop was accepted by a valid recipient,
* 'false' otherwise.
*/
- void reportDropResult(IWindow window, boolean consumed);
+ void reportDropResult(IWindow window, boolean consumed);
/**
* Cancel the current drag operation.
@@ -236,4 +244,12 @@ interface IWindowSession {
boolean startMovingTask(IWindow window, float startX, float startY);
void updatePointerIcon(IWindow window);
+
+ /**
+ * Update a tap exclude region with a rectangular area identified by provided id in the window.
+ * Touches on this region will not switch focus to this window. Passing an empty rect will
+ * remove the area from the exclude region of this window.
+ */
+ void updateTapExcludeRegion(IWindow window, int regionId, int left, int top, int width,
+ int height);
}
diff --git a/core/java/android/view/SurfaceControl.aidl b/core/java/android/view/SurfaceControl.aidl
new file mode 100644
index 000000000000..744ead2be643
--- /dev/null
+++ b/core/java/android/view/SurfaceControl.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+parcelable SurfaceControl;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f461e45372d3..05770c357526 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17886,6 +17886,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Return the window this view is currently attached to. Used in
+ * {@link android.app.ActivityView} to communicate with WM.
+ * @hide
+ */
+ protected IWindow getWindow() {
+ return mAttachInfo != null ? mAttachInfo.mWindow : null;
+ }
+
+ /**
* Return the visibility value of the least visible component passed.
*/
int combineVisibility(int vis1, int vis2) {
@@ -23553,15 +23562,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
}
- boolean okay = false;
-
Point shadowSize = new Point();
Point shadowTouchPoint = new Point();
shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
- if ((shadowSize.x < 0) || (shadowSize.y < 0) ||
- (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) {
- throw new IllegalStateException("Drag shadow dimensions must not be negative");
+ if ((shadowSize.x <= 0) || (shadowSize.y <= 0)
+ || (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) {
+ throw new IllegalStateException("Drag shadow dimensions must be positive");
}
if (ViewDebug.DEBUG_DRAG) {
@@ -23572,40 +23579,50 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mAttachInfo.mDragSurface.release();
}
mAttachInfo.mDragSurface = new Surface();
+ mAttachInfo.mDragToken = null;
+
+ final ViewRootImpl root = mAttachInfo.mViewRootImpl;
+ final SurfaceSession session = new SurfaceSession(root.mSurface);
+ final SurfaceControl surface = new SurfaceControl.Builder(session)
+ .setName("drag surface")
+ .setSize(shadowSize.x, shadowSize.y)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
try {
- mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
- flags, shadowSize.x, shadowSize.y, mAttachInfo.mDragSurface);
- if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
- + mAttachInfo.mDragToken + " surface=" + mAttachInfo.mDragSurface);
- if (mAttachInfo.mDragToken != null) {
- Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
- try {
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
- shadowBuilder.onDrawShadow(canvas);
- } finally {
- mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
- }
-
- final ViewRootImpl root = getViewRootImpl();
+ mAttachInfo.mDragSurface.copyFrom(surface);
+ final Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
+ try {
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ shadowBuilder.onDrawShadow(canvas);
+ } finally {
+ mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
+ }
- // Cache the local state object for delivery with DragEvents
- root.setLocalDragState(myLocalState);
+ // Cache the local state object for delivery with DragEvents
+ root.setLocalDragState(myLocalState);
- // repurpose 'shadowSize' for the last touch point
- root.getLastTouchPoint(shadowSize);
+ // repurpose 'shadowSize' for the last touch point
+ root.getLastTouchPoint(shadowSize);
- okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken,
- root.getLastTouchSource(), shadowSize.x, shadowSize.y,
- shadowTouchPoint.x, shadowTouchPoint.y, data);
- if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
+ mAttachInfo.mDragToken = mAttachInfo.mSession.performDrag(
+ mAttachInfo.mWindow, flags, surface, root.getLastTouchSource(),
+ shadowSize.x, shadowSize.y, shadowTouchPoint.x, shadowTouchPoint.y, data);
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "performDrag returned " + mAttachInfo.mDragToken);
}
+
+ return mAttachInfo.mDragToken != null;
} catch (Exception e) {
Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
- mAttachInfo.mDragSurface.destroy();
- mAttachInfo.mDragSurface = null;
+ return false;
+ } finally {
+ if (mAttachInfo.mDragToken == null) {
+ mAttachInfo.mDragSurface.destroy();
+ mAttachInfo.mDragSurface = null;
+ root.setLocalDragState(null);
+ }
+ session.kill();
}
-
- return okay;
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 311dd4b8b294..3ee282ec3ac2 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -639,6 +639,8 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000;
+ private static final int BOOLEAN_PROPERTY_IS_HEADING = 0x0200000;
+
/**
* Bits that provide the id of a virtual descendant of a view.
*/
@@ -2409,6 +2411,30 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Returns whether node represents a heading.
+ *
+ * @return {@code true} if the node is a heading, {@code false} otherwise.
+ */
+ public boolean isHeading() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING);
+ }
+
+ /**
+ * Sets whether the node represents a heading.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param isHeading {@code true} if the node is a heading, {@code false} otherwise.
+ */
+ public void setHeading(boolean isHeading) {
+ setBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING, isHeading);
+ }
+
+ /**
* Gets the package this node comes from.
*
* @return The package name.
@@ -4597,7 +4623,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param rowSpan The number of rows the item spans.
* @param columnIndex The column index at which the item is located.
* @param columnSpan The number of columns the item spans.
- * @param heading Whether the item is a heading.
+ * @param heading Whether the item is a heading. (Prefer
+ * {@link AccessibilityNodeInfo#setHeading(boolean)}).
*/
public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
int columnIndex, int columnSpan, boolean heading) {
@@ -4611,7 +4638,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param rowSpan The number of rows the item spans.
* @param columnIndex The column index at which the item is located.
* @param columnSpan The number of columns the item spans.
- * @param heading Whether the item is a heading.
+ * @param heading Whether the item is a heading. (Prefer
+ * {@link AccessibilityNodeInfo#setHeading(boolean)})
* @param selected Whether the item is selected.
*/
public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
@@ -4698,6 +4726,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* heading, table header, etc.
*
* @return If the item is a heading.
+ * @deprecated Use {@link AccessibilityNodeInfo#isHeading()}
*/
public boolean isHeading() {
return mHeading;
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index de9b0d7dfa4a..f54561bfb423 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -33,6 +33,7 @@ import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.service.autofill.AutofillService;
import android.service.autofill.FillEventHistory;
@@ -53,9 +54,12 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
// TODO: use java.lang.ref.Cleaner once Android supports Java 9
import sun.misc.Cleaner;
@@ -169,11 +173,15 @@ public final class AutofillManager {
public static final String EXTRA_CLIENT_STATE =
"android.view.autofill.extra.CLIENT_STATE";
-
/** @hide */
public static final String EXTRA_RESTORE_SESSION_TOKEN =
"android.view.autofill.extra.RESTORE_SESSION_TOKEN";
+ /** @hide */
+ public static final String EXTRA_AVAILABLE_ALGORITHMS = "available_algorithms";
+ /** @hide */
+ public static final String EXTRA_DEFAULT_ALGORITHM = "default_algorithm";
+
private static final String SESSION_ID_TAG = "android:sessionId";
private static final String STATE_TAG = "android:state";
private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
@@ -259,6 +267,12 @@ public final class AutofillManager {
public static final int STATE_DISABLED_BY_SERVICE = 4;
/**
+ * Timeout in ms for calls to the field classification service.
+ * @hide
+ */
+ public static final int FC_SERVICE_TIMEOUT = 5000;
+
+ /**
* Makes an authentication id from a request id and a dataset id.
*
* @param requestId The request id.
@@ -1160,10 +1174,22 @@ public final class AutofillManager {
* and it's ignored if the caller currently doesn't have an enabled autofill service for
* the user.
*/
+ // TODO(b/70939974): refactor this method to be "purely" sync by getting the info from the
+ // the ExtService manifest (instead of calling the service)
@Nullable
public String getDefaultFieldClassificationAlgorithm() {
+ final SyncRemoteCallbackListener<String> listener =
+ new SyncRemoteCallbackListener<String>() {
+
+ @Override
+ String getResult(Bundle result) {
+ return result == null ? null : result.getString(EXTRA_DEFAULT_ALGORITHM);
+ }
+ };
+
try {
- return mService.getDefaultFieldClassificationAlgorithm();
+ mService.getDefaultFieldClassificationAlgorithm(new RemoteCallback(listener));
+ return listener.getResult(FC_SERVICE_TIMEOUT);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1175,17 +1201,32 @@ public final class AutofillManager {
* <a href="AutofillService.html#FieldClassification">field classification</a>.
*
* <p><b>Note:</b> This method should only be called by an app providing an autofill service,
- * and it's ignored if the caller currently doesn't have an enabled autofill service for
- * the user.
- *
- * @return list of all algorithms currently available, or an empty list if the caller currently
- * does not have an enabled autofill service for the user.
+ * and it returns an empty list if the caller currently doesn't have an enabled autofill service
+ * for the user.
*/
+ // TODO(b/70939974): refactor this method to be "purely" sync by getting the info from the
+ // the ExtService manifest (instead of calling the service)
@NonNull
public List<String> getAvailableFieldClassificationAlgorithms() {
+ final SyncRemoteCallbackListener<List<String>> listener =
+ new SyncRemoteCallbackListener<List<String>>() {
+
+ @Override
+ List<String> getResult(Bundle result) {
+ List<String> algorithms = null;
+ if (result != null) {
+ final String[] asArray = result.getStringArray(EXTRA_AVAILABLE_ALGORITHMS);
+ if (asArray != null) {
+ algorithms = Arrays.asList(asArray);
+ }
+ }
+ return algorithms != null ? algorithms : Collections.emptyList();
+ }
+ };
+
try {
- final List<String> names = mService.getAvailableFieldClassificationAlgorithms();
- return names != null ? names : Collections.emptyList();
+ mService.getAvailableFieldClassificationAlgorithms(new RemoteCallback(listener));
+ return listener.getResult(FC_SERVICE_TIMEOUT);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -2281,4 +2322,36 @@ public final class AutofillManager {
}
}
}
+
+ private abstract static class SyncRemoteCallbackListener<T>
+ implements RemoteCallback.OnResultListener {
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private T mResult;
+
+ @Override
+ public void onResult(Bundle result) {
+ if (sVerbose) Log.w(TAG, "SyncRemoteCallbackListener.onResult(): " + result);
+ mResult = getResult(result);
+ mLatch.countDown();
+ }
+
+ T getResult(int timeoutMs) {
+ T result = null;
+ try {
+ if (mLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+ result = mResult;
+ } else {
+ Log.w(TAG, "SyncRemoteCallbackListener not called in " + timeoutMs + "ms");
+ }
+ } catch (InterruptedException e) {
+ Log.w(TAG, "SyncRemoteCallbackListener interrupted: " + e);
+ Thread.currentThread().interrupt();
+ }
+ if (sVerbose) Log.w(TAG, "SyncRemoteCallbackListener: returning " + result);
+ return result;
+ }
+
+ abstract T getResult(Bundle result);
+ }
}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 1afa35e80a26..41672e7aeb9b 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteCallback;
import android.service.autofill.FillEventHistory;
import android.service.autofill.UserData;
import android.view.autofill.AutofillId;
@@ -58,6 +59,6 @@ interface IAutoFillManager {
void setUserData(in UserData userData);
boolean isFieldClassificationEnabled();
ComponentName getAutofillServiceComponentName();
- List<String> getAvailableFieldClassificationAlgorithms();
- String getDefaultFieldClassificationAlgorithm();
+ void getAvailableFieldClassificationAlgorithms(in RemoteCallback callback);
+ void getDefaultFieldClassificationAlgorithm(in RemoteCallback callback);
}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 336c20cdcdc0..728824c2f0dc 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -106,6 +106,10 @@ public class EditText extends TextView {
@Override
public Editable getText() {
CharSequence text = super.getText();
+ // This can only happen during construction.
+ if (text == null) {
+ return null;
+ }
if (text instanceof Editable) {
return (Editable) super.getText();
}
diff --git a/core/java/android/widget/MediaController2.java b/core/java/android/widget/MediaControlView2.java
index 9035137d5362..6e85ece291b2 100644
--- a/core/java/android/widget/MediaController2.java
+++ b/core/java/android/widget/MediaControlView2.java
@@ -19,130 +19,145 @@ package android.widget;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Canvas;
import android.media.session.MediaController;
import android.media.update.ApiLoader;
-import android.media.update.MediaController2Provider;
+import android.media.update.MediaControlView2Provider;
import android.media.update.ViewProvider;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
/**
* TODO PUBLIC API
* @hide
*/
-public class MediaController2 extends FrameLayout {
- private final MediaController2Provider mProvider;
+public class MediaControlView2 extends FrameLayout {
+ private final MediaControlView2Provider mProvider;
- public MediaController2(@NonNull Context context) {
+ public MediaControlView2(@NonNull Context context) {
this(context, null);
}
- public MediaController2(@NonNull Context context, @Nullable AttributeSet attrs) {
+ public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public MediaController2(@NonNull Context context, @Nullable AttributeSet attrs,
+ public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
- public MediaController2(@NonNull Context context, @Nullable AttributeSet attrs,
+ public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mProvider = ApiLoader.getProvider(context)
- .createMediaController2(this, new SuperProvider());
+ .createMediaControlView2(this, new SuperProvider());
}
- public void setController(MediaController controller) {
- mProvider.setController_impl(controller);
+ public MediaControlView2Provider getProvider() {
+ return mProvider;
}
- public void setAnchorView(View view) {
- mProvider.setAnchorView_impl(view);
+ /**
+ * TODO: add docs
+ */
+ public void setController(MediaController controller) {
+ mProvider.setController_impl(controller);
}
+ /**
+ * TODO: add docs
+ */
public void show() {
mProvider.show_impl();
}
+ /**
+ * TODO: add docs
+ */
public void show(int timeout) {
mProvider.show_impl(timeout);
}
+ /**
+ * TODO: add docs
+ */
public boolean isShowing() {
return mProvider.isShowing_impl();
}
+ /**
+ * TODO: add docs
+ */
public void hide() {
mProvider.hide_impl();
}
- public void setPrevNextListeners(OnClickListener next, OnClickListener prev) {
- mProvider.setPrevNextListeners_impl(next, prev);
- }
-
+ /**
+ * TODO: add docs
+ */
public void showCCButton() {
mProvider.showCCButton_impl();
}
+ /**
+ * TODO: add docs
+ */
public boolean isPlaying() {
return mProvider.isPlaying_impl();
}
+ /**
+ * TODO: add docs
+ */
public int getCurrentPosition() {
return mProvider.getCurrentPosition_impl();
}
+ /**
+ * TODO: add docs
+ */
public int getBufferPercentage() {
return mProvider.getBufferPercentage_impl();
}
+ /**
+ * TODO: add docs
+ */
public boolean canPause() {
return mProvider.canPause_impl();
}
+ /**
+ * TODO: add docs
+ */
public boolean canSeekBackward() {
return mProvider.canSeekBackward_impl();
}
+ /**
+ * TODO: add docs
+ */
public boolean canSeekForward() {
return mProvider.canSeekForward_impl();
}
+ /**
+ * TODO: add docs
+ */
public void showSubtitle() {
mProvider.showSubtitle_impl();
}
+ /**
+ * TODO: add docs
+ */
public void hideSubtitle() {
mProvider.hideSubtitle_impl();
}
@Override
- protected void onAttachedToWindow() {
- mProvider.onAttachedToWindow_impl();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- mProvider.onDetachedFromWindow_impl();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- mProvider.onLayout_impl(changed, left, top, right, bottom);
- }
-
- @Override
- public void draw(Canvas canvas) {
- mProvider.draw_impl(canvas);
- }
-
- @Override
public CharSequence getAccessibilityClassName() {
return mProvider.getAccessibilityClassName_impl();
}
@@ -179,58 +194,38 @@ public class MediaController2 extends FrameLayout {
private class SuperProvider implements ViewProvider {
@Override
- public void onAttachedToWindow_impl() {
- MediaController2.super.onAttachedToWindow();
- }
-
- @Override
- public void onDetachedFromWindow_impl() {
- MediaController2.super.onDetachedFromWindow();
- }
-
- @Override
- public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
- MediaController2.super.onLayout(changed, left, top, right, bottom);
- }
-
- @Override
- public void draw_impl(Canvas canvas) {
- MediaController2.super.draw(canvas);
- }
-
- @Override
public CharSequence getAccessibilityClassName_impl() {
- return MediaController2.super.getAccessibilityClassName();
+ return MediaControlView2.super.getAccessibilityClassName();
}
@Override
public boolean onTouchEvent_impl(MotionEvent ev) {
- return MediaController2.super.onTouchEvent(ev);
+ return MediaControlView2.super.onTouchEvent(ev);
}
@Override
public boolean onTrackballEvent_impl(MotionEvent ev) {
- return MediaController2.super.onTrackballEvent(ev);
+ return MediaControlView2.super.onTrackballEvent(ev);
}
@Override
public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
- return MediaController2.super.onKeyDown(keyCode, event);
+ return MediaControlView2.super.onKeyDown(keyCode, event);
}
@Override
public void onFinishInflate_impl() {
- MediaController2.super.onFinishInflate();
+ MediaControlView2.super.onFinishInflate();
}
@Override
public boolean dispatchKeyEvent_impl(KeyEvent event) {
- return MediaController2.super.dispatchKeyEvent(event);
+ return MediaControlView2.super.dispatchKeyEvent(event);
}
@Override
public void setEnabled_impl(boolean enabled) {
- MediaController2.super.setEnabled(enabled);
+ MediaControlView2.super.setEnabled(enabled);
}
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dac6c0269539..7d3fcf469551 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -309,6 +309,7 @@ import java.util.Locale;
* @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
* @attr ref android.R.styleable#TextView_autoSizeStepGranularity
* @attr ref android.R.styleable#TextView_autoSizePresetSizes
+ * @attr ref android.R.styleable#TextView_accessibilityHeading
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
@@ -403,6 +404,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mCurTextColor;
private int mCurHintTextColor;
private boolean mFreezesText;
+ private boolean mIsAccessibilityHeading;
private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
@@ -1267,6 +1269,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextView_lineHeight:
lineHeight = a.getDimensionPixelSize(attr, -1);
break;
+ case com.android.internal.R.styleable.TextView_accessibilityHeading:
+ mIsAccessibilityHeading = a.getBoolean(attr, false);
}
}
@@ -5128,6 +5132,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Gets whether this view is a heading for accessibility purposes.
+ *
+ * @return {@code true} if the view is a heading, {@code false} otherwise.
+ *
+ * @attr ref android.R.styleable#TextView_accessibilityHeading
+ */
+ public boolean isAccessibilityHeading() {
+ return mIsAccessibilityHeading;
+ }
+
+ /**
+ * Set if view is a heading for a section of content for accessibility purposes.
+ *
+ * @param isHeading {@code true} if the view is a heading, {@code false} otherwise.
+ *
+ * @attr ref android.R.styleable#TextView_accessibilityHeading
+ */
+ public void setAccessibilityHeading(boolean isHeading) {
+ if (isHeading != mIsAccessibilityHeading) {
+ mIsAccessibilityHeading = isHeading;
+ notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ }
+
+ /**
* Convenience method to append the specified text to the TextView's
* display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
* if it was not already editable.
@@ -10677,6 +10706,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
info.setText(getTextForAccessibility());
info.setHintText(mHint);
info.setShowingHintText(isShowingHint());
+ info.setHeading(mIsAccessibilityHeading);
if (mBufferType == BufferType.EDITABLE) {
info.setEditable(true);
diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java
index 310a7bbdcabf..955f0532471a 100644
--- a/core/java/android/widget/VideoView2.java
+++ b/core/java/android/widget/VideoView2.java
@@ -20,9 +20,8 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Canvas;
import android.media.AudioAttributes;
-import android.media.MediaPlayer;
+import android.media.AudioManager;
import android.media.update.ApiLoader;
import android.media.update.VideoView2Provider;
import android.media.update.ViewProvider;
@@ -80,7 +79,8 @@ public class VideoView2 extends FrameLayout {
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mProvider = ApiLoader.getProvider(context).createVideoView2(this, new SuperProvider());
+ mProvider = ApiLoader.getProvider(context).createVideoView2(this, new SuperProvider(),
+ attrs, defStyleAttr, defStyleRes);
}
/**
@@ -93,6 +93,20 @@ public class VideoView2 extends FrameLayout {
/**
* @hide
*/
+ public void setMediaControlView2(MediaControlView2 mediaControlView) {
+ mProvider.setMediaControlView2_impl(mediaControlView);
+ }
+
+ /**
+ * @hide
+ */
+ public MediaControlView2 getMediaControlView2() {
+ return mProvider.getMediaControlView2_impl();
+ }
+
+ /**
+ * @hide
+ */
public void start() {
mProvider.start_impl();
}
@@ -161,6 +175,45 @@ public class VideoView2 extends FrameLayout {
}
/**
+ * Sets playback speed.
+ *
+ * It is expressed as a multiplicative factor, where normal speed is 1.0f. If it is less than
+ * or equal to zero, it will be just ignored and nothing will be changed. If it exceeds the
+ * maximum speed that internal engine supports, system will determine best handling or it will
+ * be reset to the normal speed 1.0f.
+ * TODO: This should be revised after integration with MediaPlayer2.
+ * @param speed the playback speed. It should be positive.
+ * @hide
+ */
+ public void setSpeed(float speed) {
+ mProvider.setSpeed_impl(speed);
+ }
+
+ /**
+ * Returns current speed setting.
+ *
+ * If setSpeed() has never been called, returns the default value 1.0f.
+ * @return current speed setting
+ * @hide
+ */
+ public float getSpeed() {
+ return mProvider.getSpeed_impl();
+ }
+
+ /**
+ * Sets which type of audio focus will be requested during the playback, or configures playback
+ * to not request audio focus. Valid values for focus requests are
+ * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use
+ * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be
+ * requested when playback starts. You can for instance use this when playing a silent animation
+ * through this class, and you don't want to affect other audio applications playing in the
+ * background.
+ *
+ * @param focusGain the type of audio focus gain that will be requested, or
+ * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during playback.
+ *
* @hide
*/
public void setAudioFocusRequest(int focusGain) {
@@ -168,6 +221,10 @@ public class VideoView2 extends FrameLayout {
}
/**
+ * Sets the {@link AudioAttributes} to be used during the playback of the video.
+ *
+ * @param attributes non-null <code>AudioAttributes</code>.
+ *
* @hide
*/
public void setAudioAttributes(@NonNull AudioAttributes attributes) {
@@ -175,6 +232,9 @@ public class VideoView2 extends FrameLayout {
}
/**
+ * Sets video path.
+ *
+ * @param path the path of the video.
* @hide
*/
public void setVideoPath(String path) {
@@ -198,13 +258,6 @@ public class VideoView2 extends FrameLayout {
/**
* @hide
*/
- public void setMediaController2(MediaController2 controllerView) {
- mProvider.setMediaController2_impl(controllerView);
- }
-
- /**
- * @hide
- */
public void setViewType(@ViewType int viewType) {
mProvider.setViewType_impl(viewType);
}
@@ -227,28 +280,28 @@ public class VideoView2 extends FrameLayout {
/**
* @hide
*/
- public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) {
+ public void setOnPreparedListener(OnPreparedListener l) {
mProvider.setOnPreparedListener_impl(l);
}
/**
* @hide
*/
- public void setOnCompletionListener(MediaPlayer.OnCompletionListener l) {
+ public void setOnCompletionListener(OnCompletionListener l) {
mProvider.setOnCompletionListener_impl(l);
}
/**
* @hide
*/
- public void setOnErrorListener(MediaPlayer.OnErrorListener l) {
+ public void setOnErrorListener(OnErrorListener l) {
mProvider.setOnErrorListener_impl(l);
}
/**
* @hide
*/
- public void setOnInfoListener(MediaPlayer.OnInfoListener l) {
+ public void setOnInfoListener(OnInfoListener l) {
mProvider.setOnInfoListener_impl(l);
}
@@ -260,15 +313,61 @@ public class VideoView2 extends FrameLayout {
}
/**
+ * Interface definition of a callback to be invoked when the viw type has been changed.
* @hide
*/
public interface OnViewTypeChangedListener {
/**
- * @hide
+ * Called when the view type has been changed.
+ * @see VideoView2#setViewType(int)
*/
void onViewTypeChanged(@ViewType int viewType);
}
+ /**
+ * @hide
+ */
+ public interface OnPreparedListener {
+ /**
+ * Called when the media file is ready for playback.
+ */
+ void onPrepared();
+ }
+
+ /**
+ * @hide
+ */
+ public interface OnCompletionListener {
+ /**
+ * Called when the end of a media source is reached during playback.
+ */
+ void onCompletion();
+ }
+
+ /**
+ * @hide
+ */
+ public interface OnErrorListener {
+ /**
+ * Called to indicate an error.
+ */
+ boolean onError(int what, int extra);
+ }
+
+ /**
+ * @hide
+ */
+ public interface OnInfoListener {
+ /**
+ * Called to indicate an info or a warning.
+ * @see MediaPlayer#OnInfoListener
+ *
+ * @param what the type of info or warning.
+ * @param extra an extra code, specific to the info.
+ */
+ void onInfo(int what, int extra);
+ }
+
@Override
public CharSequence getAccessibilityClassName() {
return mProvider.getAccessibilityClassName_impl();
@@ -306,26 +405,6 @@ public class VideoView2 extends FrameLayout {
private class SuperProvider implements ViewProvider {
@Override
- public void onAttachedToWindow_impl() {
- VideoView2.super.onAttachedToWindow();
- }
-
- @Override
- public void onDetachedFromWindow_impl() {
- VideoView2.super.onDetachedFromWindow();
- }
-
- @Override
- public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
- VideoView2.super.onLayout(changed, left, top, right, bottom);
- }
-
- @Override
- public void draw_impl(Canvas canvas) {
- VideoView2.super.draw(canvas);
- }
-
- @Override
public CharSequence getAccessibilityClassName_impl() {
return VideoView2.super.getAccessibilityClassName();
}
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index e765ab1eae2f..8a456d1c6f58 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -25,6 +25,7 @@ import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.dex.DexMetadataHelper;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
@@ -415,6 +416,9 @@ public class PackageHelper {
sizeBytes += codeFile.length();
}
+ // Include raw dex metadata files
+ sizeBytes += DexMetadataHelper.getPackageDexMetadataSize(pkg);
+
// Include all relevant native code
sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride);
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 7b023f412cbc..621619c5134d 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -619,6 +619,10 @@ public class ArrayUtils {
return size - leftIdx;
}
+ public static @NonNull int[] defeatNullable(@Nullable int[] val) {
+ return (val != null) ? val : EmptyArray.INT;
+ }
+
public static @NonNull String[] defeatNullable(@Nullable String[] val) {
return (val != null) ? val : EmptyArray.STRING;
}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 274239b218ce..e3f1f472ce5e 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -60,7 +60,7 @@ interface ILockSettings {
in byte[] token, int requestedQuality, int userId);
void unlockUserWithToken(long tokenHandle, in byte[] token, int userId);
- // Keystore RecoveryManager methods.
+ // Keystore RecoveryController methods.
// {@code ServiceSpecificException} may be thrown to signal an error, which caller can
// convert to {@code RecoveryManagerException}.
void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList);
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index ed9d0e9e86c3..1c2528ffecd4 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -393,7 +393,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
canvas.drawBitmap(bm, 0.0f, 0.0f, &paint);
bm.swap(scaledBm);
- nativeBitmap = scaledPixelRef;
+ nativeBitmap = std::move(scaledPixelRef);
}
if (jpostProcess) {
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 9494fb8e7eef..061349aee96f 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -391,6 +391,10 @@ static void JHwParcel_native_verifySuccess(JNIEnv *env, jobject thiz) {
Status status;
status_t err = ::android::hardware::readFromParcel(&status, *parcel);
signalExceptionForError(env, err);
+
+ if (!status.isOk()) {
+ signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
+ }
}
static void JHwParcel_native_release(
diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index bdae0b22987f..58c05b44ed5c 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -85,12 +85,12 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong
// Regular JNI
static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr,
- jcharArray javaText) {
+ jcharArray javaText, jboolean computeHyphenation) {
ScopedCharArrayRO text(env, javaText);
const minikin::U16StringPiece textBuffer(text.get(), text.size());
// Pass the ownership to Java.
- return toJLong(toBuilder(builderPtr)->build(textBuffer).release());
+ return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation).release());
}
// Regular JNI
@@ -108,7 +108,7 @@ static const JNINativeMethod gMethods[] = {
{"nInitBuilder", "()J", (void*) nInitBuilder},
{"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
- {"nBuildNativeMeasuredParagraph", "(J[C)J", (void*) nBuildNativeMeasuredParagraph},
+ {"nBuildNativeMeasuredParagraph", "(J[CZ)J", (void*) nBuildNativeMeasuredParagraph},
{"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
// MeasuredParagraph native functions.
diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto
index c9f7d52ae83f..8753bf768321 100644
--- a/core/proto/android/server/forceappstandbytracker.proto
+++ b/core/proto/android/server/forceappstandbytracker.proto
@@ -41,13 +41,4 @@ message ForceAppStandbyTrackerProto {
// Packages that are disallowed OP_RUN_ANY_IN_BACKGROUND.
repeated RunAnyInBackgroundRestrictedPackages run_any_in_background_restricted_packages = 5;
-
- // Whether device is a small battery device
- optional bool is_small_battery_device = 6;
-
- // Whether force app standby for small battery device setting is enabled
- optional bool force_all_apps_standby_for_small_battery = 7;
-
- // Whether device is charging
- optional bool is_charging = 8;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d2a22d0794b6..547e83c144a4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -554,8 +554,6 @@
<protected-broadcast android:name="android.intent.action.DEVICE_LOCKED_CHANGED" />
<!-- Added in O -->
- <!-- TODO: temporary broadcast used by AutoFillManagerServiceImpl; will be removed -->
- <protected-broadcast android:name="com.android.internal.autofill.action.REQUEST_AUTOFILL" />
<protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
<protected-broadcast android:name="com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION" />
<protected-broadcast android:name="android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED" />
@@ -2684,6 +2682,13 @@
<permission android:name="android.permission.BIND_AUTOFILL_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by an {@link android.service.autofill.AutofillFieldClassificationService}
+ to ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by hotword enrollment application,
to ensure that only the system can interact with it.
@hide <p>Not for use by third-party applications.</p> -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index fd33dc9a32dc..4eaf93d822b2 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4876,6 +4876,8 @@
<!-- Justification by stretching word spacing. -->
<enum name="inter_word" value = "1" />
</attr>
+ <!-- Whether or not this view is a heading for accessibility purposes. -->
+ <attr name="accessibilityHeading" format="boolean"/>
</declare-styleable>
<declare-styleable name="TextViewAppearance">
<!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3b02a967a4b2..8de3a98a3585 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3252,4 +3252,6 @@
<!-- Package name that should be granted Notification Assistant access -->
<string name="config_defaultAssistantAccessPackage" translatable="false">android.ext.services</string>
+
+ <bool name="config_supportBluetoothPersistedState">true</bool>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 656add664022..9cdf5531225b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2865,6 +2865,7 @@
<public name="firstBaselineToTopHeight" />
<public name="lastBaselineToBottomHeight" />
<public name="lineHeight" />
+ <public name="accessibilityHeading" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b2fa294f77be..2cfe919fb8f5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -379,6 +379,9 @@
<string name="factory_reset_message">The admin app can\'t be used. Your device will now be
erased.\n\nIf you have questions, contact your organization's admin.</string>
+ <!-- A toast message displayed when printing is attempted but disabled by policy. -->
+ <string name="printing_disabled_by">Printing disabled by <xliff:g id="owner_app">%s</xliff:g>.</string>
+
<!-- Display name for any time a piece of data refers to the owner of the phone. For example, this could be used in place of the phone's phone number. -->
<string name="me">Me</string>
@@ -3754,6 +3757,11 @@
<!-- Notification body when background data usage is limited. -->
<string name="data_usage_restricted_body">Tap to remove restriction.</string>
+ <!-- Notification title when there has been recent excessive data usage. [CHAR LIMIT=32] -->
+ <string name="data_usage_rapid_title">Large data usage</string>
+ <!-- Notification body when there has been recent excessive data usage. [CHAR LIMIT=128] -->
+ <string name="data_usage_rapid_body">Your data usage over the last few days is larger than normal. Tap to view usage and settings.</string>
+
<!-- SSL Certificate dialogs -->
<!-- Title for an SSL Certificate dialog -->
<string name="ssl_certificate">Security certificate</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6e33648b6f14..1993ab4b5924 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -878,6 +878,7 @@
<java-symbol type="string" name="preposition_for_time" />
<java-symbol type="string" name="print_service_installed_title" />
<java-symbol type="string" name="print_service_installed_message" />
+ <java-symbol type="string" name="printing_disabled_by" />
<java-symbol type="string" name="progress_erasing" />
<java-symbol type="string" name="mobile_provisioning_apn" />
<java-symbol type="string" name="mobile_provisioning_url" />
@@ -1973,6 +1974,8 @@
<java-symbol type="string" name="data_usage_warning_title" />
<java-symbol type="string" name="data_usage_wifi_limit_snoozed_title" />
<java-symbol type="string" name="data_usage_wifi_limit_title" />
+ <java-symbol type="string" name="data_usage_rapid_title" />
+ <java-symbol type="string" name="data_usage_rapid_body" />
<java-symbol type="string" name="default_wallpaper_component" />
<java-symbol type="string" name="device_storage_monitor_notification_channel" />
<java-symbol type="string" name="dlg_ok" />
@@ -3219,4 +3222,6 @@
<java-symbol type="string" name="harmful_app_warning_title" />
<java-symbol type="string" name="config_defaultAssistantAccessPackage" />
+
+ <java-symbol type="bool" name="config_supportBluetoothPersistedState" />
</resources>
diff --git a/core/tests/coretests/apks/install-split-base/Android.mk b/core/tests/coretests/apks/install-split-base/Android.mk
new file mode 100644
index 000000000000..5b60e3167fc7
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-base/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := install_split_base
+
+include $(FrameworkCoreTests_BUILD_PACKAGE) \ No newline at end of file
diff --git a/core/tests/coretests/apks/install-split-base/AndroidManifest.xml b/core/tests/coretests/apks/install-split-base/AndroidManifest.xml
new file mode 100644
index 000000000000..c2bfeddf32e3
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-base/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.frameworks.coretests.install_split"
+ android:isolatedSplits="true">
+
+ <application android:label="ClassloaderSplitApp">
+ <activity android:name=".BaseActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java b/core/tests/coretests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java
new file mode 100644
index 000000000000..cb5760ceef8e
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+
+package com.google.android.dexapis.splitapp;
+
+import android.app.Activity;
+
+/** Main activity */
+public class BaseActivity extends Activity {
+}
diff --git a/core/tests/coretests/apks/install-split-feature-a/Android.mk b/core/tests/coretests/apks/install-split-feature-a/Android.mk
new file mode 100644
index 000000000000..0f37d16a7688
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-feature-a/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := install_split_feature_a
+
+LOCAL_USE_AAPT2 := true
+LOCAL_AAPT_FLAGS += --custom-package com.google.android.dexapis.splitapp.feature_a
+LOCAL_AAPT_FLAGS += --package-id 0x80
+
+include $(FrameworkCoreTests_BUILD_PACKAGE) \ No newline at end of file
diff --git a/core/tests/coretests/apks/install-split-feature-a/AndroidManifest.xml b/core/tests/coretests/apks/install-split-feature-a/AndroidManifest.xml
new file mode 100644
index 000000000000..3221c75ebe0c
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-feature-a/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.frameworks.coretests.install_split"
+ featureSplit="feature_a">
+
+ <application>
+ <activity android:name=".feature_a.FeatureAActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java b/core/tests/coretests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java
new file mode 100644
index 000000000000..0af5f893164c
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+
+package com.google.android.dexapis.splitapp.feature_a;
+
+import android.app.Activity;
+
+/** Main activity */
+public class FeatureAActivity extends Activity {
+}
diff --git a/core/tests/coretests/src/android/content/pm/crossprofile/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
index 80e4c0214ad4..0cfcd8f85784 100644
--- a/core/tests/coretests/src/android/content/pm/crossprofile/CrossProfileAppsTest.java
+++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.content.pm.crossprofile;
+package android.content.pm;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.nullable;
@@ -42,7 +42,7 @@ import java.util.List;
/**
* Build/Install/Run:
- * bit FrameworksCoreTests:android.content.pm.crossprofile.CrossProfileAppsTest
+ * atest frameworks/base/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
*/
@Presubmit
@RunWith(MockitoJUnitRunner.class)
@@ -118,7 +118,7 @@ public class CrossProfileAppsTest {
public void getProfileSwitchingIcon_managedProfile() {
setValidTargetProfile(MANAGED_PROFILE);
- mCrossProfileApps.getProfileSwitchingIcon(MANAGED_PROFILE);
+ mCrossProfileApps.getProfileSwitchingIconDrawable(MANAGED_PROFILE);
verify(mResources).getDrawable(R.drawable.ic_corp_badge, null);
}
@@ -126,13 +126,13 @@ public class CrossProfileAppsTest {
public void getProfileSwitchingIcon_personalProfile() {
setValidTargetProfile(PERSONAL_PROFILE);
- mCrossProfileApps.getProfileSwitchingIcon(PERSONAL_PROFILE);
+ mCrossProfileApps.getProfileSwitchingIconDrawable(PERSONAL_PROFILE);
verify(mResources).getDrawable(R.drawable.ic_account_circle, null);
}
@Test(expected = SecurityException.class)
public void getProfileSwitchingIcon_securityException() {
- mCrossProfileApps.getProfileSwitchingIcon(PERSONAL_PROFILE);
+ mCrossProfileApps.getProfileSwitchingIconDrawable(PERSONAL_PROFILE);
}
private void setValidTargetProfile(UserHandle userHandle) {
diff --git a/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java b/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java
new file mode 100644
index 000000000000..4b8442958900
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java
@@ -0,0 +1,208 @@
+/**
+ * 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.content.pm.dex;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Package;
+import android.content.pm.PackageParser.PackageParserException;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.frameworks.coretests.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import libcore.io.IoUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DexMetadataHelperTest {
+ private static final String APK_FILE_EXTENSION = ".apk";
+ private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
+
+ private File mTmpDir = null;
+
+ @Before
+ public void setUp() {
+ mTmpDir = IoUtils.createTemporaryDirectory("DexMetadataHelperTest");
+ }
+
+ @After
+ public void tearDown() {
+ if (mTmpDir != null) {
+ File[] files = mTmpDir.listFiles();
+ for (File f : files) {
+ f.delete();
+ }
+ }
+ }
+
+ private File createDexMetadataFile(String apkFileName) throws IOException {
+ File dmFile = new File(mTmpDir, apkFileName.replace(APK_FILE_EXTENSION,
+ DEX_METADATA_FILE_EXTENSION));
+ try (FileOutputStream fos = new FileOutputStream(dmFile)) {
+ try (ZipOutputStream zipOs = new ZipOutputStream(fos)) {
+ zipOs.putNextEntry(new ZipEntry("primary.prof"));
+ zipOs.closeEntry();
+ }
+ }
+ return dmFile;
+ }
+
+ private File copyApkToToTmpDir(String apkFileName, int apkResourceId) throws IOException {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ File outFile = new File(mTmpDir, apkFileName);
+ try (InputStream is = context.getResources().openRawResource(apkResourceId)) {
+ FileUtils.copyToFileOrThrow(is, outFile);
+ }
+ return outFile;
+ }
+
+ @Test
+ public void testParsePackageWithDmFileValid() throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ createDexMetadataFile("install_split_base.apk");
+ Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
+
+ Map<String, String> packageDexMetadata = DexMetadataHelper.getPackageDexMetadata(pkg);
+ assertEquals(1, packageDexMetadata.size());
+ String baseDexMetadata = packageDexMetadata.get(pkg.baseCodePath);
+ assertNotNull(baseDexMetadata);
+ assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.baseCodePath));
+ }
+
+ @Test
+ public void testParsePackageSplitsWithDmFileValid()
+ throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
+ createDexMetadataFile("install_split_base.apk");
+ createDexMetadataFile("install_split_feature_a.apk");
+ Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
+
+ Map<String, String> packageDexMetadata = DexMetadataHelper.getPackageDexMetadata(pkg);
+ assertEquals(2, packageDexMetadata.size());
+ String baseDexMetadata = packageDexMetadata.get(pkg.baseCodePath);
+ assertNotNull(baseDexMetadata);
+ assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.baseCodePath));
+
+ String splitDexMetadata = packageDexMetadata.get(pkg.splitCodePaths[0]);
+ assertNotNull(splitDexMetadata);
+ assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.splitCodePaths[0]));
+ }
+
+ @Test
+ public void testParsePackageSplitsNoBaseWithDmFileValid()
+ throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
+ createDexMetadataFile("install_split_feature_a.apk");
+ Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
+
+ Map<String, String> packageDexMetadata = DexMetadataHelper.getPackageDexMetadata(pkg);
+ assertEquals(1, packageDexMetadata.size());
+
+ String splitDexMetadata = packageDexMetadata.get(pkg.splitCodePaths[0]);
+ assertNotNull(splitDexMetadata);
+ assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.splitCodePaths[0]));
+ }
+
+ @Test
+ public void testParsePackageWithDmFileInvalid() throws IOException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
+ Files.createFile(invalidDmFile.toPath());
+ try {
+ PackageParser.Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
+ DexMetadataHelper.validatePackageDexMetadata(pkg);
+ } catch (PackageParserException e) {
+ assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+ }
+ }
+
+ @Test
+ public void testParsePackageSplitsWithDmFileInvalid()
+ throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
+ createDexMetadataFile("install_split_base.apk");
+ File invalidDmFile = new File(mTmpDir, "install_split_feature_a.dm");
+ Files.createFile(invalidDmFile.toPath());
+
+ try {
+ PackageParser.Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
+ DexMetadataHelper.validatePackageDexMetadata(pkg);
+ } catch (PackageParserException e) {
+ assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+ }
+ }
+
+ @Test
+ public void testPackageWithDmFileNoMatch() throws IOException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ createDexMetadataFile("non_existent.apk");
+
+ try {
+ DexMetadataHelper.validateDexPaths(mTmpDir.list());
+ fail("Should fail validation");
+ } catch (IllegalStateException e) {
+ // expected.
+ }
+ }
+
+ @Test
+ public void testPackageSplitsWithDmFileNoMatch()
+ throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
+ createDexMetadataFile("install_split_base.apk");
+ createDexMetadataFile("install_split_feature_a.mistake.apk");
+
+ try {
+ DexMetadataHelper.validateDexPaths(mTmpDir.list());
+ fail("Should fail validation");
+ } catch (IllegalStateException e) {
+ // expected.
+ }
+ }
+
+ private static boolean isDexMetadataForApk(String dmaPath, String apkPath) {
+ return apkPath.substring(0, apkPath.length() - APK_FILE_EXTENSION.length()).equals(
+ dmaPath.substring(0, dmaPath.length() - DEX_METADATA_FILE_EXTENSION.length()));
+ }
+}
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 417faf220d39..eaabdc8b815c 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -505,4 +505,84 @@ public class TypefaceSystemFallbackTest {
assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f);
assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
}
+
+ @Test
+ public void testBuildSystemFallback_ElegantFallback_customFallback_missingFile() {
+ final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font weight='400' style='normal'>no_coverage.ttf</font>"
+ + " </family>"
+ + " <family name='serif'>"
+ + " <font weight='400' style='normal'>no_coverage.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font weight='400' style='normal'>a3em.ttf</font>"
+ + " <font weight='400' style='normal' fallbackFor='serif'>NoSuchFont.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font weight='400' style='normal'>b3em.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(xml, fontMap, fallbackMap);
+
+ final Paint paint = new Paint();
+
+ Typeface testTypeface = fontMap.get("serif");
+ assertNotNull(testTypeface);
+ paint.setTypeface(testTypeface);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+
+ testTypeface = fontMap.get("sans-serif");
+ assertNotNull(testTypeface);
+ paint.setTypeface(testTypeface);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+ }
+
+ @Test
+ public void testBuildSystemFallback_ElegantFallback_customFallback_missingFile2() {
+ final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font weight='400' style='normal'>no_coverage.ttf</font>"
+ + " </family>"
+ + " <family name='serif'>"
+ + " <font weight='400' style='normal'>no_coverage.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font weight='400' style='normal' fallbackFor='serif'>NoSuchFont.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font weight='400' style='normal'>a3em.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(xml, fontMap, fallbackMap);
+
+ final Paint paint = new Paint();
+
+ Typeface testTypeface = fontMap.get("serif");
+ assertNotNull(testTypeface);
+ paint.setTypeface(testTypeface);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+
+ testTypeface = fontMap.get("sans-serif");
+ assertNotNull(testTypeface);
+ paint.setTypeface(testTypeface);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+ }
+
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index c2ae7760c80e..8da7cedd7ea1 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -213,7 +213,6 @@ public class SettingsBackupTest {
Settings.Global.FANCY_IME_ANIMATIONS,
Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
Settings.Global.FORCED_APP_STANDBY_ENABLED,
- Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED,
Settings.Global.FSTRIM_MANDATORY_INTERVAL,
Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
Settings.Global.GLOBAL_HTTP_PROXY_HOST,
diff --git a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
new file mode 100644
index 000000000000..00732b09f821
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.provider;
+
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.SettingsValidators.Validator;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+/** Tests that ensure all backed up settings have non-null validators. */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SettingsValidatorsTest {
+
+ @Test
+ public void ensureAllBackedUpSystemSettingsHaveValidators() {
+ String offenders = getOffenders(Settings.System.SETTINGS_TO_BACKUP,
+ Settings.System.VALIDATORS);
+
+ failIfOffendersPresent(offenders, "Settings.System");
+ }
+
+ @Test
+ public void ensureAllBackedUpGlobalSettingsHaveValidators() {
+ String offenders = getOffenders(Settings.Global.SETTINGS_TO_BACKUP,
+ Settings.Global.VALIDATORS);
+
+ failIfOffendersPresent(offenders, "Settings.Global");
+ }
+
+ private void failIfOffendersPresent(String offenders, String settingsType) {
+ if (offenders.length() > 0) {
+ fail("All " + settingsType + " settings that are backed up have to have a non-null"
+ + " validator, but those don't: " + offenders);
+ }
+ }
+
+ private String getOffenders(String[] settingsToBackup, Map<String, Validator> validators) {
+ StringBuilder offenders = new StringBuilder();
+ for (String setting : settingsToBackup) {
+ if (validators.get(setting) == null) {
+ offenders.append(setting).append(" ");
+ }
+ }
+ return offenders.toString();
+ }
+}
diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index 5d33397e13f2..f6300ee20985 100644
--- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -132,7 +132,7 @@ public class MeasuredParagraphTest {
public void buildForStaticLayout() {
MeasuredParagraph mt = null;
- mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
+ mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, false, null);
assertNotNull(mt);
assertNotNull(mt.getChars());
assertEquals("XXX", charsToString(mt.getChars()));
@@ -147,7 +147,7 @@ public class MeasuredParagraphTest {
// Recycle it
MeasuredParagraph mt2 =
- MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
+ MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, false, mt);
assertEquals(mt2, mt);
assertNotNull(mt2.getChars());
assertEquals("VVV", charsToString(mt.getChars()));
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 68b7ac287e98..ef4150763139 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1025,6 +1025,10 @@ public class Typeface {
xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir);
if (family != null) {
fallbackMap.valueAt(i).add(family);
+ } else if (defaultFamily != null) {
+ fallbackMap.valueAt(i).add(defaultFamily);
+ } else {
+ // There is no valid for for default fallback. Ignore.
}
}
}
diff --git a/media/java/android/media/update/ApiLoader.java b/media/java/android/media/update/ApiLoader.java
index 07483f60c69e..b928e9319b18 100644
--- a/media/java/android/media/update/ApiLoader.java
+++ b/media/java/android/media/update/ApiLoader.java
@@ -16,8 +16,10 @@
package android.media.update;
+import android.content.res.Resources;
import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager;
+import android.os.Build;
/**
* @hide
@@ -34,23 +36,25 @@ public final class ApiLoader {
public static StaticProvider getProvider(Context context) {
try {
return (StaticProvider) getMediaLibraryImpl(context);
- } catch (NameNotFoundException | ReflectiveOperationException e) {
+ } catch (PackageManager.NameNotFoundException | ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
// TODO This method may do I/O; Ensure it does not violate (emit warnings in) strict mode.
- private static synchronized Object getMediaLibraryImpl(Context appContext)
- throws NameNotFoundException, ReflectiveOperationException {
+ private static synchronized Object getMediaLibraryImpl(Context context)
+ throws PackageManager.NameNotFoundException, ReflectiveOperationException {
if (sMediaLibrary != null) return sMediaLibrary;
- // TODO Dynamically find the package name
- Context libContext = appContext.createPackageContext(UPDATE_PACKAGE,
+ // TODO Figure out when to use which package (query media update service)
+ int flags = Build.IS_DEBUGGABLE ? 0 : PackageManager.MATCH_FACTORY_ONLY;
+ Context libContext = context.createApplicationContext(
+ context.getPackageManager().getPackageInfo(UPDATE_PACKAGE, flags).applicationInfo,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
sMediaLibrary = libContext.getClassLoader()
.loadClass(UPDATE_CLASS)
- .getMethod(UPDATE_METHOD, Context.class, Context.class)
- .invoke(null, appContext, libContext);
+ .getMethod(UPDATE_METHOD, Resources.class, Resources.Theme.class)
+ .invoke(null, libContext.getResources(), libContext.getTheme());
return sMediaLibrary;
}
}
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaControlView2Provider.java
index 71fbd084e643..83763b41bcaa 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaControlView2Provider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ package android.media.update;
import android.annotation.SystemApi;
import android.media.session.MediaController;
import android.view.View;
-import android.view.View.OnClickListener;
/**
* Interface for connecting the public API to an updatable implementation.
@@ -30,19 +29,17 @@ import android.view.View.OnClickListener;
*
* All methods behave as per their namesake in the public API.
*
- * @see android.widget.MediaController2
+ * @see android.widget.MediaControlView2
*
* @hide
*/
// TODO @SystemApi
-public interface MediaController2Provider extends ViewProvider {
+public interface MediaControlView2Provider extends ViewProvider {
void setController_impl(MediaController controller);
- void setAnchorView_impl(View view);
void show_impl();
void show_impl(int timeout);
boolean isShowing_impl();
void hide_impl();
- void setPrevNextListeners_impl(OnClickListener next, OnClickListener prev);
void showCCButton_impl();
boolean isPlaying_impl();
int getCurrentPosition_impl();
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index a1e2404ca78a..1a0df52413d2 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -16,8 +16,10 @@
package android.media.update;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.widget.MediaController2;
+import android.util.AttributeSet;
+import android.widget.MediaControlView2;
import android.widget.VideoView2;
/**
@@ -30,7 +32,9 @@ import android.widget.VideoView2;
*/
// TODO @SystemApi
public interface StaticProvider {
- MediaController2Provider createMediaController2(
- MediaController2 instance, ViewProvider superProvider);
- VideoView2Provider createVideoView2(VideoView2 instance, ViewProvider superProvider);
+ MediaControlView2Provider createMediaControlView2(
+ MediaControlView2 instance, ViewProvider superProvider);
+ VideoView2Provider createVideoView2(
+ VideoView2 instance, ViewProvider superProvider,
+ @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
}
diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java
index 6fc9bdc64e02..b7a24e59ab3e 100644
--- a/media/java/android/media/update/VideoView2Provider.java
+++ b/media/java/android/media/update/VideoView2Provider.java
@@ -17,9 +17,8 @@
package android.media.update;
import android.media.AudioAttributes;
-import android.media.MediaPlayer;
import android.net.Uri;
-import android.widget.MediaController2;
+import android.widget.MediaControlView2;
import android.widget.VideoView2;
import java.util.Map;
@@ -39,6 +38,8 @@ import java.util.Map;
*/
// TODO @SystemApi
public interface VideoView2Provider extends ViewProvider {
+ void setMediaControlView2_impl(MediaControlView2 mediaControlView);
+ MediaControlView2 getMediaControlView2_impl();
void start_impl();
void pause_impl();
int getDuration_impl();
@@ -49,18 +50,19 @@ public interface VideoView2Provider extends ViewProvider {
int getAudioSessionId_impl();
void showSubtitle_impl();
void hideSubtitle_impl();
+ void setSpeed_impl(float speed);
+ float getSpeed_impl();
void setAudioFocusRequest_impl(int focusGain);
void setAudioAttributes_impl(AudioAttributes attributes);
void setVideoPath_impl(String path);
void setVideoURI_impl(Uri uri);
void setVideoURI_impl(Uri uri, Map<String, String> headers);
- void setMediaController2_impl(MediaController2 controllerView);
void setViewType_impl(int viewType);
int getViewType_impl();
void stopPlayback_impl();
- void setOnPreparedListener_impl(MediaPlayer.OnPreparedListener l);
- void setOnCompletionListener_impl(MediaPlayer.OnCompletionListener l);
- void setOnErrorListener_impl(MediaPlayer.OnErrorListener l);
- void setOnInfoListener_impl(MediaPlayer.OnInfoListener l);
+ void setOnPreparedListener_impl(VideoView2.OnPreparedListener l);
+ void setOnCompletionListener_impl(VideoView2.OnCompletionListener l);
+ void setOnErrorListener_impl(VideoView2.OnErrorListener l);
+ void setOnInfoListener_impl(VideoView2.OnInfoListener l);
void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l);
}
diff --git a/media/java/android/media/update/ViewProvider.java b/media/java/android/media/update/ViewProvider.java
index bc8f20302d35..e54240433121 100644
--- a/media/java/android/media/update/ViewProvider.java
+++ b/media/java/android/media/update/ViewProvider.java
@@ -37,10 +37,6 @@ import android.view.MotionEvent;
// TODO @SystemApi
public interface ViewProvider {
// TODO Add more (all?) methods from View
- void onAttachedToWindow_impl();
- void onDetachedFromWindow_impl();
- void onLayout_impl(boolean changed, int left, int top, int right, int bottom);
- void draw_impl(Canvas canvas);
CharSequence getAccessibilityClassName_impl();
boolean onTouchEvent_impl(MotionEvent ev);
boolean onTrackballEvent_impl(MotionEvent ev);
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 61164e068da6..0704e3545b62 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -29,6 +29,13 @@ cc_library_shared {
shared_libs: [
"libandroid_runtime",
],
+
+ arch: {
+ arm: {
+ // TODO: This is to work around b/24465209. Remove after root cause is fixed
+ ldflags: ["-Wl,--hash-style=both"],
+ },
+ },
}
// The headers module is in frameworks/native/Android.bp.
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
index 291009ef7005..63d3623c468a 100644
--- a/packages/ExtServices/AndroidManifest.xml
+++ b/packages/ExtServices/AndroidManifest.xml
@@ -51,6 +51,13 @@
</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>
+ </service>
+
<library android:name="android.ext.services"/>
</application>
diff --git a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
new file mode 100644
index 000000000000..ea516a1db8b8
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
@@ -0,0 +1,81 @@
+/*
+ * 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 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.Arrays;
+import java.util.List;
+
+public class AutofillFieldClassificationServiceImpl extends AutofillFieldClassificationService {
+
+ private static final String TAG = "AutofillFieldClassificationServiceImpl";
+ private static final boolean DEBUG = false;
+ private static final List<String> sAvailableAlgorithms = Arrays.asList(EditDistanceScorer.NAME);
+
+ @Override
+ public List<String> onGetAvailableAlgorithms() {
+ return sAvailableAlgorithms;
+ }
+
+ @Override
+ public String onGetDefaultAlgorithm() {
+ return EditDistanceScorer.NAME;
+ }
+
+ @Nullable
+ @Override
+ public Scores 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 + ")");
+ // TODO(b/70939974): add unit test
+ return null;
+ }
+ if (algorithmName != null && !algorithmName.equals(EditDistanceScorer.NAME)) {
+ Log.w(TAG, "Ignoring invalid algorithm (" + algorithmName + ") and using "
+ + EditDistanceScorer.NAME + " instead");
+ }
+
+ final String actualAlgorithmName = EditDistanceScorer.NAME;
+ final int actualValuesSize = actualValues.size();
+ final int userDataValuesSize = userDataValues.size();
+ if (DEBUG) {
+ Log.d(TAG, "getScores() will return a " + actualValuesSize + "x"
+ + userDataValuesSize + " matrix for " + actualAlgorithmName);
+ }
+ final Scores scores = new Scores(actualAlgorithmName, actualValuesSize, userDataValuesSize);
+ final float[][] scoresMatrix = scores.getScores();
+
+ final EditDistanceScorer algorithm = EditDistanceScorer.getInstance();
+ for (int i = 0; i < actualValuesSize; i++) {
+ for (int j = 0; j < userDataValuesSize; j++) {
+ final float score = algorithm.getScore(actualValues.get(i), userDataValues.get(j));
+ scoresMatrix[i][j] = score;
+ }
+ }
+ return scores;
+ }
+}
diff --git a/core/java/android/service/autofill/EditDistanceScorer.java b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java
index 97a386866665..d2e804af1b43 100644
--- a/core/java/android/service/autofill/EditDistanceScorer.java
+++ b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java
@@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.service.autofill;
+package android.ext.services.autofill;
import android.annotation.NonNull;
-import android.annotation.TestApi;
import android.view.autofill.AutofillValue;
/**
@@ -24,20 +23,15 @@ import android.view.autofill.AutofillValue;
* by the user and the expected value predicted by an autofill service.
*/
// TODO(b/70291841): explain algorithm once it's fully implemented
-/** @hide */
-@TestApi
-public final class EditDistanceScorer {
+final class EditDistanceScorer {
private static final EditDistanceScorer sInstance = new EditDistanceScorer();
- /** @hide */
public static final String NAME = "EDIT_DISTANCE";
/**
* Gets the singleton instance.
*/
- @TestApi
- /** @hide */
public static EditDistanceScorer getInstance() {
return sInstance;
}
@@ -52,9 +46,7 @@ public final class EditDistanceScorer {
* <p>A full-match is {@code 1.0} (representing 100%), a full mismatch is {@code 0.0} and
* partial mathces are something in between, typically using edit-distance algorithms.
*
- * @hide
*/
- @TestApi
public float getScore(@NonNull AutofillValue actualValue, @NonNull String userDataValue) {
if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0;
// TODO(b/70291841): implement edit distance - currently it's returning either 0, 100%, or
diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java
new file mode 100644
index 000000000000..cc1571920e86
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.ext.services.autofill;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class EditDistanceScorerTest {
+
+ private final EditDistanceScorer mScorer = EditDistanceScorer.getInstance();
+
+ @Test
+ public void testGetScore_nullValue() {
+ assertFloat(mScorer.getScore(null, "D'OH!"), 0);
+ }
+
+ @Test
+ public void testGetScore_nonTextValue() {
+ assertFloat(mScorer.getScore(AutofillValue.forToggle(true), "D'OH!"), 0);
+ }
+
+ @Test
+ public void testGetScore_nullUserData() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("D'OH!"), null), 0);
+ }
+
+ @Test
+ public void testGetScore_fullMatch() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("D'OH!"), "D'OH!"), 1);
+ }
+
+ @Test
+ public void testGetScore_fullMatchMixedCase() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("D'OH!"), "D'oH!"), 1);
+ }
+
+ // TODO(b/70291841): might need to change it once it supports different sizes
+ @Test
+ public void testGetScore_mismatchDifferentSizes() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("One"), "MoreThanOne"), 0);
+ assertFloat(mScorer.getScore(AutofillValue.forText("MoreThanOne"), "One"), 0);
+ }
+
+ @Test
+ public void testGetScore_partialMatch() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dude"), "Dxxx"), 0.25F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dude"), "DUxx"), 0.50F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dude"), "DUDx"), 0.75F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dxxx"), "Dude"), 0.25F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("DUxx"), "Dude"), 0.50F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("DUDx"), "Dude"), 0.75F);
+ }
+
+ public static void assertFloat(float actualValue, float expectedValue) {
+ assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
+ }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index d130a61f62b3..b13de2ec5ce1 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -498,8 +498,6 @@
<string name="wifi_display_certification">Wireless display certification</string>
<!-- Setting Checkbox title whether to enable WiFi Verbose Logging. [CHAR LIMIT=40] -->
<string name="wifi_verbose_logging">Enable Wi\u2011Fi Verbose Logging</string>
- <!-- Setting Checkbox title whether to enable WiFi Scanning in the presence of traffic. [CHAR LIMIT=80] -->
- <string name="wifi_allow_scan_with_traffic">Always allow Wi\u2011Fi Roam Scans</string>
<!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] -->
<string name="mobile_data_always_on">Mobile data always active</string>
<!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] -->
@@ -554,8 +552,6 @@
<string name="wifi_display_certification_summary">Show options for wireless display certification</string>
<!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] -->
<string name="wifi_verbose_logging_summary">Increase Wi\u2011Fi logging level, show per SSID RSSI in Wi\u2011Fi Picker</string>
- <!-- Setting Checkbox summary whether to always allow WiFi Roam Scans [CHAR LIMIT=130] -->
- <string name="wifi_allow_scan_with_traffic_summary">Allow/Disallow Wi\u2011Fi Roam Scans based on the amount of data traffic present at the interface</string>
<!-- UI debug setting: limit size of Android logger buffers -->
<string name="select_logd_size_title">Logger buffer sizes</string>
<!-- UI debug setting: limit size of Android logger buffers [CHAR LIMIT=59] -->
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 175cff6b61b2..9ee205f9cde7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -60,6 +60,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.provider.SettingsValidators;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.text.TextUtils;
@@ -297,6 +298,11 @@ public class SettingsProvider extends ContentProvider {
@Override
public boolean onCreate() {
Settings.setInSystemServer();
+
+ // fail to boot if there're any backed up settings that don't have a non-null validator
+ ensureAllBackedUpSystemSettingsHaveValidators();
+ ensureAllBackedUpGlobalSettingsHaveValidators();
+
synchronized (mLock) {
mUserManager = UserManager.get(getContext());
mPackageManager = AppGlobals.getPackageManager();
@@ -314,6 +320,38 @@ public class SettingsProvider extends ContentProvider {
return true;
}
+ private void ensureAllBackedUpSystemSettingsHaveValidators() {
+ String offenders = getOffenders(Settings.System.SETTINGS_TO_BACKUP,
+ Settings.System.VALIDATORS);
+
+ failToBootIfOffendersPresent(offenders, "Settings.System");
+ }
+
+ private void ensureAllBackedUpGlobalSettingsHaveValidators() {
+ String offenders = getOffenders(Settings.Global.SETTINGS_TO_BACKUP,
+ Settings.Global.VALIDATORS);
+
+ failToBootIfOffendersPresent(offenders, "Settings.Global");
+ }
+
+ private void failToBootIfOffendersPresent(String offenders, String settingsType) {
+ if (offenders.length() > 0) {
+ throw new RuntimeException("All " + settingsType + " settings that are backed up"
+ + " have to have a non-null validator, but those don't: " + offenders);
+ }
+ }
+
+ private String getOffenders(String[] settingsToBackup, Map<String,
+ SettingsValidators.Validator> validators) {
+ StringBuilder offenders = new StringBuilder();
+ for (String setting : settingsToBackup) {
+ if (validators.get(setting) == null) {
+ offenders.append(setting).append(" ");
+ }
+ }
+ return offenders.toString();
+ }
+
@Override
public Bundle call(String method, String name, Bundle args) {
final int requestingUserId = getRequestingUserId(args);
@@ -1472,7 +1510,7 @@ public class SettingsProvider extends ContentProvider {
}
private void validateSystemSettingValue(String name, String value) {
- Settings.System.Validator validator = Settings.System.VALIDATORS.get(name);
+ SettingsValidators.Validator validator = Settings.System.VALIDATORS.get(name);
if (validator != null && !validator.validate(value)) {
throw new IllegalArgumentException("Invalid value: " + value
+ " for setting: " + name);
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 631cc0d3df30..41dd0b36d160 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -67,7 +67,7 @@
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:paddingRight="0dp"
- android:paddingLeft="24dp"
+ android:paddingLeft="0dp"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_delete"
android:layout_alignEnd="@+id/pinEntry"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
index 947f27deb84e..33f7e750c390 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
@@ -81,7 +81,7 @@
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:paddingRight="0dp"
- android:paddingLeft="24dp"
+ android:paddingLeft="0dp"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_delete"
android:layout_alignEnd="@+id/pinEntry"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
index 6f270b405c11..4b385fc3d6e1 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
@@ -82,7 +82,7 @@
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:paddingRight="0dp"
- android:paddingLeft="24dp"
+ android:paddingLeft="0dp"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_delete"
android:layout_alignEnd="@+id/pinEntry"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index dde4dcfb23fe..6437903bd12a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -775,6 +775,15 @@
<string name="quick_settings_work_mode_label">Work mode</string>
<!-- QuickSettings: Label for the toggle to activate Night display (renamed "Night Light" with title caps). [CHAR LIMIT=20] -->
<string name="quick_settings_night_display_label">Night Light</string>
+ <!-- QuickSettings: Secondary text for when the Night Light will be enabled at sunset. [CHAR LIMIT=20] -->
+ <string name="quick_settings_night_secondary_label_on_at_sunset">On at sunset</string>
+ <!-- QuickSettings: Secondary text for when the Night Light will be on until sunrise. [CHAR LIMIT=20] -->
+ <string name="quick_settings_night_secondary_label_until_sunrise">Until sunrise</string>
+ <!-- QuickSettings: Secondary text for when the Night Light will be enabled at some user-selected time. [CHAR LIMIT=20] -->
+ <string name="quick_settings_night_secondary_label_on_at">On at <xliff:g id="time" example="10 pm">%s</xliff:g></string>
+ <!-- QuickSettings: Secondary text for when the Night Light will be on until some user-selected time. [CHAR LIMIT=20] -->
+ <string name="quick_settings_night_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string>
+
<!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] -->
<string name="quick_settings_nfc_label">NFC</string>
<!-- QuickSettings: NFC (off) [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 653e5000f72c..8501519d26aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -707,6 +707,10 @@ public class KeyguardViewMediator extends SystemUI {
&& !mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser()),
mSecondaryDisplayShowing, true /* forceCallbacks */);
+ } else {
+ // The system's keyguard is disabled or missing.
+ setShowingLocked(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser()),
+ mSecondaryDisplayShowing, true);
}
mStatusBarKeyguardViewManager =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index a226f3cac83a..9eb9906ba3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -29,7 +29,6 @@ import android.widget.TextView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
-import com.android.systemui.R.id;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 763ffc677bc2..99a9be38065d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Intent;
import android.provider.Settings;
@@ -29,9 +30,17 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import java.time.format.DateTimeFormatter;
+
public class NightDisplayTile extends QSTileImpl<BooleanState>
implements ColorDisplayController.Callback {
+ /**
+ * Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the
+ * nearest hour and add on the AM/PM indicator.
+ */
+ private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "H a";
+
private ColorDisplayController mController;
private boolean mIsListening;
@@ -74,13 +83,49 @@ public class NightDisplayTile extends QSTileImpl<BooleanState>
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- final boolean isActivated = mController.isActivated();
- state.value = isActivated;
+ state.value = mController.isActivated();
state.label = state.contentDescription =
mContext.getString(R.string.quick_settings_night_display_label);
state.icon = ResourceIcon.get(R.drawable.ic_qs_night_display_on);
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.secondaryLabel = getSecondaryLabel(state.value);
+ }
+
+ /**
+ * Returns a {@link String} for the secondary label that reflects when the light will be turned
+ * on or off based on the current auto mode and night light activated status.
+ */
+ @Nullable
+ private String getSecondaryLabel(boolean isNightLightActivated) {
+ switch(mController.getAutoMode()) {
+ case ColorDisplayController.AUTO_MODE_TWILIGHT:
+ // Auto mode related to sunrise & sunset. If the light is on, it's guaranteed to be
+ // turned off at sunrise. If it's off, it's guaranteed to be turned on at sunset.
+ return isNightLightActivated
+ ? mContext.getString(
+ R.string.quick_settings_night_secondary_label_until_sunrise)
+ : mContext.getString(
+ R.string.quick_settings_night_secondary_label_on_at_sunset);
+
+ case ColorDisplayController.AUTO_MODE_CUSTOM:
+ // User-specified time, approximated to the nearest hour.
+ return isNightLightActivated
+ ? mContext.getString(
+ R.string.quick_settings_night_secondary_label_until,
+ mController.getCustomEndTime().format(
+ DateTimeFormatter.ofPattern(
+ APPROXIMATE_HOUR_DATE_TIME_PATTERN)))
+ : mContext.getString(
+ R.string.quick_settings_night_secondary_label_on_at,
+ mController.getCustomStartTime().format(
+ DateTimeFormatter.ofPattern(
+ APPROXIMATE_HOUR_DATE_TIME_PATTERN)));
+
+ default:
+ // No secondary label when auto mode is disabled.
+ return null;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index f41cb293aeb0..6857337e5c10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -39,7 +39,6 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
import android.graphics.drawable.Icon;
import android.media.AudioManager;
import android.net.Uri;
@@ -66,7 +65,6 @@ import com.android.systemui.UiOffloadThread;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
-import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
@@ -146,7 +144,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
private boolean mDockedStackExists;
private boolean mManagedProfileIconVisible = false;
- private boolean mManagedProfileInQuietMode = false;
private BluetoothController mBluetooth;
@@ -474,17 +471,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
}
}
- private void updateQuietState() {
- mManagedProfileInQuietMode = false;
- int currentUserId = ActivityManager.getCurrentUser();
- for (UserInfo ui : mUserManager.getEnabledProfiles(currentUserId)) {
- if (ui.isManagedProfile() && ui.isQuietModeEnabled()) {
- mManagedProfileInQuietMode = true;
- return;
- }
- }
- }
-
private void updateManagedProfile() {
// getLastResumedActivityUserId needds to acquire the AM lock, which may be contended in
// some cases. Since it doesn't really matter here whether it's updated in this frame
@@ -502,11 +488,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mIconController.setIcon(mSlotManagedProfile,
R.drawable.stat_sys_managed_profile_status,
mContext.getString(R.string.accessibility_managed_profile));
- } else if (mManagedProfileInQuietMode) {
- showIcon = true;
- mIconController.setIcon(mSlotManagedProfile,
- R.drawable.stat_sys_managed_profile_status_off,
- mContext.getString(R.string.accessibility_managed_profile));
} else {
showIcon = false;
}
@@ -676,7 +657,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
public void onUserSwitchComplete(int newUserId) throws RemoteException {
mHandler.post(() -> {
updateAlarm();
- updateQuietState();
updateManagedProfile();
updateForegroundInstantApps();
});
@@ -724,7 +704,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
if (mCurrentUserSetup == userSetup) return;
mCurrentUserSetup = userSetup;
updateAlarm();
- updateQuietState();
}
@Override
@@ -793,7 +772,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
} else if (action.equals(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) ||
action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) ||
action.equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)) {
- updateQuietState();
updateManagedProfile();
} else if (action.equals(AudioManager.ACTION_HEADSET_PLUG)) {
updateHeadsetPlug(intent);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
index 2951943404b3..2ede327ab698 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
@@ -74,17 +74,9 @@ public class DataSaverControllerImpl implements DataSaverController {
}
}
- private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+ private final INetworkPolicyListener mPolicyListener = new NetworkPolicyManager.Listener() {
@Override
- public void onUidRulesChanged(int uid, int uidRules) throws RemoteException {
- }
-
- @Override
- public void onMeteredIfacesChanged(String[] strings) throws RemoteException {
- }
-
- @Override
- public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException {
+ public void onRestrictBackgroundChanged(final boolean isDataSaving) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -92,10 +84,6 @@ public class DataSaverControllerImpl implements DataSaverController {
}
});
}
-
- @Override
- public void onUidPoliciesChanged(int uid, int uidPolicies) throws RemoteException {
- }
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 4d8da441c039..ebf4cda457e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.stack;
+import android.annotation.Nullable;
import android.content.Context;
import android.view.View;
@@ -236,6 +237,7 @@ public class AmbientState {
mShelf = shelf;
}
+ @Nullable
public NotificationShelf getShelf() {
return mShelf;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 7374f115a19b..2ce6df275588 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -122,7 +122,9 @@ public class StackScrollAlgorithm {
}
private void updateShelfState(StackScrollState resultState, AmbientState ambientState) {
NotificationShelf shelf = ambientState.getShelf();
- shelf.updateState(resultState, ambientState);
+ if (shelf != null) {
+ shelf.updateState(resultState, ambientState);
+ }
}
private void updateClipping(StackScrollState resultState,
@@ -495,6 +497,10 @@ public class StackScrollAlgorithm {
*/
private void clampPositionToShelf(ExpandableViewState childViewState,
AmbientState ambientState) {
+ if (ambientState.getShelf() == null) {
+ return;
+ }
+
int shelfStart = ambientState.getInnerHeight()
- ambientState.getShelf().getIntrinsicHeight();
childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
@@ -556,7 +562,8 @@ public class StackScrollAlgorithm {
} else if (i == 0 && ambientState.isAboveShelf(child)) {
// In case this is a new view that has never been measured before, we don't want to
// elevate if we are currently expanded more then the notification
- int shelfHeight = ambientState.getShelf().getIntrinsicHeight();
+ int shelfHeight = ambientState.getShelf() == null ? 0 :
+ ambientState.getShelf().getIntrinsicHeight();
float shelfStart = ambientState.getInnerHeight()
- shelfHeight + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
diff --git a/packages/VpnDialogs/res/values-hi/strings.xml b/packages/VpnDialogs/res/values-hi/strings.xml
index fc4fdcd93459..526541d26b08 100644
--- a/packages/VpnDialogs/res/values-hi/strings.xml
+++ b/packages/VpnDialogs/res/values-hi/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"कनेक्शन अनुरोध"</string>
- <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> VPN कनेक्‍शन सेट करना चाहता है जिससे वह नेटवर्क ट्रैफ़िक पर नज़र रख पाएगा. इसकी मंज़ूरी तभी दें जब आपको आप इसपर भरोसा हो. VPN चालू होने पर आपकी स्क्रीन के सबसे ऊपर &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; दिखाई देता है."</string>
+ <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> VPN कनेक्‍शन सेट अप करना चाहता है, जिससे वह नेटवर्क ट्रैफ़िक पर नज़र रख पाएगा. इसकी मंज़ूरी तभी दें जब आपको आप इस पर भरोसा हो. VPN चालू होने पर आपकी स्क्रीन के सबसे ऊपर &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; दिखाई देता है."</string>
<string name="legacy_title" msgid="192936250066580964">"VPN कनेक्‍ट है"</string>
<string name="session" msgid="6470628549473641030">"सत्र:"</string>
<string name="duration" msgid="3584782459928719435">"अवधि:"</string>
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index d817da53f523..7c6019e76416 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -193,6 +193,9 @@ message SystemMessage {
// Inform the user that Wifi Wake has automatically re-enabled Wifi
NOTE_WIFI_WAKE_TURNED_BACK_ON = 44;
+ // Inform the user that unexpectedly rapid network usage is happening
+ NOTE_NET_RAPID = 45;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index cac7fedd0b00..6d845f9a9d3a 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -44,6 +44,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -51,6 +52,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.service.autofill.AutofillFieldClassificationService.Scores;
import android.service.autofill.FillEventHistory;
import android.service.autofill.UserData;
import android.util.LocalLog;
@@ -78,6 +80,7 @@ import com.android.server.autofill.ui.AutoFillUI;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -443,6 +446,18 @@ public final class AutofillManagerService extends SystemService {
}
}
+ // Called by Shell command.
+ public void getScore(@Nullable String algorithmName, @NonNull String value1,
+ @NonNull String value2, @NonNull RemoteCallback callback) {
+ mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+
+ final FieldClassificationStrategy strategy =
+ new FieldClassificationStrategy(mContext, UserHandle.USER_CURRENT);
+
+ strategy.getScores(callback, algorithmName, null,
+ Arrays.asList(AutofillValue.forText(value1)), new String[] { value2 });
+ }
+
private void setDebugLocked(boolean debug) {
com.android.server.autofill.Helper.sDebug = debug;
android.view.autofill.Helper.sDebug = debug;
@@ -518,6 +533,8 @@ public final class AutofillManagerService extends SystemService {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.removeClientLocked(client);
+ } else if (sVerbose) {
+ Slog.v(TAG, "removeClient(): no service for " + userId);
}
}
}
@@ -574,6 +591,8 @@ public final class AutofillManagerService extends SystemService {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
return service.getFillEventHistory(getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "getFillEventHistory(): no service for " + userId);
}
}
@@ -588,6 +607,8 @@ public final class AutofillManagerService extends SystemService {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
return service.getUserData(getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "getUserData(): no service for " + userId);
}
}
@@ -602,6 +623,8 @@ public final class AutofillManagerService extends SystemService {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.setUserData(getCallingUid(), userData);
+ } else if (sVerbose) {
+ Slog.v(TAG, "setUserData(): no service for " + userId);
}
}
}
@@ -614,6 +637,8 @@ public final class AutofillManagerService extends SystemService {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
return service.isFieldClassificationEnabled(getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "isFieldClassificationEnabled(): no service for " + userId);
}
}
@@ -621,31 +646,39 @@ public final class AutofillManagerService extends SystemService {
}
@Override
- public String getDefaultFieldClassificationAlgorithm() throws RemoteException {
+ public void getDefaultFieldClassificationAlgorithm(RemoteCallback callback)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getDefaultFieldClassificationAlgorithm(getCallingUid());
+ service.getDefaultFieldClassificationAlgorithm(getCallingUid(), callback);
+ } else {
+ if (sVerbose) {
+ Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId);
+ }
+ callback.sendResult(null);
}
}
-
- return null;
}
@Override
- public List<String> getAvailableFieldClassificationAlgorithms() throws RemoteException {
+ public void getAvailableFieldClassificationAlgorithms(RemoteCallback callback)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getAvailableFieldClassificationAlgorithms(getCallingUid());
+ service.getAvailableFieldClassificationAlgorithms(getCallingUid(), callback);
+ } else {
+ if (sVerbose) {
+ Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId);
+ }
+ callback.sendResult(null);
}
}
-
- return null;
}
@Override
@@ -656,6 +689,8 @@ public final class AutofillManagerService extends SystemService {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
return service.getServiceComponentName();
+ } else if (sVerbose) {
+ Slog.v(TAG, "getAutofillServiceComponentName(): no service for " + userId);
}
}
@@ -665,15 +700,17 @@ public final class AutofillManagerService extends SystemService {
@Override
public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
throws RemoteException {
+ final int userId = UserHandle.getCallingUserId();
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
synchronized (mLock) {
- final AutofillManagerServiceImpl service = mServicesCache.get(
- UserHandle.getCallingUserId());
+ final AutofillManagerServiceImpl service = mServicesCache.get(userId);
if (service != null) {
return service.restoreSession(sessionId, getCallingUid(), activityToken,
appCallback);
+ } else if (sVerbose) {
+ Slog.v(TAG, "restoreSession(): no service for " + userId);
}
}
@@ -688,6 +725,8 @@ public final class AutofillManagerService extends SystemService {
if (service != null) {
service.updateSessionLocked(sessionId, getCallingUid(), autoFillId, bounds,
value, action, flags);
+ } else if (sVerbose) {
+ Slog.v(TAG, "updateSession(): no service for " + userId);
}
}
}
@@ -703,6 +742,8 @@ public final class AutofillManagerService extends SystemService {
if (service != null) {
restart = service.updateSessionLocked(sessionId, getCallingUid(), autoFillId,
bounds, value, action, flags);
+ } else if (sVerbose) {
+ Slog.v(TAG, "updateOrRestartSession(): no service for " + userId);
}
}
if (restart) {
@@ -720,6 +761,8 @@ public final class AutofillManagerService extends SystemService {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.finishSessionLocked(sessionId, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "finishSession(): no service for " + userId);
}
}
}
@@ -730,6 +773,8 @@ public final class AutofillManagerService extends SystemService {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.cancelSessionLocked(sessionId, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "cancelSession(): no service for " + userId);
}
}
}
@@ -740,6 +785,8 @@ public final class AutofillManagerService extends SystemService {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.disableOwnedAutofillServicesLocked(Binder.getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "cancelSession(): no service for " + userId);
}
}
}
@@ -755,8 +802,12 @@ public final class AutofillManagerService extends SystemService {
public boolean isServiceEnabled(int userId, String packageName) {
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service == null) return false;
- return Objects.equals(packageName, service.getServicePackageName());
+ if (service != null) {
+ return Objects.equals(packageName, service.getServicePackageName());
+ } else if (sVerbose) {
+ Slog.v(TAG, "isServiceEnabled(): no service for " + userId);
+ }
+ return false;
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index da74dba31416..a5bd59a9e77d 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -43,20 +43,15 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Parcelable.Creator;
-import android.os.RemoteCallback;
import android.provider.Settings;
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
-import android.service.autofill.Dataset;
-import android.service.autofill.EditDistanceScorer;
import android.service.autofill.FieldClassification;
import android.service.autofill.FieldClassification.Match;
import android.service.autofill.FillEventHistory;
@@ -69,8 +64,6 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.LocalLog;
-import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -89,7 +82,6 @@ import com.android.server.autofill.ui.AutoFillUI;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Random;
@@ -124,137 +116,7 @@ final class AutofillManagerServiceImpl {
private final LocalLog mRequestsHistory;
private final LocalLog mUiLatencyHistory;
-
- // TODO(b/70939974): temporary, will be moved to ExtServices
- static final class FieldClassificationAlgorithmService {
-
- static final String EXTRA_SCORES = "scores";
-
- /**
- * Gets the name of all available algorithms.
- */
- @NonNull
- public List<String> getAvailableAlgorithms() {
- return Arrays.asList(EditDistanceScorer.NAME);
- }
-
- /**
- * Gets the default algorithm that's used when an algorithm is not specified or is invalid.
- */
- @NonNull
- public String getDefaultAlgorithm() {
- return EditDistanceScorer.NAME;
- }
-
- /**
- * Gets the field classification scores.
- *
- * @param algorithmName algorithm to be used. If invalid, the default algorithm will be used
- * instead.
- * @param algorithmArgs optional arguments to be passed to the algorithm.
- * @param currentValues values entered by the user.
- * @param userValues values from the user data.
- * @param callback returns a nullable bundle with the parcelable results on
- * {@link #EXTRA_SCORES}.
- */
- @Nullable
- void getScores(@NonNull String algorithmName, @Nullable Bundle algorithmArgs,
- List<AutofillValue> currentValues, @NonNull String[] userValues,
- @NonNull RemoteCallback callback) {
- if (currentValues == null || userValues == null) {
- // TODO(b/70939974): use preconditions / add unit test
- throw new IllegalArgumentException("values cannot be null");
- }
- if (currentValues.isEmpty() || userValues.length == 0) {
- Slog.w(TAG, "getScores(): empty currentvalues (" + currentValues
- + ") or userValues (" + Arrays.toString(userValues) + ")");
- // TODO(b/70939974): add unit test
- callback.sendResult(null);
- }
- String actualAlgorithName = algorithmName;
- if (!EditDistanceScorer.NAME.equals(algorithmName)) {
- Slog.w(TAG, "Ignoring invalid algorithm (" + algorithmName + ") and using "
- + EditDistanceScorer.NAME + " instead");
- actualAlgorithName = EditDistanceScorer.NAME;
- }
- final int currentValuesSize = currentValues.size();
- if (sDebug) {
- Log.d(TAG, "getScores() will return a " + currentValuesSize + "x"
- + userValues.length + " matrix for " + actualAlgorithName);
- }
- final FieldClassificationScores scores = new FieldClassificationScores(
- actualAlgorithName, currentValuesSize, userValues.length);
- final EditDistanceScorer algorithm = EditDistanceScorer.getInstance();
- for (int i = 0; i < currentValuesSize; i++) {
- for (int j = 0; j < userValues.length; j++) {
- final float score = algorithm.getScore(currentValues.get(i), userValues[j]);
- scores.scores[i][j] = score;
- }
- }
- final Bundle result = new Bundle();
- result.putParcelable(EXTRA_SCORES, scores);
- callback.sendResult(result);
- }
- }
-
- // TODO(b/70939974): temporary, will be moved to ExtServices
- public static final class FieldClassificationScores implements Parcelable {
- public final String algorithmName;
- public final float[][] scores;
-
- public FieldClassificationScores(String algorithmName, int size1, int size2) {
- this.algorithmName = algorithmName;
- scores = new float[size1][size2];
- }
-
- public FieldClassificationScores(Parcel parcel) {
- algorithmName = parcel.readString();
- final int size1 = parcel.readInt();
- final int size2 = parcel.readInt();
- scores = new float[size1][size2];
- for (int i = 0; i < size1; i++) {
- for (int j = 0; j < size2; j++) {
- scores[i][j] = parcel.readFloat();
- }
- }
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeString(algorithmName);
- int size1 = scores.length;
- int size2 = scores[0].length;
- parcel.writeInt(size1);
- parcel.writeInt(size2);
- for (int i = 0; i < size1; i++) {
- for (int j = 0; j < size2; j++) {
- parcel.writeFloat(scores[i][j]);
- }
- }
- }
-
- public static final Creator<FieldClassificationScores> CREATOR = new Creator<FieldClassificationScores>() {
-
- @Override
- public FieldClassificationScores createFromParcel(Parcel parcel) {
- return new FieldClassificationScores(parcel);
- }
-
- @Override
- public FieldClassificationScores[] newArray(int size) {
- return new FieldClassificationScores[size];
- }
-
- };
- }
-
- private final FieldClassificationAlgorithmService mFcService =
- new FieldClassificationAlgorithmService();
+ private final FieldClassificationStrategy mFieldClassificationStrategy;
/**
* Apps disabled by the service; key is package name, value is when they will be enabled again.
@@ -324,6 +186,7 @@ final class AutofillManagerServiceImpl {
mUiLatencyHistory = uiLatencyHistory;
mUserId = userId;
mUi = ui;
+ mFieldClassificationStrategy = new FieldClassificationStrategy(context, userId);
updateLocked(disabled);
}
@@ -1089,10 +952,8 @@ final class AutofillManagerServiceImpl {
mUserData.dump(prefix2, pw);
}
- pw.print(prefix); pw.print("Available Field Classification algorithms: ");
- pw.println(mFcService.getAvailableAlgorithms());
- pw.print(prefix); pw.print("Default Field Classification algorithm: ");
- pw.println(mFcService.getDefaultAlgorithm());
+ pw.print(prefix); pw.println("Field Classification strategy: ");
+ mFieldClassificationStrategy.dump(prefix2, pw);
}
void destroySessionsLocked() {
@@ -1288,26 +1149,26 @@ final class AutofillManagerServiceImpl {
mUserId) == 1;
}
- FieldClassificationAlgorithmService getFieldClassificationService() {
- return mFcService;
+ FieldClassificationStrategy getFieldClassificationStrategy() {
+ return mFieldClassificationStrategy;
}
- List<String> getAvailableFieldClassificationAlgorithms(int callingUid) {
+ void getAvailableFieldClassificationAlgorithms(int callingUid, RemoteCallback callback) {
synchronized (mLock) {
if (!isCalledByServiceLocked("getFCAlgorithms()", callingUid)) {
- return null;
+ return;
}
}
- return mFcService.getAvailableAlgorithms();
+ mFieldClassificationStrategy.getAvailableAlgorithms(callback);
}
- String getDefaultFieldClassificationAlgorithm(int callingUid) {
+ void getDefaultFieldClassificationAlgorithm(int callingUid, RemoteCallback callback) {
synchronized (mLock) {
if (!isCalledByServiceLocked("getDefaultFCAlgorithm()", callingUid)) {
- return null;
+ return;
}
}
- return mFcService.getDefaultAlgorithm();
+ mFieldClassificationStrategy.getDefaultAlgorithm(callback);
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index f3de557ec540..44560879f028 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -16,11 +16,15 @@
package com.android.server.autofill;
+import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
+
import static com.android.server.autofill.AutofillManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS;
import android.os.Bundle;
+import android.os.RemoteCallback;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.service.autofill.AutofillFieldClassificationService.Scores;
import android.view.autofill.AutofillManager;
import com.android.internal.os.IResultReceiver;
@@ -80,13 +84,16 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand {
pw.println(" Sets the maximum number of partitions per session.");
pw.println("");
pw.println(" list sessions [--user USER_ID]");
- pw.println(" List all pending sessions.");
+ pw.println(" Lists all pending sessions.");
pw.println("");
pw.println(" destroy sessions [--user USER_ID]");
- pw.println(" Destroy all pending sessions.");
+ pw.println(" Destroys all pending sessions.");
pw.println("");
pw.println(" reset");
- pw.println(" Reset all pending sessions and cached service connections.");
+ pw.println(" Resets all pending sessions and cached service connections.");
+ pw.println("");
+ pw.println(" get fc_score [--algorithm ALGORITHM] value1 value2");
+ pw.println(" Gets the field classification score for 2 fields.");
pw.println("");
}
}
@@ -98,6 +105,8 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand {
return getLogLevel(pw);
case "max_partitions":
return getMaxPartitions(pw);
+ case "fc_score":
+ return getFieldClassificationScore(pw);
default:
pw.println("Invalid set: " + what);
return -1;
@@ -164,6 +173,35 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand {
return 0;
}
+ private int getFieldClassificationScore(PrintWriter pw) {
+ final String nextArg = getNextArgRequired();
+ final String algorithm, value1;
+ if ("--algorithm".equals(nextArg)) {
+ algorithm = getNextArgRequired();
+ value1 = getNextArgRequired();
+ } else {
+ algorithm = null;
+ value1 = nextArg;
+ }
+ final String value2 = getNextArgRequired();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ mService.getScore(algorithm, value1, value2, new RemoteCallback((result) -> {
+ final Scores scores = result.getParcelable(EXTRA_SCORES);
+ if (scores == null) {
+ pw.println("no score");
+ } else {
+ pw.print("algorithm: ");
+ pw.print(scores.getAlgorithm());
+ pw.print(" score: ");
+ pw.println(scores.getScores()[0][0]);
+ }
+ latch.countDown();
+ }));
+
+ return waitForLatch(pw, latch);
+ }
+
private int requestDestroy(PrintWriter pw) {
if (!isNextArgSessions(pw)) {
return -1;
@@ -210,19 +248,13 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand {
return true;
}
- private boolean isNextArgLogLevel(PrintWriter pw, String cmd) {
- final String type = getNextArgRequired();
- if (!type.equals("log_level")) {
- pw.println("Error: invalid " + cmd + " type: " + type);
- return false;
- }
- return true;
- }
-
private int requestSessionCommon(PrintWriter pw, CountDownLatch latch,
Runnable command) {
command.run();
+ return waitForLatch(pw, latch);
+ }
+ private int waitForLatch(PrintWriter pw, CountDownLatch latch) {
try {
final boolean received = latch.await(5, TimeUnit.SECONDS);
if (!received) {
diff --git a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
new file mode 100644
index 000000000000..7228f1d2a8ea
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
@@ -0,0 +1,279 @@
+/*
+ * 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.autofill;
+
+import static android.view.autofill.AutofillManager.EXTRA_AVAILABLE_ALGORITHMS;
+import static android.view.autofill.AutofillManager.EXTRA_DEFAULT_ALGORITHM;
+import static android.view.autofill.AutofillManager.FC_SERVICE_TIMEOUT;
+
+import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.Manifest;
+import android.annotation.MainThread;
+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.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.autofill.AutofillFieldClassificationService;
+import android.service.autofill.IAutofillFieldClassificationService;
+import android.util.Log;
+import android.util.Slog;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Strategy used to bridge the field classification algorithms provided by a service in an external
+ * package.
+ */
+//TODO(b/70291841): add unit tests ?
+final class FieldClassificationStrategy {
+
+ private static final String TAG = "FieldClassificationStrategy";
+
+ private final Context mContext;
+ private final Object mLock = new Object();
+ private final int mUserId;
+
+ @GuardedBy("mLock")
+ private ServiceConnection mServiceConnection;
+
+ @GuardedBy("mLock")
+ private IAutofillFieldClassificationService mRemoteService;
+
+ @GuardedBy("mLock")
+ private ArrayList<Command> mQueuedCommands;
+
+ public FieldClassificationStrategy(Context context, int userId) {
+ mContext = context;
+ mUserId = userId;
+ }
+
+ private ComponentName getServiceComponentName() {
+ final String packageName =
+ mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+ if (packageName == null) {
+ Slog.w(TAG, "no external services package!");
+ return null;
+ }
+
+ final Intent intent = new Intent(AutofillFieldClassificationService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+
+ if (!Manifest.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE
+ .equals(serviceInfo.permission)) {
+ Slog.w(TAG, name.flattenToShortString() + " does not require permission "
+ + Manifest.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE);
+ return null;
+ }
+
+ if (sVerbose) Slog.v(TAG, "getServiceComponentName(): " + name);
+ return name;
+ }
+
+ /**
+ * Run a command, starting the service connection if necessary.
+ */
+ private void connectAndRun(@NonNull Command command) {
+ synchronized (mLock) {
+ if (mRemoteService != null) {
+ try {
+ if (sVerbose) Slog.v(TAG, "running command right away");
+ command.run(mRemoteService);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "exception calling service: " + e);
+ }
+ return;
+ } else {
+ if (sDebug) Slog.d(TAG, "service is null; queuing command");
+ if (mQueuedCommands == null) {
+ mQueuedCommands = new ArrayList<>(1);
+ }
+ mQueuedCommands.add(command);
+ // If we're already connected, don't create a new connection, just leave - the
+ // command will be run when the service connects
+ if (mServiceConnection != null) return;
+ }
+
+ if (sVerbose) Slog.v(TAG, "creating connection");
+
+ // Create the connection
+ mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (sVerbose) Slog.v(TAG, "onServiceConnected(): " + name);
+ synchronized (mLock) {
+ mRemoteService = IAutofillFieldClassificationService.Stub
+ .asInterface(service);
+ if (mQueuedCommands != null) {
+ final int size = mQueuedCommands.size();
+ if (sDebug) Slog.d(TAG, "running " + size + " queued commands");
+ for (int i = 0; i < size; i++) {
+ final Command queuedCommand = mQueuedCommands.get(i);
+ try {
+ if (sVerbose) Slog.v(TAG, "running queued command #" + i);
+ queuedCommand.run(mRemoteService);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "exception calling " + name + ": " + e);
+ }
+ }
+ mQueuedCommands = null;
+ } else if (sDebug) Slog.d(TAG, "no queued commands");
+ }
+ }
+
+ @Override
+ @MainThread
+ public void onServiceDisconnected(ComponentName name) {
+ if (sVerbose) Slog.v(TAG, "onServiceDisconnected(): " + name);
+ synchronized (mLock) {
+ mRemoteService = null;
+ }
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ if (sVerbose) Slog.v(TAG, "onBindingDied(): " + name);
+ synchronized (mLock) {
+ mRemoteService = null;
+ }
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ if (sVerbose) Slog.v(TAG, "onNullBinding(): " + name);
+ synchronized (mLock) {
+ mRemoteService = null;
+ }
+ }
+ };
+
+ final ComponentName component = getServiceComponentName();
+ if (sVerbose) Slog.v(TAG, "binding to: " + component);
+ if (component != null) {
+ final Intent intent = new Intent();
+ intent.setComponent(component);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE,
+ UserHandle.of(mUserId));
+ if (sVerbose) Slog.v(TAG, "bound");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ }
+
+ void getAvailableAlgorithms(RemoteCallback callback) {
+ connectAndRun((service) -> service.getAvailableAlgorithms(callback));
+ }
+
+ void getDefaultAlgorithm(RemoteCallback callback) {
+ connectAndRun((service) -> service.getDefaultAlgorithm(callback));
+ }
+
+ //TODO(b/70291841): rename this method (and all others in the chain) to something like
+ // calculateScores() ?
+ void getScores(RemoteCallback callback, @Nullable String algorithmName,
+ @Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues,
+ @NonNull String[] userDataValues) {
+ connectAndRun((service) -> service.getScores(callback, algorithmName,
+ algorithmArgs, actualValues, userDataValues));
+ }
+
+ void dump(String prefix, PrintWriter pw) {
+ final ComponentName impl = getServiceComponentName();
+ pw.print(prefix); pw.print("User ID: "); pw.println(mUserId);
+ pw.print(prefix); pw.print("Queued commands: ");
+ if (mQueuedCommands == null) {
+ pw.println("N/A");
+ } else {
+ pw.println(mQueuedCommands.size());
+ }
+ pw.print(prefix); pw.print("Implementation: ");
+ if (impl == null) {
+ pw.println("N/A");
+ return;
+ }
+ pw.println(impl.flattenToShortString());
+
+ final CountDownLatch latch = new CountDownLatch(2);
+
+ // Lock used to make sure lines don't overlap
+ final Object lock = latch;
+
+ connectAndRun((service) -> service.getAvailableAlgorithms(new RemoteCallback((bundle) -> {
+ synchronized (lock) {
+ pw.print(prefix); pw.print("Available algorithms: ");
+ pw.println(bundle.getStringArrayList(EXTRA_AVAILABLE_ALGORITHMS));
+ }
+ latch.countDown();
+ })));
+
+ connectAndRun((service) -> service.getDefaultAlgorithm(new RemoteCallback((bundle) -> {
+ synchronized (lock) {
+ pw.print(prefix); pw.print("Default algorithm: ");
+ pw.println(bundle.getString(EXTRA_DEFAULT_ALGORITHM));
+ }
+ latch.countDown();
+ })));
+
+ try {
+ if (!latch.await(FC_SERVICE_TIMEOUT, TimeUnit.MILLISECONDS)) {
+ synchronized (lock) {
+ pw.print(prefix); pw.print("timeout ("); pw.print(FC_SERVICE_TIMEOUT);
+ pw.println("ms) waiting for service");
+ }
+ }
+ } catch (InterruptedException e) {
+ synchronized (lock) {
+ pw.print(prefix); pw.println("interrupted while waiting for service");
+ }
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ private interface Command {
+ void run(IAutofillFieldClassificationService service) throws RemoteException;
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f5d1336a0f6e..a0e23a152224 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -18,6 +18,7 @@ package com.android.server.autofill;
import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
@@ -25,7 +26,6 @@ import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
-import static com.android.server.autofill.AutofillManagerServiceImpl.FieldClassificationAlgorithmService.EXTRA_SCORES;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sPartitionMaxCount;
import static com.android.server.autofill.Helper.sVerbose;
@@ -54,11 +54,11 @@ import android.os.Parcelable;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.service.autofill.AutofillFieldClassificationService.Scores;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
import android.service.autofill.FieldClassification;
import android.service.autofill.FieldClassification.Match;
-import android.service.carrier.CarrierMessagingService.ResultCallback;
import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
@@ -86,8 +86,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.HandlerCaller;
import com.android.internal.util.ArrayUtils;
-import com.android.server.autofill.AutofillManagerServiceImpl.FieldClassificationAlgorithmService;
-import com.android.server.autofill.AutofillManagerServiceImpl.FieldClassificationScores;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.autofill.ui.PendingUi;
@@ -99,7 +97,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
/**
* A session for a given activity.
@@ -1101,10 +1098,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
// Sets field classification scores
- final FieldClassificationAlgorithmService fcService =
- mService.getFieldClassificationService();
- if (userData != null && fcService != null) {
- logFieldClassificationScoreLocked(fcService, ignoredDatasets, changedFieldIds,
+ final FieldClassificationStrategy fcStrategy = mService.getFieldClassificationStrategy();
+ if (userData != null && fcStrategy != null) {
+ logFieldClassificationScoreLocked(fcStrategy, ignoredDatasets, changedFieldIds,
changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds,
manuallyFilledIds, userData,
mViewStates.values());
@@ -1121,7 +1117,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* {@code fieldId} based on its {@code currentValue} and {@code userData}.
*/
private void logFieldClassificationScoreLocked(
- @NonNull AutofillManagerServiceImpl.FieldClassificationAlgorithmService fcService,
+ @NonNull FieldClassificationStrategy fcStrategy,
@NonNull ArraySet<String> ignoredDatasets,
@NonNull ArrayList<AutofillId> changedFieldIds,
@NonNull ArrayList<String> changedDatasetIds,
@@ -1161,6 +1157,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
fieldIds[k++] = viewState.id;
}
+ // Then use the results, asynchronously
final RemoteCallback callback = new RemoteCallback((result) -> {
if (result == null) {
if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
@@ -1170,35 +1167,46 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mComponentName.getPackageName());
return;
}
- final FieldClassificationScores matrix = result.getParcelable(EXTRA_SCORES);
-
- // Then use the results.
- for (int i = 0; i < viewsSize; i++) {
- final AutofillId fieldId = fieldIds[i];
+ final Scores scores = result.getParcelable(EXTRA_SCORES);
+ if (scores == null) {
+ Slog.w(TAG, "No field classification score on " + result);
+ return;
+ }
+ final float[][] scoresMatrix = scores.getScores();
- ArrayList<Match> matches = null;
- for (int j = 0; j < userValues.length; j++) {
- String remoteId = remoteIds[j];
- final String actualAlgorithm = matrix.algorithmName;
- final float score = matrix.scores[i][j];
- if (score > 0) {
- if (sVerbose) {
- Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
- + fieldId);
+ int i = 0, j = 0;
+ try {
+ for (i = 0; i < viewsSize; i++) {
+ final AutofillId fieldId = fieldIds[i];
+
+ ArrayList<Match> matches = null;
+ for (j = 0; j < userValues.length; j++) {
+ String remoteId = remoteIds[j];
+ final String actualAlgorithm = scores.getAlgorithm();
+ final float score = scoresMatrix[i][j];
+ if (score > 0) {
+ if (sVerbose) {
+ Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
+ + fieldId);
+ }
+ if (matches == null) {
+ matches = new ArrayList<>(userValues.length);
+ }
+ matches.add(new Match(remoteId, score, actualAlgorithm));
}
- if (matches == null) {
- matches = new ArrayList<>(userValues.length);
+ else if (sVerbose) {
+ Slog.v(TAG, "skipping score 0 at index " + j + " and id " + fieldId);
}
- matches.add(new Match(remoteId, score, actualAlgorithm));
}
- else if (sVerbose) {
- Slog.v(TAG, "skipping score 0 at index " + j + " and id " + fieldId);
+ if (matches != null) {
+ detectedFieldIds.add(fieldId);
+ detectedFieldClassifications.add(new FieldClassification(matches));
}
}
- if (matches != null) {
- detectedFieldIds.add(fieldId);
- detectedFieldClassifications.add(new FieldClassification(matches));
- }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ Slog.wtf(TAG, "Error accessing FC score at " + i + " x " + j + ": "
+ + Arrays.toString(scoresMatrix), e);
+ return;
}
mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
@@ -1207,7 +1215,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mComponentName.getPackageName());
});
- fcService.getScores(algorithm, algorithmArgs, currentValues, userValues, callback);
+ fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues);
}
/**
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 30fd25a92484..5b901ee2b3da 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -62,9 +62,17 @@ public class TransportManager {
private final PackageManager mPackageManager;
private final Set<ComponentName> mTransportWhitelist;
private final TransportClientManager mTransportClientManager;
- private final Object mTransportLock = new Object();
private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {};
+ /**
+ * Lock for registered transports and currently selected transport.
+ *
+ * <p><b>Warning:</b> No calls to {@link IBackupTransport} or calls that result in transport
+ * code being executed such as {@link TransportClient#connect(String)}} and its variants should
+ * be made with this lock held, risk of deadlock.
+ */
+ private final Object mTransportLock = new Object();
+
/** @see #getRegisteredTransportNames() */
@GuardedBy("mTransportLock")
private final Map<ComponentName, TransportDescription> mRegisteredTransportsDescriptionMap =
@@ -109,15 +117,16 @@ public class TransportManager {
@WorkerThread
void onPackageChanged(String packageName, String... components) {
+ // Unfortunately this can't be atomic because we risk a deadlock if
+ // registerTransportsFromPackage() is put inside the synchronized block
+ Set<ComponentName> transportComponents =
+ Stream.of(components)
+ .map(component -> new ComponentName(packageName, component))
+ .collect(Collectors.toSet());
synchronized (mTransportLock) {
- Set<ComponentName> transportComponents =
- Stream.of(components)
- .map(component -> new ComponentName(packageName, component))
- .collect(Collectors.toSet());
-
mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains);
- registerTransportsFromPackage(packageName, transportComponents::contains);
}
+ registerTransportsFromPackage(packageName, transportComponents::contains);
}
/**
@@ -263,6 +272,9 @@ public class TransportManager {
* This is called with an internal lock held, ensuring that the transport will remain registered
* while {@code transportConsumer} is being executed. Don't do heavy operations in {@code
* transportConsumer}.
+ *
+ * <p><b>Warning:</b> Do NOT make any calls to {@link IBackupTransport} or call any variants of
+ * {@link TransportClient#connect(String)} here, otherwise you risk deadlock.
*/
public void forEachRegisteredTransport(Consumer<String> transportConsumer) {
synchronized (mTransportLock) {
@@ -465,20 +477,27 @@ public class TransportManager {
*/
@WorkerThread
public int registerAndSelectTransport(ComponentName transportComponent) {
+ // If it's already registered we select and return
synchronized (mTransportLock) {
- if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) {
- int result = registerTransport(transportComponent);
- if (result != BackupManager.SUCCESS) {
- return result;
- }
+ try {
+ selectTransport(getTransportName(transportComponent));
+ return BackupManager.SUCCESS;
+ } catch (TransportNotRegisteredException e) {
+ // Fall through and release lock
}
+ }
+ // We can't call registerTransport() with the transport lock held
+ int result = registerTransport(transportComponent);
+ if (result != BackupManager.SUCCESS) {
+ return result;
+ }
+ synchronized (mTransportLock) {
try {
selectTransport(getTransportName(transportComponent));
return BackupManager.SUCCESS;
} catch (TransportNotRegisteredException e) {
- // Shouldn't happen because we are holding the lock
- Slog.wtf(TAG, "Transport unexpectedly not registered");
+ Slog.wtf(TAG, "Transport got unregistered");
return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
}
}
@@ -512,13 +531,11 @@ public class TransportManager {
if (hosts == null) {
return;
}
- synchronized (mTransportLock) {
- for (ResolveInfo host : hosts) {
- ComponentName transportComponent = host.serviceInfo.getComponentName();
- if (transportComponentFilter.test(transportComponent)
- && isTransportTrusted(transportComponent)) {
- registerTransport(transportComponent);
- }
+ for (ResolveInfo host : hosts) {
+ ComponentName transportComponent = host.serviceInfo.getComponentName();
+ if (transportComponentFilter.test(transportComponent)
+ && isTransportTrusted(transportComponent)) {
+ registerTransport(transportComponent);
}
}
}
@@ -547,22 +564,24 @@ public class TransportManager {
/**
* Tries to register transport represented by {@code transportComponent}.
*
+ * <p><b>Warning:</b> Don't call this with the transport lock held.
+ *
* @param transportComponent Host of the transport that we want to register.
* @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
* or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
*/
@WorkerThread
private int registerTransport(ComponentName transportComponent) {
+ checkCanUseTransport();
+
if (!isTransportTrusted(transportComponent)) {
return BackupManager.ERROR_TRANSPORT_INVALID;
}
String transportString = transportComponent.flattenToShortString();
-
String callerLogString = "TransportManager.registerTransport()";
TransportClient transportClient =
mTransportClientManager.getTransportClient(transportComponent, callerLogString);
-
final IBackupTransport transport;
try {
transport = transportClient.connectOrThrow(callerLogString);
@@ -593,20 +612,26 @@ public class TransportManager {
/** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
throws RemoteException {
+ checkCanUseTransport();
+
+ TransportDescription description =
+ new TransportDescription(
+ transport.name(),
+ transport.transportDirName(),
+ transport.configurationIntent(),
+ transport.currentDestinationString(),
+ transport.dataManagementIntent(),
+ transport.dataManagementLabel());
synchronized (mTransportLock) {
- String name = transport.name();
- TransportDescription description =
- new TransportDescription(
- name,
- transport.transportDirName(),
- transport.configurationIntent(),
- transport.currentDestinationString(),
- transport.dataManagementIntent(),
- transport.dataManagementLabel());
mRegisteredTransportsDescriptionMap.put(transportComponent, description);
}
}
+ private void checkCanUseTransport() {
+ Preconditions.checkState(
+ !Thread.holdsLock(mTransportLock), "Can't call transport with transport lock held");
+ }
+
private static Predicate<ComponentName> fromPackageFilter(String packageName) {
return transportComponent -> packageName.equals(transportComponent.getPackageName());
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 04d292fa1ae4..dc5f5a270748 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -383,16 +383,16 @@ public final class BatteryService extends SystemService {
}
}
- private void update(HealthInfo info) {
+ private void update(android.hardware.health.V2_0.HealthInfo info) {
traceBegin("HealthInfoUpdate");
synchronized (mLock) {
if (!mUpdatesStopped) {
- mHealthInfo = info;
+ mHealthInfo = info.legacy;
// Process the new values.
processValuesLocked(false);
mLock.notifyAll(); // for any waiters on new info
} else {
- copy(mLastHealthInfo, info);
+ copy(mLastHealthInfo, info.legacy);
}
}
traceEnd();
@@ -1010,7 +1010,7 @@ public final class BatteryService extends SystemService {
private final class HealthHalCallback extends IHealthInfoCallback.Stub
implements HealthServiceWrapper.Callback {
- @Override public void healthInfoChanged(HealthInfo props) {
+ @Override public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) {
BatteryService.this.update(props);
}
// on new service registered
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 337406d58f9d..20777901a3aa 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -439,10 +439,17 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
}
+ private boolean supportBluetoothPersistedState() {
+ return mContext.getResources().getBoolean(R.bool.config_supportBluetoothPersistedState);
+ }
+
/**
* Returns true if the Bluetooth saved state is "on"
*/
private boolean isBluetoothPersistedStateOn() {
+ if (!supportBluetoothPersistedState()) {
+ return false;
+ }
int state = Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON, -1);
if (DBG) {
Slog.d(TAG, "Bluetooth persisted state: " + state);
@@ -454,6 +461,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
* Returns true if the Bluetooth saved state is BLUETOOTH_ON_BLUETOOTH
*/
private boolean isBluetoothPersistedStateOnBluetooth() {
+ if (!supportBluetoothPersistedState()) {
+ return false;
+ }
return Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON,
BLUETOOTH_ON_BLUETOOTH) == BLUETOOTH_ON_BLUETOOTH;
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 9574545682c1..409770793fbb 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -63,6 +63,7 @@ import android.net.NetworkConfig;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkMisc;
+import android.net.NetworkPolicyManager;
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
@@ -1501,15 +1502,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
return true;
}
- private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+ private final INetworkPolicyListener mPolicyListener = new NetworkPolicyManager.Listener() {
@Override
public void onUidRulesChanged(int uid, int uidRules) {
// TODO: notify UID when it has requested targeted updates
}
@Override
- public void onMeteredIfacesChanged(String[] meteredIfaces) {
- }
- @Override
public void onRestrictBackgroundChanged(boolean restrictBackground) {
// TODO: relocate this specific callback in Tethering.
if (restrictBackground) {
@@ -1517,9 +1515,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
mTethering.untetherAll();
}
}
- @Override
- public void onUidPoliciesChanged(int uid, int uidPolicies) {
- }
};
/**
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 45516115b629..a75a3675f7f9 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -26,8 +26,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -91,9 +89,6 @@ public class ForceAppStandbyTracker {
private final MyHandler mHandler;
- @VisibleForTesting
- FeatureFlagsObserver mFlagsObserver;
-
/**
* Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed.
*/
@@ -119,32 +114,13 @@ public class ForceAppStandbyTracker {
boolean mStarted;
@GuardedBy("mLock")
- boolean mIsCharging;
+ boolean mForceAllAppsStandby; // True if device is in extreme battery saver mode
@GuardedBy("mLock")
- boolean mBatterySaverEnabled;
+ boolean mForcedAppStandbyEnabled; // True if the forced app standby feature is enabled
- /**
- * True if the forced app standby is currently enabled
- */
- @GuardedBy("mLock")
- boolean mForceAllAppsStandby;
-
- /**
- * True if the forced app standby for small battery devices feature is enabled in settings
- */
- @GuardedBy("mLock")
- boolean mForceAllAppStandbyForSmallBattery;
-
- /**
- * True if the forced app standby feature is enabled in settings
- */
- @GuardedBy("mLock")
- boolean mForcedAppStandbyEnabled;
-
- @VisibleForTesting
- class FeatureFlagsObserver extends ContentObserver {
- FeatureFlagsObserver() {
+ private class FeatureFlagObserver extends ContentObserver {
+ FeatureFlagObserver() {
super(null);
}
@@ -152,9 +128,6 @@ public class ForceAppStandbyTracker {
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED),
false, this);
-
- mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED), false, this);
}
boolean isForcedAppStandbyEnabled() {
@@ -162,43 +135,20 @@ public class ForceAppStandbyTracker {
Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1;
}
- boolean isForcedAppStandbyForSmallBatteryEnabled() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 0) == 1;
- }
-
@Override
- public void onChange(boolean selfChange, Uri uri) {
- if (Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED).equals(uri)) {
- final boolean enabled = isForcedAppStandbyEnabled();
- synchronized (mLock) {
- if (mForcedAppStandbyEnabled == enabled) {
- return;
- }
- mForcedAppStandbyEnabled = enabled;
- if (DEBUG) {
- Slog.d(TAG,
- "Forced app standby feature flag changed: " + mForcedAppStandbyEnabled);
- }
+ public void onChange(boolean selfChange) {
+ final boolean enabled = isForcedAppStandbyEnabled();
+ synchronized (mLock) {
+ if (mForcedAppStandbyEnabled == enabled) {
+ return;
}
- mHandler.notifyForcedAppStandbyFeatureFlagChanged();
- } else if (Settings.Global.getUriFor(
- Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED).equals(uri)) {
- final boolean enabled = isForcedAppStandbyForSmallBatteryEnabled();
- synchronized (mLock) {
- if (mForceAllAppStandbyForSmallBattery == enabled) {
- return;
- }
- mForceAllAppStandbyForSmallBattery = enabled;
- if (DEBUG) {
- Slog.d(TAG, "Forced app standby for small battery feature flag changed: "
- + mForceAllAppStandbyForSmallBattery);
- }
- updateForceAllAppStandbyState();
+ mForcedAppStandbyEnabled = enabled;
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Forced app standby feature flag changed: " + mForcedAppStandbyEnabled);
}
- } else {
- Slog.w(TAG, "Unexpected feature flag uri encountered: " + uri);
}
+ mHandler.notifyFeatureFlagChanged();
}
}
@@ -339,11 +289,9 @@ public class ForceAppStandbyTracker {
mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
- mFlagsObserver = new FeatureFlagsObserver();
- mFlagsObserver.register();
- mForcedAppStandbyEnabled = mFlagsObserver.isForcedAppStandbyEnabled();
- mForceAllAppStandbyForSmallBattery =
- mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled();
+ final FeatureFlagObserver flagObserver = new FeatureFlagObserver();
+ flagObserver.register();
+ mForcedAppStandbyEnabled = flagObserver.isForcedAppStandbyEnabled();
try {
mIActivityManager.registerUidObserver(new UidObserver(),
@@ -358,24 +306,16 @@ public class ForceAppStandbyTracker {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_REMOVED);
- filter.addAction(Intent.ACTION_BATTERY_CHANGED);
mContext.registerReceiver(new MyReceiver(), filter);
refreshForcedAppStandbyUidPackagesLocked();
mPowerManagerInternal.registerLowPowerModeObserver(
ServiceType.FORCE_ALL_APPS_STANDBY,
- (state) -> {
- synchronized (mLock) {
- mBatterySaverEnabled = state.batterySaverEnabled;
- updateForceAllAppStandbyState();
- }
- });
+ (state) -> updateForceAllAppsStandby(state.batterySaverEnabled));
- mBatterySaverEnabled = mPowerManagerInternal.getLowPowerState(
- ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled;
-
- updateForceAllAppStandbyState();
+ updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState(
+ ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled);
}
}
@@ -400,11 +340,6 @@ public class ForceAppStandbyTracker {
return LocalServices.getService(PowerManagerInternal.class);
}
- @VisibleForTesting
- boolean isSmallBatteryDevice() {
- return ActivityManager.isSmallBatteryDevice();
- }
-
/**
* Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
*/
@@ -434,29 +369,18 @@ public class ForceAppStandbyTracker {
}
}
- private void updateForceAllAppStandbyState() {
- synchronized (mLock) {
- if (mIsCharging) {
- toggleForceAllAppsStandbyLocked(false);
- } else if (mForceAllAppStandbyForSmallBattery
- && isSmallBatteryDevice()) {
- toggleForceAllAppsStandbyLocked(true);
- } else {
- toggleForceAllAppsStandbyLocked(mBatterySaverEnabled);
- }
- }
- }
-
/**
* Update {@link #mForceAllAppsStandby} and notifies the listeners.
*/
- private void toggleForceAllAppsStandbyLocked(boolean enable) {
- if (enable == mForceAllAppsStandby) {
- return;
- }
- mForceAllAppsStandby = enable;
+ void updateForceAllAppsStandby(boolean enable) {
+ synchronized (mLock) {
+ if (enable == mForceAllAppsStandby) {
+ return;
+ }
+ mForceAllAppsStandby = enable;
- mHandler.notifyForceAllAppsStandbyChanged();
+ mHandler.notifyForceAllAppsStandbyChanged();
+ }
}
private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
@@ -591,13 +515,6 @@ public class ForceAppStandbyTracker {
if (userId > 0) {
mHandler.doUserRemoved(userId);
}
- } else if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
- int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
- synchronized (mLock) {
- mIsCharging = (status == BatteryManager.BATTERY_STATUS_CHARGING
- || status == BatteryManager.BATTERY_STATUS_FULL);
- }
- updateForceAllAppStandbyState();
}
}
}
@@ -616,7 +533,7 @@ public class ForceAppStandbyTracker {
private static final int MSG_TEMP_WHITELIST_CHANGED = 5;
private static final int MSG_FORCE_ALL_CHANGED = 6;
private static final int MSG_USER_REMOVED = 7;
- private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8;
+ private static final int MSG_FEATURE_FLAG_CHANGED = 8;
public MyHandler(Looper looper) {
super(looper);
@@ -646,8 +563,8 @@ public class ForceAppStandbyTracker {
obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
}
- public void notifyForcedAppStandbyFeatureFlagChanged() {
- obtainMessage(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED).sendToTarget();
+ public void notifyFeatureFlagChanged() {
+ obtainMessage(MSG_FEATURE_FLAG_CHANGED).sendToTarget();
}
public void doUserRemoved(int userId) {
@@ -701,7 +618,7 @@ public class ForceAppStandbyTracker {
l.onForceAllAppsStandbyChanged(sender);
}
return;
- case MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED:
+ case MSG_FEATURE_FLAG_CHANGED:
// Feature flag for forced app standby changed.
final boolean unblockAlarms;
synchronized (mLock) {
@@ -925,18 +842,6 @@ public class ForceAppStandbyTracker {
pw.println(isForceAllAppsStandbyEnabled());
pw.print(indent);
- pw.print("Small Battery Device: ");
- pw.println(isSmallBatteryDevice());
-
- pw.print(indent);
- pw.print("Force all apps standby for small battery device: ");
- pw.println(mForceAllAppStandbyForSmallBattery);
-
- pw.print(indent);
- pw.print("Charging: ");
- pw.println(mIsCharging);
-
- pw.print(indent);
pw.print("Foreground uids: [");
String sep = "";
@@ -975,11 +880,6 @@ public class ForceAppStandbyTracker {
final long token = proto.start(fieldId);
proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby);
- proto.write(ForceAppStandbyTrackerProto.IS_SMALL_BATTERY_DEVICE,
- isSmallBatteryDevice());
- proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY_FOR_SMALL_BATTERY,
- mForceAllAppStandbyForSmallBattery);
- proto.write(ForceAppStandbyTrackerProto.IS_CHARGING, mIsCharging);
for (int i = 0; i < mForegroundUids.size(); i++) {
if (mForegroundUids.valueAt(i)) {
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 55046959e4dd..21137adce69a 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -51,6 +51,7 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.Manifest;
+import android.annotation.AnyThread;
import android.annotation.BinderThread;
import android.annotation.ColorInt;
import android.annotation.IntDef;
@@ -110,6 +111,7 @@ import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -256,6 +258,44 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private static final String ACTION_SHOW_INPUT_METHOD_PICKER =
"com.android.server.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER";
+ /**
+ * Debug flag for overriding runtime {@link SystemProperties}.
+ */
+ @AnyThread
+ private static final class DebugFlag {
+ private static final Object LOCK = new Object();
+ private final String mKey;
+ @GuardedBy("LOCK")
+ private boolean mValue;
+
+ public DebugFlag(String key) {
+ mKey = key;
+ refresh();
+ }
+
+ void refresh() {
+ synchronized (LOCK) {
+ mValue = SystemProperties.getBoolean(mKey, true);
+ }
+ }
+
+ boolean value() {
+ synchronized (LOCK) {
+ return mValue;
+ }
+ }
+ }
+
+ /**
+ * Debug flags that can be overridden using "adb shell setprop <key>"
+ * Note: These flags are cached. To refresh, run "adb shell ime refresh_debug_properties".
+ */
+ private static final class DebugFlags {
+ static final DebugFlag FLAG_OPTIMIZE_START_INPUT =
+ new DebugFlag("debug.optimize_startinput");
+ }
+
+
final Context mContext;
final Resources mRes;
final Handler mHandler;
@@ -2930,8 +2970,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
if (!didStart && attribute != null) {
- res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
- controlFlags, startInputReason);
+ if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
+ || (controlFlags
+ & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
+ res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
+ controlFlags, startInputReason);
+ }
}
}
} finally {
@@ -4703,6 +4747,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return mService.handleShellCommandSetInputMethod(this);
case "reset":
return mService.handleShellCommandResetInputMethod(this);
+ case "refresh_debug_properties":
+ return refreshDebugProperties();
default:
getOutPrintWriter().println("Unknown command: " + imeCommand);
return ShellCommandResult.FAILURE;
@@ -4713,6 +4759,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
@BinderThread
+ @ShellCommandResult
+ private int refreshDebugProperties() {
+ DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
+ return ShellCommandResult.SUCCESS;
+ }
+
+ @BinderThread
@Override
public void onHelp() {
try (PrintWriter pw = getOutPrintWriter()) {
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index ea748db159e1..6c63f43234ab 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -232,10 +232,9 @@ public class LocationManagerService extends ILocationManager.Stub {
private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
- private final ArrayMap<IGnssMeasurementsListener, Identity> mGnssMeasurementsListeners =
- new ArrayMap<>();
+ private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>();
- private final ArrayMap<IGnssNavigationMessageListener, Identity>
+ private final ArrayMap<IBinder, Identity>
mGnssNavigationMessageListeners = new ArrayMap<>();
// current active user on the device - other users are denied location data
@@ -438,23 +437,23 @@ public class LocationManagerService extends ILocationManager.Stub {
applyRequirementsLocked(provider);
}
- for (Entry<IGnssMeasurementsListener, Identity> entry
- : mGnssMeasurementsListeners.entrySet()) {
+ for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) {
if (entry.getValue().mUid == uid) {
if (D) {
Log.d(TAG, "gnss measurements listener from uid " + uid
+ " is now " + (foreground ? "foreground" : "background)"));
}
if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssMeasurementsProvider.addListener(entry.getKey());
+ mGnssMeasurementsProvider.addListener(
+ IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
} else {
- mGnssMeasurementsProvider.removeListener(entry.getKey());
+ mGnssMeasurementsProvider.removeListener(
+ IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
}
}
}
- for (Entry<IGnssNavigationMessageListener, Identity> entry
- : mGnssNavigationMessageListeners.entrySet()) {
+ for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) {
if (entry.getValue().mUid == uid) {
if (D) {
Log.d(TAG, "gnss navigation message listener from uid "
@@ -462,9 +461,11 @@ public class LocationManagerService extends ILocationManager.Stub {
+ (foreground ? "foreground" : "background)"));
}
if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssNavigationMessageProvider.addListener(entry.getKey());
+ mGnssNavigationMessageProvider.addListener(
+ IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()));
} else {
- mGnssNavigationMessageProvider.removeListener(entry.getKey());
+ mGnssNavigationMessageProvider.removeListener(
+ IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()));
}
}
}
@@ -2401,7 +2402,7 @@ public class LocationManagerService extends ILocationManager.Stub {
synchronized (mLock) {
Identity callerIdentity
= new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
- mGnssMeasurementsListeners.put(listener, callerIdentity);
+ mGnssMeasurementsListeners.put(listener.asBinder(), callerIdentity);
long identity = Binder.clearCallingIdentity();
try {
if (isThrottlingExemptLocked(callerIdentity)
@@ -2421,7 +2422,7 @@ public class LocationManagerService extends ILocationManager.Stub {
public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) {
if (mGnssMeasurementsProvider != null) {
synchronized (mLock) {
- mGnssMeasurementsListeners.remove(listener);
+ mGnssMeasurementsListeners.remove(listener.asBinder());
mGnssMeasurementsProvider.removeListener(listener);
}
}
@@ -2438,7 +2439,7 @@ public class LocationManagerService extends ILocationManager.Stub {
synchronized (mLock) {
Identity callerIdentity
= new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
- mGnssNavigationMessageListeners.put(listener, callerIdentity);
+ mGnssNavigationMessageListeners.put(listener.asBinder(), callerIdentity);
long identity = Binder.clearCallingIdentity();
try {
if (isThrottlingExemptLocked(callerIdentity)
@@ -2458,7 +2459,7 @@ public class LocationManagerService extends ILocationManager.Stub {
public void removeGnssNavigationMessageListener(IGnssNavigationMessageListener listener) {
if (mGnssNavigationMessageProvider != null) {
synchronized (mLock) {
- mGnssNavigationMessageListeners.remove(listener);
+ mGnssNavigationMessageListeners.remove(listener.asBinder());
mGnssNavigationMessageProvider.removeListener(listener);
}
}
@@ -3180,6 +3181,16 @@ public class LocationManagerService extends ILocationManager.Stub {
pw.println(" " + record);
}
}
+ pw.println(" Active GnssMeasurement Listeners:");
+ for (Identity identity : mGnssMeasurementsListeners.values()) {
+ pw.println(" " + identity.mPid + " " + identity.mUid + " "
+ + identity.mPackageName + ": " + isThrottlingExemptLocked(identity));
+ }
+ pw.println(" Active GnssNavigationMessage Listeners:");
+ for (Identity identity : mGnssNavigationMessageListeners.values()) {
+ pw.println(" " + identity.mPid + " " + identity.mUid + " "
+ + identity.mPackageName + ": " + isThrottlingExemptLocked(identity));
+ }
pw.println(" Overlay Provider Packages:");
for (LocationProviderInterface provider : mProviders) {
if (provider instanceof LocationProviderProxy) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 65bebc6c235a..1a47aa5cd777 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -795,11 +795,13 @@ class UserController implements Handler.Callback {
*/
private void stopGuestOrEphemeralUserIfBackground(int oldUserId) {
if (DEBUG_MU) Slog.i(TAG, "Stop guest or ephemeral user if background: " + oldUserId);
- UserState oldUss = mStartedUsers.get(oldUserId);
- if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId || oldUss == null
- || oldUss.state == UserState.STATE_STOPPING
- || oldUss.state == UserState.STATE_SHUTDOWN) {
- return;
+ synchronized(mLock) {
+ UserState oldUss = mStartedUsers.get(oldUserId);
+ if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId || oldUss == null
+ || oldUss.state == UserState.STATE_STOPPING
+ || oldUss.state == UserState.STATE_SHUTDOWN) {
+ return;
+ }
}
UserInfo userInfo = getUserInfo(oldUserId);
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index 97a6e850654b..db8dedbf12cc 100644..100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -228,12 +228,20 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) {
handleSetOsdName(cmd);
return true;
+ } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) &&
+ ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_OSD_NAME)) {
+ handleSetOsdName(cmd);
+ return true;
}
return false;
case STATE_WAITING_FOR_VENDOR_ID:
if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) {
handleVendorId(cmd);
return true;
+ } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) &&
+ ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID)) {
+ handleVendorId(cmd);
+ return true;
}
return false;
case STATE_WAITING_FOR_DEVICE_POLLING:
@@ -281,7 +289,11 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
String displayName = null;
try {
- displayName = new String(cmd.getParams(), "US-ASCII");
+ if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) {
+ displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
+ } else {
+ displayName = new String(cmd.getParams(), "US-ASCII");
+ }
} catch (UnsupportedEncodingException e) {
Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
// If failed to get display name, use the default name of device.
@@ -302,9 +314,12 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
return;
}
- byte[] params = cmd.getParams();
- int vendorId = HdmiUtils.threeBytesToInt(params);
- current.mVendorId = vendorId;
+ if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
+ byte[] params = cmd.getParams();
+ int vendorId = HdmiUtils.threeBytesToInt(params);
+ current.mVendorId = vendorId;
+ }
+
increaseProcessedDeviceCount();
checkAndProceedStage();
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 81bccdc7dfaa..1e09383db56d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -698,10 +698,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
protected boolean handleReportAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
- byte params[] = message.getParams();
- int mute = params[0] & 0x80;
- int volume = params[0] & 0x7F;
- setAudioStatus(mute == 0x80, volume);
+ boolean mute = HdmiUtils.isAudioStatusMute(message);
+ int volume = HdmiUtils.getAudioStatusVolume(message);
+ setAudioStatus(mute, volume);
return true;
}
@@ -1004,6 +1003,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
void setAudioStatus(boolean mute, int volume) {
+ if (!isSystemAudioActivated()) {
+ return;
+ }
synchronized (mLock) {
mSystemAudioMute = mute;
mSystemAudioVolume = volume;
@@ -1019,6 +1021,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@ServiceThreadOnly
void changeVolume(int curVolume, int delta, int maxVolume) {
assertRunOnServiceThread();
+ if (getAvrDeviceInfo() == null) {
+ // On initialization process, getAvrDeviceInfo() may return null and cause exception
+ return;
+ }
if (delta == 0 || !isSystemAudioActivated()) {
return;
}
@@ -1048,6 +1054,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@ServiceThreadOnly
void changeMute(boolean mute) {
assertRunOnServiceThread();
+ if (getAvrDeviceInfo() == null) {
+ // On initialization process, getAvrDeviceInfo() may return null and cause exception
+ return;
+ }
HdmiLogger.debug("[A]:Change mute:%b", mute);
synchronized (mLock) {
if (mSystemAudioMute == mute) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 807b1b19f870..3d079ccb0cad 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -989,8 +989,12 @@ public final class HdmiControlService extends SystemService {
}
// FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
// volume change notification back to hdmi control service.
- audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
- AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
+ int flag = AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME;
+ if (0 <= volume && volume <= 100) {
+ Slog.i(TAG, "volume: " + volume);
+ flag |= AudioManager.FLAG_SHOW_UI;
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, flag);
+ }
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 8b1641187ac8..4ac3bba73e25 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -152,6 +152,32 @@ final class HdmiUtils {
}
/**
+ * Parse the <Report Audio Status> message and check if it is mute
+ *
+ * @param cmd the CEC message to parse
+ * @return true if the given parameter has [MUTE]
+ */
+ static boolean isAudioStatusMute(HdmiCecMessage cmd) {
+ byte params[] = cmd.getParams();
+ return (params[0] & 0x80) == 0x80;
+ }
+
+ /**
+ * Parse the <Report Audio Status> message and extract the volume
+ *
+ * @param cmd the CEC message to parse
+ * @return device's volume. Constants.UNKNOWN_VOLUME in case it is out of range
+ */
+ static int getAudioStatusVolume(HdmiCecMessage cmd) {
+ byte params[] = cmd.getParams();
+ int volume = params[0] & 0x7F;
+ if (volume < 0x00 || 0x64 < volume) {
+ volume = Constants.UNKNOWN_VOLUME;
+ }
+ return volume;
+ }
+
+ /**
* Convert integer array to list of {@link Integer}.
*
* <p>The result is immutable.
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
index cab8439b6f92..d41a36ca031f 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -92,8 +92,8 @@ final class SystemAudioStatusAction extends HdmiCecFeatureAction {
private void handleReportAudioStatus(HdmiCecMessage cmd) {
byte[] params = cmd.getParams();
- boolean mute = (params[0] & 0x80) == 0x80;
- int volume = params[0] & 0x7F;
+ boolean mute = HdmiUtils.isAudioStatusMute(cmd);
+ int volume = HdmiUtils.getAudioStatusVolume(cmd);
tv().setAudioStatus(mute, volume);
if (!(tv().isSystemAudioActivated() ^ mute)) {
diff --git a/services/core/java/com/android/server/hdmi/VolumeControlAction.java b/services/core/java/com/android/server/hdmi/VolumeControlAction.java
index cd38b1fb2ac6..0011387f1c28 100644
--- a/services/core/java/com/android/server/hdmi/VolumeControlAction.java
+++ b/services/core/java/com/android/server/hdmi/VolumeControlAction.java
@@ -139,8 +139,8 @@ final class VolumeControlAction extends HdmiCecFeatureAction {
private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
byte params[] = cmd.getParams();
- boolean mute = (params[0] & 0x80) == 0x80;
- int volume = params[0] & 0x7F;
+ boolean mute = HdmiUtils.isAudioStatusMute(cmd);
+ int volume = HdmiUtils.getAudioStatusVolume(cmd);
mLastAvrVolume = volume;
mLastAvrMute = mute;
if (shouldUpdateAudioVolume(mute)) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerInternal.java b/services/core/java/com/android/server/job/JobSchedulerInternal.java
index c97eeaf30ab3..945726dc4748 100644
--- a/services/core/java/com/android/server/job/JobSchedulerInternal.java
+++ b/services/core/java/com/android/server/job/JobSchedulerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import android.annotation.UserIdInt;
import android.app.job.JobInfo;
import java.util.List;
@@ -39,6 +40,14 @@ public interface JobSchedulerInternal {
long nextHeartbeatForBucket(int bucket);
/**
+ * Heartbeat ordinal for the given app. This is typically the heartbeat at which
+ * the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
+ * jobs in a long time is immediately runnable even if the app is bucketed into
+ * an infrequent time allocation.
+ */
+ public long baseHeartbeatForApp(String packageName, @UserIdInt int userId, int appBucket);
+
+ /**
* Returns a list of pending jobs scheduled by the system service.
*/
List<JobInfo> getSystemScheduledPendingJobs();
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index bd1dbf9c46e8..91227c177d4d 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -19,6 +19,7 @@ package com.android.server.job;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -38,6 +39,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.Intent.UriFlags;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -2020,6 +2022,29 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
/**
+ * Heartbeat ordinal for the given app. This is typically the heartbeat at which
+ * the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
+ * jobs in a long time is immediately runnable even if the app is bucketed into
+ * an infrequent time allocation.
+ */
+ public long baseHeartbeatForApp(String packageName, @UserIdInt int userId,
+ final int appStandbyBucket) {
+ if (appStandbyBucket == 0) {
+ // Active => everything can be run right away
+ return 0;
+ }
+ final long timeSinceLastJob = mStandbyTracker.getTimeSinceLastJobRun(
+ packageName, userId);
+ final long bucketLength = mConstants.STANDBY_BEATS[appStandbyBucket];
+ final long bucketsAgo = timeSinceLastJob / bucketLength;
+
+ // If we haven't run any jobs for more than the app's current bucket period, just
+ // consider anything new to be immediately runnable. Otherwise, base it on the
+ // bucket at which we last ran jobs.
+ return (bucketsAgo > bucketLength) ? 0 : (getCurrentHeartbeat() - bucketsAgo);
+ }
+
+ /**
* Returns a list of all pending jobs. A running job is not considered pending. Periodic
* jobs are always considered pending.
*/
@@ -2094,10 +2119,14 @@ public final class JobSchedulerService extends com.android.server.SystemService
mUsageStats = usageStats;
}
+ public long getTimeSinceLastJobRun(String packageName, final @UserIdInt int userId) {
+ return mUsageStats.getTimeSinceLastJobRun(packageName, userId);
+ }
+
// AppIdleStateChangeListener interface for live updates
@Override
- public void onAppIdleStateChanged(final String packageName, final int userId,
+ public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
boolean idle, int bucket) {
final int uid = mLocalPM.getPackageUid(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 03fd7b3fffd9..373d87d971b8 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -16,6 +16,10 @@
package com.android.server.job.controllers;
+import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+
import android.app.job.JobInfo;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -35,6 +39,7 @@ import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobServiceContext;
import com.android.server.job.StateChangedListener;
@@ -62,15 +67,15 @@ public final class ConnectivityController extends StateController implements
private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
/** Singleton. */
- private static ConnectivityController mSingleton;
+ private static ConnectivityController sSingleton;
private static Object sCreationLock = new Object();
public static ConnectivityController get(JobSchedulerService jms) {
synchronized (sCreationLock) {
- if (mSingleton == null) {
- mSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
+ if (sSingleton == null) {
+ sSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
}
- return mSingleton;
+ return sSingleton;
}
}
@@ -105,37 +110,29 @@ public final class ConnectivityController extends StateController implements
}
/**
- * Test to see if running the given job on the given network is sane.
+ * Test to see if running the given job on the given network is insane.
* <p>
* For example, if a job is trying to send 10MB over a 128Kbps EDGE
* connection, it would take 10.4 minutes, and has no chance of succeeding
* before the job times out, so we'd be insane to try running it.
*/
- private boolean isSane(JobStatus jobStatus, NetworkCapabilities capabilities) {
+ @SuppressWarnings("unused")
+ private static boolean isInsane(JobStatus jobStatus, Network network,
+ NetworkCapabilities capabilities) {
final long estimatedBytes = jobStatus.getEstimatedNetworkBytes();
if (estimatedBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
// We don't know how large the job is; cross our fingers!
- return true;
- }
- if (capabilities == null) {
- // We don't know what the network is like; cross our fingers!
- return true;
+ return false;
}
// We don't ask developers to differentiate between upstream/downstream
// in their size estimates, so test against the slowest link direction.
- final long downstream = capabilities.getLinkDownstreamBandwidthKbps();
- final long upstream = capabilities.getLinkUpstreamBandwidthKbps();
- final long slowest;
- if (downstream > 0 && upstream > 0) {
- slowest = Math.min(downstream, upstream);
- } else if (downstream > 0) {
- slowest = downstream;
- } else if (upstream > 0) {
- slowest = upstream;
- } else {
+ final long slowest = NetworkCapabilities.minBandwidth(
+ capabilities.getLinkDownstreamBandwidthKbps(),
+ capabilities.getLinkUpstreamBandwidthKbps());
+ if (slowest == LINK_BANDWIDTH_UNSPECIFIED) {
// We don't know what the network is like; cross our fingers!
- return true;
+ return false;
}
final long estimatedMillis = ((estimatedBytes * DateUtils.SECOND_IN_MILLIS)
@@ -144,28 +141,87 @@ public final class ConnectivityController extends StateController implements
// If we'd never finish before the timeout, we'd be insane!
Slog.w(TAG, "Estimated " + estimatedBytes + " bytes over " + slowest
+ " kbps network would take " + estimatedMillis + "ms; that's insane!");
+ return true;
+ } else {
return false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
+ NetworkCapabilities capabilities) {
+ // If network is congested, and job is less than 50% through the
+ // developer-requested window, then we're okay delaying the job.
+ if (!capabilities.hasCapability(NET_CAPABILITY_NOT_CONGESTED)) {
+ return jobStatus.getFractionRunTime() < 0.5;
} else {
- return true;
+ return false;
}
}
+ @SuppressWarnings("unused")
+ private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
+ NetworkCapabilities capabilities) {
+ return jobStatus.getJob().getRequiredNetwork().networkCapabilities
+ .satisfiedByNetworkCapabilities(capabilities);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
+ NetworkCapabilities capabilities) {
+ // Only consider doing this for prefetching jobs
+ if ((jobStatus.getJob().getFlags() & JobInfo.FLAG_IS_PREFETCH) == 0) {
+ return false;
+ }
+
+ // See if we match after relaxing any unmetered request
+ final NetworkCapabilities relaxed = new NetworkCapabilities(
+ jobStatus.getJob().getRequiredNetwork().networkCapabilities)
+ .removeCapability(NET_CAPABILITY_NOT_METERED);
+ if (relaxed.satisfiedByNetworkCapabilities(capabilities)) {
+ // TODO: treat this as "maybe" response; need to check quotas
+ return jobStatus.getFractionRunTime() > 0.5;
+ } else {
+ return false;
+ }
+ }
+
+ @VisibleForTesting
+ static boolean isSatisfied(JobStatus jobStatus, Network network,
+ NetworkCapabilities capabilities) {
+ // Zeroth, we gotta have a network to think about being satisfied
+ if (network == null || capabilities == null) return false;
+
+ // First, are we insane?
+ if (isInsane(jobStatus, network, capabilities)) return false;
+
+ // Second, is the network congested?
+ if (isCongestionDelayed(jobStatus, network, capabilities)) return false;
+
+ // Third, is the network a strict match?
+ if (isStrictSatisfied(jobStatus, network, capabilities)) return true;
+
+ // Third, is the network a relaxed match?
+ if (isRelaxedSatisfied(jobStatus, network, capabilities)) return true;
+
+ return false;
+ }
+
private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
// TODO: consider matching against non-active networks
final int jobUid = jobStatus.getSourceUid();
final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+
final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
final NetworkInfo info = mConnManager.getNetworkInfoForUid(network, jobUid, ignoreBlocked);
final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
final boolean connected = (info != null) && info.isConnected();
- final boolean satisfied = jobStatus.getJob().getRequiredNetwork().networkCapabilities
- .satisfiedByNetworkCapabilities(capabilities);
- final boolean sane = isSane(jobStatus, capabilities);
+ final boolean satisfied = isSatisfied(jobStatus, network, capabilities);
final boolean changed = jobStatus
- .setConnectivityConstraintSatisfied(connected && satisfied && sane);
+ .setConnectivityConstraintSatisfied(connected && satisfied);
// Pass along the evaluated network for job to use; prevents race
// conditions as default routes change over time, and opens the door to
@@ -181,8 +237,7 @@ public final class ConnectivityController extends StateController implements
if (DEBUG) {
Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
+ " for " + jobStatus + ": connected=" + connected
- + " satisfied=" + satisfied
- + " sane=" + sane);
+ + " satisfied=" + satisfied);
}
return changed;
}
@@ -244,7 +299,7 @@ public final class ConnectivityController extends StateController implements
}
};
- private final INetworkPolicyListener mNetPolicyListener = new INetworkPolicyListener.Stub() {
+ private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() {
@Override
public void onUidRulesChanged(int uid, int uidRules) {
if (DEBUG) {
@@ -254,11 +309,6 @@ public final class ConnectivityController extends StateController implements
}
@Override
- public void onMeteredIfacesChanged(String[] meteredIfaces) {
- // We track this via our NetworkCallback
- }
-
- @Override
public void onRestrictBackgroundChanged(boolean restrictBackground) {
if (DEBUG) {
Slog.v(TAG, "Background restriction change to " + restrictBackground);
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 8f68713945ca..59529e0c9f10 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -24,6 +24,7 @@ import android.app.job.JobInfo;
import android.app.job.JobWorkItem;
import android.content.ClipData;
import android.content.ComponentName;
+import android.content.pm.PackageManagerInternal;
import android.net.Network;
import android.net.Uri;
import android.os.RemoteException;
@@ -96,6 +97,7 @@ public final class JobStatus {
final JobInfo job;
/** Uid of the package requesting this job. */
final int callingUid;
+ final int targetSdkVersion;
final String batteryName;
final String sourcePackageName;
@@ -243,12 +245,13 @@ public final class JobStatus {
return callingUid;
}
- private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
+ private JobStatus(JobInfo job, int callingUid, int targetSdkVersion, String sourcePackageName,
int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime) {
this.job = job;
this.callingUid = callingUid;
+ this.targetSdkVersion = targetSdkVersion;
this.standbyBucket = standbyBucket;
this.baseHeartbeat = heartbeat;
@@ -307,7 +310,7 @@ public final class JobStatus {
/** Copy constructor: used specifically when cloning JobStatus objects for persistence,
* so we preserve RTC window bounds if the source object has them. */
public JobStatus(JobStatus jobStatus) {
- this(jobStatus.getJob(), jobStatus.getUid(),
+ this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.targetSdkVersion,
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(),
jobStatus.getSourceTag(), jobStatus.getNumFailures(),
@@ -334,7 +337,7 @@ public final class JobStatus {
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime,
Pair<Long, Long> persistedExecutionTimesUTC) {
- this(job, callingUid, sourcePkgName, sourceUserId,
+ this(job, callingUid, resolveTargetSdkVersion(job), sourcePkgName, sourceUserId,
standbyBucket, baseHeartbeat,
sourceTag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
@@ -357,7 +360,7 @@ public final class JobStatus {
long newEarliestRuntimeElapsedMillis,
long newLatestRuntimeElapsedMillis, int backoffAttempt,
long lastSuccessfulRunTime, long lastFailedRunTime) {
- this(rescheduling.job, rescheduling.getUid(),
+ this(rescheduling.job, rescheduling.getUid(), resolveTargetSdkVersion(rescheduling.job),
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
rescheduling.getStandbyBucket(), newBaseHeartbeat,
rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
@@ -391,8 +394,10 @@ public final class JobStatus {
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
sourceUserId, elapsedNow);
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
- long currentHeartbeat = js != null ? js.currentHeartbeat() : 0;
- return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
+ long currentHeartbeat = js != null
+ ? js.baseHeartbeatForApp(jobPackage, sourceUserId, standbyBucket)
+ : 0;
+ return new JobStatus(job, callingUid, resolveTargetSdkVersion(job), sourcePkg, sourceUserId,
standbyBucket, currentHeartbeat, tag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */);
@@ -539,6 +544,10 @@ public final class JobStatus {
return job.getId();
}
+ public int getTargetSdkVersion() {
+ return targetSdkVersion;
+ }
+
public void printUniqueId(PrintWriter pw) {
UserHandle.formatUid(pw, callingUid);
pw.print("/");
@@ -713,6 +722,37 @@ public final class JobStatus {
return latestRunTimeElapsedMillis;
}
+ /**
+ * Return the fractional position of "now" within the "run time" window of
+ * this job.
+ * <p>
+ * For example, if the earliest run time was 10 minutes ago, and the latest
+ * run time is 30 minutes from now, this would return 0.25.
+ * <p>
+ * If the job has no window defined, returns 1. When only an earliest or
+ * latest time is defined, it's treated as an infinitely small window at
+ * that time.
+ */
+ public float getFractionRunTime() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) {
+ return 1;
+ } else if (earliestRunTimeElapsedMillis == 0) {
+ return now >= latestRunTimeElapsedMillis ? 1 : 0;
+ } else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) {
+ return now >= earliestRunTimeElapsedMillis ? 1 : 0;
+ } else {
+ if (now <= earliestRunTimeElapsedMillis) {
+ return 0;
+ } else if (now >= latestRunTimeElapsedMillis) {
+ return 1;
+ } else {
+ return (float) (now - earliestRunTimeElapsedMillis)
+ / (float) (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis);
+ }
+ }
+ }
+
public Pair<Long, Long> getPersistedUtcTimes() {
return mPersistedUtcTimes;
}
@@ -1091,6 +1131,11 @@ public final class JobStatus {
}
}
+ private static int resolveTargetSdkVersion(JobInfo job) {
+ return LocalServices.getService(PackageManagerInternal.class)
+ .getPackageTargetSdkVersion(job.getService().getPackageName());
+ }
+
// Dumpsys infrastructure
public void dump(PrintWriter pw, String prefix, boolean full, long elapsedRealtimeMillis) {
pw.print(prefix); UserHandle.formatUid(pw, callingUid);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index a29e1be0fdca..724073a87a70 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -228,6 +228,10 @@ public class KeySyncTask implements Runnable {
counterId = generateAndStoreCounterId(recoveryAgentUid);
}
}
+
+ // TODO: make sure the same counter id is used during recovery and remove temporary fix.
+ counterId = 1L;
+
byte[] vaultParams = KeySyncUtils.packVaultParams(
publicKey,
counterId,
@@ -263,12 +267,16 @@ public class KeySyncTask implements Runnable {
// If application keys are not updated, snapshot will not be created on next unlock.
mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);
- // TODO: use Builder.
- mRecoverySnapshotStorage.put(recoveryAgentUid, new KeychainSnapshot(
- snapshotVersion,
- /*recoveryMetadata=*/ metadataList,
- /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys),
- /*encryptedRecoveryKeyblob=*/ encryptedRecoveryKey));
+ mRecoverySnapshotStorage.put(recoveryAgentUid, new KeychainSnapshot.Builder()
+ .setSnapshotVersion(snapshotVersion)
+ .setMaxAttempts(TRUSTED_HARDWARE_MAX_ATTEMPTS)
+ .setCounterId(counterId)
+ .setTrustedHardwarePublicKey(SecureBox.encodePublicKey(publicKey))
+ .setServerParams(vaultHandle)
+ .setKeychainProtectionParams(metadataList)
+ .setWrappedApplicationKeys(createApplicationKeyEntries(encryptedApplicationKeys))
+ .setEncryptedRecoveryKeyBlob(encryptedRecoveryKey)
+ .build());
mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index 219f6e10938e..89e2debec9d2 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -300,7 +300,7 @@ public class KeySyncUtils {
.put(SecureBox.encodePublicKey(thmPublicKey))
.putLong(counterId)
.putInt(maxAttempts)
- .put(new byte[VAULT_HANDLE_LENGTH_BYTES]) // TODO: replace with real vaultHandle
+ .put(vaultHandle)
.array();
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 59855beec625..76508d5817e2 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -16,12 +16,12 @@
package com.android.server.locksettings.recoverablekeystore;
-import static android.security.keystore.RecoveryManager.ERROR_BAD_CERTIFICATE_FORMAT;
-import static android.security.keystore.RecoveryManager.ERROR_DECRYPTION_FAILED;
-import static android.security.keystore.RecoveryManager.ERROR_INSECURE_USER;
-import static android.security.keystore.RecoveryManager.ERROR_NO_SNAPSHOT_PENDING;
-import static android.security.keystore.RecoveryManager.ERROR_SERVICE_INTERNAL_ERROR;
-import static android.security.keystore.RecoveryManager.ERROR_SESSION_EXPIRED;
+import static android.security.keystore.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
+import static android.security.keystore.RecoveryController.ERROR_DECRYPTION_FAILED;
+import static android.security.keystore.RecoveryController.ERROR_INSECURE_USER;
+import static android.security.keystore.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
+import static android.security.keystore.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
+import static android.security.keystore.RecoveryController.ERROR_SESSION_EXPIRED;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -35,8 +35,8 @@ import android.os.UserHandle;
import android.security.keystore.KeychainProtectionParams;
import android.security.keystore.KeychainSnapshot;
+import android.security.keystore.RecoveryController;
import android.security.keystore.WrappedApplicationKey;
-import android.security.keystore.RecoveryManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -63,7 +63,7 @@ import java.util.concurrent.Executors;
import javax.crypto.AEADBadTagException;
/**
- * Class with {@link RecoveryManager} API implementation and internal methods to interact
+ * Class with {@link RecoveryController} API implementation and internal methods to interact
* with {@code LockSettingsService}.
*
* @hide
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
index 0042e101b4f1..c33c9de95182 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
@@ -16,8 +16,8 @@
package com.android.server.locksettings.recoverablekeystore;
+import android.security.keystore.RecoveryController;
import android.util.Log;
-import android.security.keystore.RecoveryManager;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -97,7 +97,7 @@ public class WrappedKey {
/*nonce=*/ cipher.getIV(),
/*keyMaterial=*/ encryptedKeyMaterial,
/*platformKeyGenerationId=*/ wrappingKey.getGenerationId(),
- RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+ RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS);
}
/**
@@ -107,14 +107,14 @@ public class WrappedKey {
* @param keyMaterial The encrypted bytes of the key material.
* @param platformKeyGenerationId The generation ID of the key used to wrap this key.
*
- * @see RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS
+ * @see RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS
* @hide
*/
public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) {
mNonce = nonce;
mKeyMaterial = keyMaterial;
mPlatformKeyGenerationId = platformKeyGenerationId;
- mRecoveryStatus = RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS;
+ mRecoveryStatus = RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS;
}
/**
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 7934a9682426..e458f485c9ab 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -16,6 +16,9 @@
package com.android.server.net;
+import android.net.Network;
+import android.telephony.SubscriptionPlan;
+
/**
* Network Policy Manager local system service interface.
*
@@ -47,4 +50,20 @@ public abstract class NetworkPolicyManagerInternal {
* @param added Denotes whether the {@param appId} has been added or removed from the whitelist.
*/
public abstract void onTempPowerSaveWhitelistChange(int appId, boolean added);
+
+ /**
+ * Return the active {@link SubscriptionPlan} for the given network.
+ */
+ public abstract SubscriptionPlan getSubscriptionPlan(Network network);
+
+ public static final int QUOTA_TYPE_JOBS = 1;
+ public static final int QUOTA_TYPE_MULTIPATH = 2;
+
+ /**
+ * Return the daily quota (in bytes) that can be opportunistically used on
+ * the given network to improve the end user experience. It's called
+ * "opportunistic" because it's traffic that would typically not use the
+ * given network.
+ */
+ public abstract long getSubscriptionOpportunisticQuota(Network network, int quotaType);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index ff9b2fd4ae2d..1318fc833c20 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -34,6 +34,7 @@ import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicy.WARNING_DISABLED;
@@ -69,6 +70,7 @@ import static android.net.TrafficStats.MB_IN_BYTES;
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static com.android.internal.util.ArrayUtils.appendInt;
@@ -97,6 +99,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.Manifest;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -134,8 +137,10 @@ import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.NetworkState;
import android.net.NetworkTemplate;
+import android.net.StringNetworkSpecifier;
import android.net.TrafficStats;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
@@ -174,6 +179,7 @@ import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.DataUnit;
import android.util.Log;
import android.util.NtpTrustedTime;
import android.util.Pair;
@@ -182,6 +188,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import android.util.TrustedTime;
import android.util.Xml;
@@ -200,8 +207,10 @@ import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
+import com.android.server.SystemService;
import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
@@ -219,7 +228,6 @@ import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;
@@ -278,6 +286,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
public static final int TYPE_LIMIT = SystemMessage.NOTE_NET_LIMIT;
@VisibleForTesting
public static final int TYPE_LIMIT_SNOOZED = SystemMessage.NOTE_NET_LIMIT_SNOOZED;
+ @VisibleForTesting
+ public static final int TYPE_RAPID = SystemMessage.NOTE_NET_RAPID;
private static final String TAG_POLICY_LIST = "policy-list";
private static final String TAG_NETWORK_POLICY = "network-policy";
@@ -332,6 +342,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
private static final int MSG_POLICIES_CHANGED = 13;
private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
+ private static final int MSG_SUBSCRIPTION_OVERRIDE = 16;
private static final int UID_MSG_STATE_CHANGED = 100;
private static final int UID_MSG_GONE = 101;
@@ -384,6 +395,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@GuardedBy("mNetworkPoliciesSecondLock")
final SparseArray<String> mSubscriptionPlansOwner = new SparseArray<>();
+ /** Map from subId to daily opportunistic quota. */
+ @GuardedBy("mNetworkPoliciesSecondLock")
+ final SparseLongArray mSubscriptionOpportunisticQuota = new SparseLongArray();
+
/** Defined UID policies. */
@GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidPolicy = new SparseIntArray();
/** Currently derived rules for each UID. */
@@ -453,6 +468,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@GuardedBy("mNetworkPoliciesSecondLock")
private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
+ /** Map from netId to subId as of last update */
+ @GuardedBy("mNetworkPoliciesSecondLock")
+ private final SparseIntArray mNetIdToSubId = new SparseIntArray();
+
private final RemoteCallbackList<INetworkPolicyListener>
mListeners = new RemoteCallbackList<>();
@@ -984,6 +1003,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
};
+ @VisibleForTesting
+ public void updateNotifications() {
+ synchronized (mNetworkPoliciesSecondLock) {
+ updateNotificationsNL();
+ }
+ }
+
/**
* Check {@link NetworkPolicy} against current {@link INetworkStatsService}
* to show visible notifications as needed.
@@ -1028,6 +1054,44 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
}
+ // Alert the user about heavy recent data usage that might result in
+ // going over their carrier limit.
+ for (int i = 0; i < mNetIdToSubId.size(); i++) {
+ final int subId = mNetIdToSubId.valueAt(i);
+ final SubscriptionPlan plan = getPrimarySubscriptionPlanLocked(subId);
+ if (plan == null) continue;
+
+ final long limitBytes = plan.getDataLimitBytes();
+ if (limitBytes == SubscriptionPlan.BYTES_UNKNOWN) {
+ // Ignore missing limits
+ } else if (limitBytes == SubscriptionPlan.BYTES_UNLIMITED) {
+ // Unlimited data; no rapid usage alerting
+ } else {
+ // Warn if average usage over last 4 days is on track to blow
+ // pretty far past the plan limits.
+ final long recentDuration = TimeUnit.DAYS.toMillis(4);
+ final long end = RecurrenceRule.sClock.millis();
+ final long start = end - recentDuration;
+
+ final NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(
+ mContext.getSystemService(TelephonyManager.class).getSubscriberId(subId));
+ final long recentBytes = getTotalBytes(template, start, end);
+
+ final Pair<ZonedDateTime, ZonedDateTime> cycle = plan.cycleIterator().next();
+ final long cycleDuration = cycle.second.toInstant().toEpochMilli()
+ - cycle.first.toInstant().toEpochMilli();
+
+ final long projectedBytes = (recentBytes * cycleDuration) / recentDuration;
+ final long alertBytes = (limitBytes * 3) / 2;
+ if (projectedBytes > alertBytes) {
+ final NetworkPolicy policy = new NetworkPolicy(template, plan.getCycleRule(),
+ NetworkPolicy.WARNING_DISABLED, NetworkPolicy.LIMIT_DISABLED,
+ NetworkPolicy.SNOOZE_NEVER, NetworkPolicy.SNOOZE_NEVER, true, true);
+ enqueueNotification(policy, TYPE_RAPID, 0);
+ }
+ }
+ }
+
// cancel stale notifications that we didn't renew above
for (int i = beforeNotifs.size()-1; i >= 0; i--) {
final NotificationId notificationId = beforeNotifs.valueAt(i);
@@ -1049,7 +1113,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final SubscriptionManager sub = SubscriptionManager.from(mContext);
// Mobile template is relevant when any active subscriber matches
- final int[] subIds = sub.getActiveSubscriptionIdList();
+ final int[] subIds = ArrayUtils.defeatNullable(sub.getActiveSubscriptionIdList());
for (int subId : subIds) {
final String subscriberId = tele.getSubscriberId(subId);
final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
@@ -1186,6 +1250,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
break;
}
+ case TYPE_RAPID: {
+ final CharSequence title = res.getText(R.string.data_usage_rapid_title);
+ body = res.getText(R.string.data_usage_rapid_body);
+
+ builder.setOngoing(true);
+ builder.setSmallIcon(R.drawable.stat_notify_error);
+ builder.setTicker(title);
+ builder.setContentTitle(title);
+ builder.setContentText(body);
+
+ final Intent intent = buildViewDataUsageIntent(res, policy.template);
+ builder.setContentIntent(PendingIntent.getActivity(
+ mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
+ break;
+ }
}
// TODO: move to NotificationManager once we can mock it
@@ -1239,6 +1318,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
};
+ @VisibleForTesting
+ public void updateNetworks() {
+ mConnReceiver.onReceive(null, null);
+ }
+
/**
* Update mobile policies with data cycle information from {@link CarrierConfigManager}
* if necessary.
@@ -1457,7 +1541,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final SubscriptionManager sm = SubscriptionManager.from(mContext);
final TelephonyManager tm = TelephonyManager.from(mContext);
- final int[] subIds = sm.getActiveSubscriptionIdList();
+ final int[] subIds = ArrayUtils.defeatNullable(sm.getActiveSubscriptionIdList());
for (int subId : subIds) {
final String subscriberId = tm.getSubscriberId(subId);
final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
@@ -1496,7 +1580,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final NetworkState[] states;
try {
- states = mConnManager.getAllNetworkState();
+ states = defeatNullable(mConnManager.getAllNetworkState());
} catch (RemoteException e) {
// ignored; service lives in system_server
return;
@@ -1504,8 +1588,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// First, generate identities of all connected networks so we can
// quickly compare them against all defined policies below.
+ mNetIdToSubId.clear();
final ArrayMap<NetworkState, NetworkIdentity> identified = new ArrayMap<>();
for (NetworkState state : states) {
+ if (state.network != null) {
+ mNetIdToSubId.put(state.network.netId, parseSubId(state));
+ }
if (state.networkInfo != null && state.networkInfo.isConnected()) {
final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
identified.put(state, ident);
@@ -1607,6 +1695,42 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
mMeteredIfaces = newMeteredIfaces;
+ // Finally, calculate our opportunistic quotas
+ // TODO: add experiments support to disable or tweak ratios
+ mSubscriptionOpportunisticQuota.clear();
+ for (NetworkState state : states) {
+ if (state.network == null) continue;
+ final int subId = getSubIdLocked(state.network);
+ final SubscriptionPlan plan = getPrimarySubscriptionPlanLocked(subId);
+ if (plan == null) continue;
+
+ // By default assume we have no quota
+ long quotaBytes = 0;
+
+ final long limitBytes = plan.getDataLimitBytes();
+ if (limitBytes == SubscriptionPlan.BYTES_UNKNOWN) {
+ // Ignore missing limits
+ } else if (limitBytes == SubscriptionPlan.BYTES_UNLIMITED) {
+ // Unlimited data; let's use 20MiB/day (600MiB/month)
+ quotaBytes = DataUnit.MEBIBYTES.toBytes(20);
+ } else {
+ // Limited data; let's only use 10% of remaining budget
+ final Pair<ZonedDateTime, ZonedDateTime> cycle = plan.cycleIterator().next();
+ final long start = cycle.first.toInstant().toEpochMilli();
+ final long end = cycle.second.toInstant().toEpochMilli();
+ final long totalBytes = getTotalBytes(
+ NetworkTemplate.buildTemplateMobileAll(state.subscriberId), start, end);
+ final long remainingBytes = limitBytes - totalBytes;
+ final long remainingDays = Math.min(1, (end - RecurrenceRule.sClock.millis())
+ / TimeUnit.DAYS.toMillis(1));
+ if (remainingBytes > 0) {
+ quotaBytes = (remainingBytes / remainingDays) / 10;
+ }
+ }
+
+ mSubscriptionOpportunisticQuota.put(subId, quotaBytes);
+ }
+
final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]);
mHandler.obtainMessage(MSG_METERED_IFACES_CHANGED, meteredIfaces).sendToTarget();
@@ -1624,7 +1748,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final TelephonyManager tele = TelephonyManager.from(mContext);
final SubscriptionManager sub = SubscriptionManager.from(mContext);
- final int[] subIds = sub.getActiveSubscriptionIdList();
+ final int[] subIds = ArrayUtils.defeatNullable(sub.getActiveSubscriptionIdList());
for (int subId : subIds) {
final String subscriberId = tele.getSubscriberId(subId);
ensureActiveMobilePolicyAL(subId, subscriberId);
@@ -2815,6 +2939,27 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
@Override
+ public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue,
+ long timeoutMillis, String callingPackage) {
+ enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
+
+ // We can only override when carrier told us about plans
+ synchronized (mNetworkPoliciesSecondLock) {
+ if (ArrayUtils.isEmpty(mSubscriptionPlans.get(subId))) {
+ throw new IllegalStateException(
+ "Must provide SubscriptionPlan information before overriding");
+ }
+ }
+
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
+ overrideMask, overrideValue, subId));
+ if (timeoutMillis > 0) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
+ overrideMask, 0, subId), timeoutMillis);
+ }
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
@@ -3819,6 +3964,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
}
+ private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId,
+ int overrideMask, int overrideValue) {
+ if (listener != null) {
+ try {
+ listener.onSubscriptionOverride(subId, overrideMask, overrideValue);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
@@ -3922,6 +4077,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
resetUidFirewallRules(msg.arg1);
return true;
}
+ case MSG_SUBSCRIPTION_OVERRIDE: {
+ final int overrideMask = msg.arg1;
+ final int overrideValue = msg.arg2;
+ final int subId = (int) msg.obj;
+ final int length = mListeners.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+ dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue);
+ }
+ mListeners.finishBroadcast();
+ return true;
+ }
default: {
return false;
}
@@ -4404,12 +4571,57 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
updateRulesForTempWhitelistChangeUL(appId);
}
}
+
+ @Override
+ public SubscriptionPlan getSubscriptionPlan(Network network) {
+ synchronized (mNetworkPoliciesSecondLock) {
+ final int subId = getSubIdLocked(network);
+ return getPrimarySubscriptionPlanLocked(subId);
+ }
+ }
+
+ @Override
+ public long getSubscriptionOpportunisticQuota(Network network, int quotaType) {
+ synchronized (mNetworkPoliciesSecondLock) {
+ // TODO: handle splitting quota between use-cases
+ return mSubscriptionOpportunisticQuota.get(getSubIdLocked(network));
+ }
+ }
+ }
+
+ private int parseSubId(NetworkState state) {
+ // TODO: moved to using a legitimate NetworkSpecifier instead of string parsing
+ int subId = INVALID_SUBSCRIPTION_ID;
+ if (state != null && state.networkCapabilities != null
+ && state.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ NetworkSpecifier spec = state.networkCapabilities.getNetworkSpecifier();
+ if (spec instanceof StringNetworkSpecifier) {
+ try {
+ subId = Integer.parseInt(((StringNetworkSpecifier) spec).specifier);
+ } catch (NumberFormatException e) {
+ }
+ }
+ }
+ return subId;
+ }
+
+ private int getSubIdLocked(Network network) {
+ return mNetIdToSubId.get(network.netId, INVALID_SUBSCRIPTION_ID);
+ }
+
+ private SubscriptionPlan getPrimarySubscriptionPlanLocked(int subId) {
+ final SubscriptionPlan[] plans = mSubscriptionPlans.get(subId);
+ return ArrayUtils.isEmpty(plans) ? null : plans[0];
}
private static boolean hasRule(int uidRules, int rule) {
return (uidRules & rule) != 0;
}
+ private static @NonNull NetworkState[] defeatNullable(@Nullable NetworkState[] val) {
+ return (val != null) ? val : new NetworkState[0];
+ }
+
private class NotificationId {
private final String mTag;
private final int mId;
diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
index 3bcc36f0ba2c..239ddbeb5f86 100644
--- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
+++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
@@ -17,15 +17,11 @@
package com.android.server.net.watchlist;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.net.IIpConnectivityMetrics;
import android.net.INetdEventCallback;
-import android.net.NetworkWatchlistManager;
import android.net.metrics.IpConnectivityLog;
import android.os.Binder;
import android.os.Process;
-import android.os.SharedMemory;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -42,9 +38,7 @@ import com.android.server.ServiceThread;
import com.android.server.SystemService;
import java.io.FileDescriptor;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.util.List;
/**
* Implementation of network watchlist service.
@@ -99,7 +93,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
private volatile boolean mIsLoggingEnabled = false;
private final Object mLoggingSwitchLock = new Object();
- private final WatchlistSettings mSettings;
+ private final WatchlistConfig mConfig;
private final Context mContext;
// Separate thread to handle expensive watchlist logging work.
@@ -112,7 +106,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
public NetworkWatchlistService(Context context) {
mContext = context;
- mSettings = WatchlistSettings.getInstance();
+ mConfig = WatchlistConfig.getInstance();
mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
/* allowIo */ false);
mHandlerThread.start();
@@ -126,7 +120,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
NetworkWatchlistService(Context context, ServiceThread handlerThread,
WatchlistLoggingHandler handler, IIpConnectivityMetrics ipConnectivityMetrics) {
mContext = context;
- mSettings = WatchlistSettings.getInstance();
+ mConfig = WatchlistConfig.getInstance();
mHandlerThread = handlerThread;
mNetworkWatchlistHandler = handler;
mIpConnectivityMetrics = ipConnectivityMetrics;
@@ -228,7 +222,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
public void reloadWatchlist() throws RemoteException {
enforceWatchlistLoggingPermission();
Slog.i(TAG, "Reloading watchlist");
- mSettings.reloadSettings();
+ mConfig.reloadConfig();
}
@Override
@@ -240,7 +234,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- mSettings.dump(fd, pw, args);
+ mConfig.dump(fd, pw, args);
}
}
diff --git a/services/core/java/com/android/server/net/watchlist/PrivacyUtils.java b/services/core/java/com/android/server/net/watchlist/PrivacyUtils.java
new file mode 100644
index 000000000000..c1231fa342e7
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/PrivacyUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.privacy.DifferentialPrivacyEncoder;
+import android.privacy.internal.longitudinalreporting.LongitudinalReportingConfig;
+import android.privacy.internal.longitudinalreporting.LongitudinalReportingEncoder;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class to apply differential privacy to watchlist reports.
+ */
+class PrivacyUtils {
+
+ private static final String TAG = "PrivacyUtils";
+
+ /**
+ * Parameters used for encoding watchlist reports.
+ * These numbers are optimal parameters for protecting privacy with good utility.
+ *
+ * TODO: Add links to explain the math behind.
+ */
+ private static final String ENCODER_ID_PREFIX = "watchlist_encoder:";
+ private static final double PROB_F = 0.469;
+ private static final double PROB_P = 0.28;
+ private static final double PROB_Q = 1.0;
+
+ private PrivacyUtils() {
+ }
+
+ /**
+ * Get insecure DP encoder.
+ * Should not apply it directly on real data as seed is not randomized.
+ */
+ @VisibleForTesting
+ static DifferentialPrivacyEncoder createInsecureDPEncoderForTest(String appDigest) {
+ final LongitudinalReportingConfig config = createLongitudinalReportingConfig(appDigest);
+ return LongitudinalReportingEncoder.createInsecureEncoderForTest(config);
+ }
+
+ /**
+ * Get secure encoder to encode watchlist.
+ *
+ * Warning: If you use the same user secret and app digest, then you will get the same
+ * PRR result.
+ */
+ @VisibleForTesting
+ static DifferentialPrivacyEncoder createSecureDPEncoder(byte[] userSecret,
+ String appDigest) {
+ final LongitudinalReportingConfig config = createLongitudinalReportingConfig(appDigest);
+ return LongitudinalReportingEncoder.createEncoder(config, userSecret);
+ }
+
+ /**
+ * Get DP config for encoding watchlist reports.
+ */
+ private static LongitudinalReportingConfig createLongitudinalReportingConfig(String appDigest) {
+ return new LongitudinalReportingConfig(ENCODER_ID_PREFIX + appDigest, PROB_F, PROB_P,
+ PROB_Q);
+ }
+
+ /**
+ * Create a map that stores appDigest, encoded_visitedWatchlist pairs.
+ */
+ @VisibleForTesting
+ static Map<String, Boolean> createDpEncodedReportMap(boolean isSecure, byte[] userSecret,
+ List<String> appDigestList, WatchlistReportDbHelper.AggregatedResult aggregatedResult) {
+ final int appDigestListSize = appDigestList.size();
+ final HashMap<String, Boolean> resultMap = new HashMap<>(appDigestListSize);
+ for (int i = 0; i < appDigestListSize; i++) {
+ final String appDigest = appDigestList.get(i);
+ // Each app needs to have different PRR result, hence we use appDigest as encoder Id.
+ final DifferentialPrivacyEncoder encoder = isSecure
+ ? createSecureDPEncoder(userSecret, appDigest)
+ : createInsecureDPEncoderForTest(appDigest);
+ final boolean visitedWatchlist = aggregatedResult.appDigestList.contains(appDigest);
+ // Get the least significant bit of first byte, and set result to True if it is 1
+ boolean encodedVisitedWatchlist = ((int) encoder.encodeBoolean(visitedWatchlist)[0]
+ & 0x1) == 0x1;
+ resultMap.put(appDigest, encodedVisitedWatchlist);
+ }
+ return resultMap;
+ }
+}
diff --git a/services/core/java/com/android/server/net/watchlist/ReportEncoder.java b/services/core/java/com/android/server/net/watchlist/ReportEncoder.java
new file mode 100644
index 000000000000..5d7ff5a751aa
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/ReportEncoder.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.server.net.watchlist;
+
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class to encode and generate serialized DP encoded watchlist report.
+ *
+ * <p>Serialized report data structure:
+ * [4 bytes magic number][4_bytes_report_version_code][32_bytes_watchlist_hash]
+ * [app_1_digest_byte_array][app_1_encoded_visited_cnc_byte]
+ * [app_2_digest_byte_array][app_2_encoded_visited_cnc_byte]
+ * ...
+ *
+ * Total size: 4 + 4 + 32 + (32+1)*N, where N = number of digests
+ */
+class ReportEncoder {
+
+ private static final String TAG = "ReportEncoder";
+
+ // Report header magic number
+ private static final byte[] MAGIC_NUMBER = {(byte) 0x8D, (byte) 0x37, (byte) 0x0A, (byte) 0xAC};
+ // Report version number, as file format / parameters can be changed in later version, we need
+ // to have versioning on watchlist report format
+ private static final byte[] REPORT_VERSION = {(byte) 0x00, (byte) 0x01};
+
+ private static final int WATCHLIST_HASH_SIZE = 32;
+ private static final int APP_DIGEST_SIZE = 32;
+
+ /**
+ * Apply DP on watchlist results, and generate a serialized watchlist report ready to store
+ * in DropBox.
+ */
+ static byte[] encodeWatchlistReport(WatchlistConfig config, byte[] userSecret,
+ List<String> appDigestList, WatchlistReportDbHelper.AggregatedResult aggregatedResult) {
+ Map<String, Boolean> resultMap = PrivacyUtils.createDpEncodedReportMap(
+ config.isConfigSecure(), userSecret, appDigestList, aggregatedResult);
+ return serializeReport(config, resultMap);
+ }
+
+ /**
+ * Convert DP encoded watchlist report into byte[] format.
+ * TODO: Serialize it using protobuf
+ *
+ * @param encodedReportMap DP encoded watchlist report.
+ * @return Watchlist report in byte[] format, which will be shared in Dropbox. Null if
+ * watchlist report cannot be generated.
+ */
+ @Nullable
+ @VisibleForTesting
+ static byte[] serializeReport(WatchlistConfig config,
+ Map<String, Boolean> encodedReportMap) {
+ // TODO: Handle watchlist config changed case
+ final byte[] watchlistHash = config.getWatchlistConfigHash();
+ if (watchlistHash == null) {
+ Log.e(TAG, "No watchlist hash");
+ return null;
+ }
+ if (watchlistHash.length != WATCHLIST_HASH_SIZE) {
+ Log.e(TAG, "Unexpected hash length");
+ return null;
+ }
+ final int reportMapSize = encodedReportMap.size();
+ final byte[] outputReport =
+ new byte[MAGIC_NUMBER.length + REPORT_VERSION.length + WATCHLIST_HASH_SIZE
+ + reportMapSize * (APP_DIGEST_SIZE + /* Result */ 1)];
+ final List<String> sortedKeys = new ArrayList(encodedReportMap.keySet());
+ Collections.sort(sortedKeys);
+
+ int offset = 0;
+
+ // Set magic number to report
+ System.arraycopy(MAGIC_NUMBER, 0, outputReport, offset, MAGIC_NUMBER.length);
+ offset += MAGIC_NUMBER.length;
+
+ // Set report version to report
+ System.arraycopy(REPORT_VERSION, 0, outputReport, offset, REPORT_VERSION.length);
+ offset += REPORT_VERSION.length;
+
+ // Set watchlist hash to report
+ System.arraycopy(watchlistHash, 0, outputReport, offset, watchlistHash.length);
+ offset += watchlistHash.length;
+
+ // Set app digest, encoded_isPha pair to report
+ for (int i = 0; i < reportMapSize; i++) {
+ String key = sortedKeys.get(i);
+ byte[] digest = HexDump.hexStringToByteArray(key);
+ boolean isPha = encodedReportMap.get(key);
+ System.arraycopy(digest, 0, outputReport, offset, APP_DIGEST_SIZE);
+ offset += digest.length;
+ outputReport[offset] = (byte) (isPha ? 1 : 0);
+ offset += 1;
+ }
+ if (outputReport.length != offset) {
+ Log.e(TAG, "Watchlist report size does not match! Offset: " + offset + ", report size: "
+ + outputReport.length);
+
+ }
+ return outputReport;
+ }
+}
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java b/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java
new file mode 100644
index 000000000000..fbf11fce08eb
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.watchlist;
+
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.CRC32;
+
+/**
+ * Class for watchlist config operations, like setting watchlist, query if a domain
+ * exists in watchlist.
+ */
+class WatchlistConfig {
+ private static final String TAG = "WatchlistConfig";
+
+ // Watchlist config that pushed by ConfigUpdater.
+ private static final String NETWORK_WATCHLIST_DB_PATH =
+ "/data/misc/network_watchlist/network_watchlist.xml";
+
+ // Hash for null / unknown config, a 32 byte array filled with content 0x00
+ private static final byte[] UNKNOWN_CONFIG_HASH = new byte[32];
+
+ private static class XmlTags {
+ private static final String WATCHLIST_CONFIG = "watchlist-config";
+ private static final String SHA256_DOMAIN = "sha256-domain";
+ private static final String CRC32_DOMAIN = "crc32-domain";
+ private static final String SHA256_IP = "sha256-ip";
+ private static final String CRC32_IP = "crc32-ip";
+ private static final String HASH = "hash";
+ }
+
+ private static class CrcShaDigests {
+ final HarmfulDigests crc32Digests;
+ final HarmfulDigests sha256Digests;
+
+ public CrcShaDigests(HarmfulDigests crc32Digests, HarmfulDigests sha256Digests) {
+ this.crc32Digests = crc32Digests;
+ this.sha256Digests = sha256Digests;
+ }
+ }
+
+ /*
+ * This is always true unless watchlist is being set by adb command, then it will be false
+ * until next reboot.
+ */
+ private boolean mIsSecureConfig = true;
+
+ private final static WatchlistConfig sInstance = new WatchlistConfig();
+ private final File mXmlFile;
+
+ private volatile CrcShaDigests mDomainDigests;
+ private volatile CrcShaDigests mIpDigests;
+
+ public static WatchlistConfig getInstance() {
+ return sInstance;
+ }
+
+ private WatchlistConfig() {
+ this(new File(NETWORK_WATCHLIST_DB_PATH));
+ }
+
+ @VisibleForTesting
+ protected WatchlistConfig(File xmlFile) {
+ mXmlFile = xmlFile;
+ reloadConfig();
+ }
+
+ /**
+ * Reload watchlist by reading config file.
+ */
+ public void reloadConfig() {
+ try (FileInputStream stream = new FileInputStream(mXmlFile)){
+ final List<byte[]> crc32DomainList = new ArrayList<>();
+ final List<byte[]> sha256DomainList = new ArrayList<>();
+ final List<byte[]> crc32IpList = new ArrayList<>();
+ final List<byte[]> sha256IpList = new ArrayList<>();
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_CONFIG);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ switch (tagName) {
+ case XmlTags.CRC32_DOMAIN:
+ parseHashes(parser, tagName, crc32DomainList);
+ break;
+ case XmlTags.CRC32_IP:
+ parseHashes(parser, tagName, crc32IpList);
+ break;
+ case XmlTags.SHA256_DOMAIN:
+ parseHashes(parser, tagName, sha256DomainList);
+ break;
+ case XmlTags.SHA256_IP:
+ parseHashes(parser, tagName, sha256IpList);
+ break;
+ default:
+ Log.w(TAG, "Unknown element: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_CONFIG);
+ mDomainDigests = new CrcShaDigests(new HarmfulDigests(crc32DomainList),
+ new HarmfulDigests(sha256DomainList));
+ mIpDigests = new CrcShaDigests(new HarmfulDigests(crc32IpList),
+ new HarmfulDigests(sha256IpList));
+ Log.i(TAG, "Reload watchlist done");
+ } catch (IllegalStateException | NullPointerException | NumberFormatException |
+ XmlPullParserException | IOException | IndexOutOfBoundsException e) {
+ Slog.e(TAG, "Failed parsing xml", e);
+ }
+ }
+
+ private void parseHashes(XmlPullParser parser, String tagName, List<byte[]> hashList)
+ throws IOException, XmlPullParserException {
+ parser.require(XmlPullParser.START_TAG, null, tagName);
+ // Get all the hashes for this tag
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ parser.require(XmlPullParser.START_TAG, null, XmlTags.HASH);
+ byte[] hash = HexDump.hexStringToByteArray(parser.nextText());
+ parser.require(XmlPullParser.END_TAG, null, XmlTags.HASH);
+ hashList.add(hash);
+ }
+ parser.require(XmlPullParser.END_TAG, null, tagName);
+ }
+
+ public boolean containsDomain(String domain) {
+ final CrcShaDigests domainDigests = mDomainDigests;
+ if (domainDigests == null) {
+ Slog.wtf(TAG, "domainDigests should not be null");
+ return false;
+ }
+ // First it does a quick CRC32 check.
+ final byte[] crc32 = getCrc32(domain);
+ if (!domainDigests.crc32Digests.contains(crc32)) {
+ return false;
+ }
+ // Now we do a slow SHA256 check.
+ final byte[] sha256 = getSha256(domain);
+ return domainDigests.sha256Digests.contains(sha256);
+ }
+
+ public boolean containsIp(String ip) {
+ final CrcShaDigests ipDigests = mIpDigests;
+ if (ipDigests == null) {
+ Slog.wtf(TAG, "ipDigests should not be null");
+ return false;
+ }
+ // First it does a quick CRC32 check.
+ final byte[] crc32 = getCrc32(ip);
+ if (!ipDigests.crc32Digests.contains(crc32)) {
+ return false;
+ }
+ // Now we do a slow SHA256 check.
+ final byte[] sha256 = getSha256(ip);
+ return ipDigests.sha256Digests.contains(sha256);
+ }
+
+
+ /** Get CRC32 of a string
+ *
+ * TODO: Review if we should use CRC32 or other algorithms
+ */
+ private byte[] getCrc32(String str) {
+ final CRC32 crc = new CRC32();
+ crc.update(str.getBytes());
+ final long tmp = crc.getValue();
+ return new byte[]{(byte) (tmp >> 24 & 255), (byte) (tmp >> 16 & 255),
+ (byte) (tmp >> 8 & 255), (byte) (tmp & 255)};
+ }
+
+ /** Get SHA256 of a string */
+ private byte[] getSha256(String str) {
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA256");
+ } catch (NoSuchAlgorithmException e) {
+ /* can't happen */
+ return null;
+ }
+ messageDigest.update(str.getBytes());
+ return messageDigest.digest();
+ }
+
+ public boolean isConfigSecure() {
+ return mIsSecureConfig;
+ }
+
+ public byte[] getWatchlistConfigHash() {
+ if (!mXmlFile.exists()) {
+ return UNKNOWN_CONFIG_HASH;
+ }
+ try {
+ return DigestUtils.getSha256Hash(mXmlFile);
+ } catch (IOException | NoSuchAlgorithmException e) {
+ Log.e(TAG, "Unable to get watchlist config hash", e);
+ }
+ return UNKNOWN_CONFIG_HASH;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Domain CRC32 digest list:");
+ mDomainDigests.crc32Digests.dump(fd, pw, args);
+ pw.println("Domain SHA256 digest list:");
+ mDomainDigests.sha256Digests.dump(fd, pw, args);
+ pw.println("Ip CRC32 digest list:");
+ mIpDigests.crc32Digests.dump(fd, pw, args);
+ pw.println("Ip SHA256 digest list:");
+ mIpDigests.sha256Digests.dump(fd, pw, args);
+ }
+}
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
index 2247558476c4..ee0049be8584 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
@@ -16,8 +16,10 @@
package com.android.server.net.watchlist;
+import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
@@ -30,14 +32,19 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.HexDump;
import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
@@ -57,14 +64,17 @@ class WatchlistLoggingHandler extends Handler {
private static final String DROPBOX_TAG = "network_watchlist_report";
private final Context mContext;
+ private final @Nullable DropBoxManager mDropBoxManager;
private final ContentResolver mResolver;
private final PackageManager mPm;
private final WatchlistReportDbHelper mDbHelper;
+ private final WatchlistConfig mConfig;
private final WatchlistSettings mSettings;
// A cache for uid and apk digest mapping.
// As uid won't be reused until reboot, it's safe to assume uid is unique per signature and app.
// TODO: Use more efficient data structure.
- private final HashMap<Integer, byte[]> mCachedUidDigestMap = new HashMap<>();
+ private final ConcurrentHashMap<Integer, byte[]> mCachedUidDigestMap =
+ new ConcurrentHashMap<>();
private interface WatchlistEventKeys {
String HOST = "host";
@@ -79,7 +89,9 @@ class WatchlistLoggingHandler extends Handler {
mPm = mContext.getPackageManager();
mResolver = mContext.getContentResolver();
mDbHelper = WatchlistReportDbHelper.getInstance(context);
+ mConfig = WatchlistConfig.getInstance();
mSettings = WatchlistSettings.getInstance();
+ mDropBoxManager = mContext.getSystemService(DropBoxManager.class);
}
@Override
@@ -162,69 +174,88 @@ class WatchlistLoggingHandler extends Handler {
}
private void tryAggregateRecords() {
- if (shouldReportNetworkWatchlist()) {
- Slog.i(TAG, "Start aggregating watchlist records.");
- final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
- if (dbox != null && !dbox.isTagEnabled(DROPBOX_TAG)) {
- final WatchlistReportDbHelper.AggregatedResult aggregatedResult =
- mDbHelper.getAggregatedRecords();
- final byte[] encodedResult = encodeAggregatedResult(aggregatedResult);
- if (encodedResult != null) {
- addEncodedReportToDropBox(encodedResult);
- }
- }
- mDbHelper.cleanup();
- Settings.Global.putLong(mResolver, Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
- System.currentTimeMillis());
- } else {
+ // Check if it's necessary to generate watchlist report now.
+ if (!shouldReportNetworkWatchlist()) {
Slog.i(TAG, "No need to aggregate record yet.");
+ return;
}
+ Slog.i(TAG, "Start aggregating watchlist records.");
+ if (mDropBoxManager != null && mDropBoxManager.isTagEnabled(DROPBOX_TAG)) {
+ Settings.Global.putLong(mResolver,
+ Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
+ System.currentTimeMillis());
+ final WatchlistReportDbHelper.AggregatedResult aggregatedResult =
+ mDbHelper.getAggregatedRecords();
+ // Get all digests for watchlist report, it should include all installed
+ // application digests and previously recorded app digests.
+ final List<String> digestsForReport = getAllDigestsForReport(aggregatedResult);
+ final byte[] secretKey = mSettings.getPrivacySecretKey();
+ final byte[] encodedResult = ReportEncoder.encodeWatchlistReport(mConfig,
+ secretKey, digestsForReport, aggregatedResult);
+ if (encodedResult != null) {
+ addEncodedReportToDropBox(encodedResult);
+ }
+ }
+ mDbHelper.cleanup();
}
- private byte[] encodeAggregatedResult(
- WatchlistReportDbHelper.AggregatedResult aggregatedResult) {
- // TODO: Encode results using differential privacy.
- return null;
+ /**
+ * Get all digests for watchlist report.
+ * It should include:
+ * (1) All installed app digests. We need this because we need to ensure after DP we don't know
+ * if an app is really visited C&C site.
+ * (2) App digests that previously recorded in database.
+ */
+ private List<String> getAllDigestsForReport(WatchlistReportDbHelper.AggregatedResult record) {
+ // Step 1: Get all installed application digests.
+ final List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
+ PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
+ final HashSet<String> result = new HashSet<>(apps.size() + record.appDigestCNCList.size());
+ final int size = apps.size();
+ for (int i = 0; i < size; i++) {
+ byte[] digest = getDigestFromUid(apps.get(i).uid);
+ result.add(HexDump.toHexString(digest));
+ }
+ // Step 2: Add all digests from records
+ result.addAll(record.appDigestCNCList.keySet());
+ return new ArrayList<>(result);
}
private void addEncodedReportToDropBox(byte[] encodedReport) {
- final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
- dbox.addData(DROPBOX_TAG, encodedReport, 0);
+ mDropBoxManager.addData(DROPBOX_TAG, encodedReport, 0);
}
/**
* Get app digest from app uid.
+ * Return null if system cannot get digest from uid.
*/
+ @Nullable
private byte[] getDigestFromUid(int uid) {
- final byte[] cachedDigest = mCachedUidDigestMap.get(uid);
- if (cachedDigest != null) {
- return cachedDigest;
- }
- final String[] packageNames = mPm.getPackagesForUid(uid);
- final int userId = UserHandle.getUserId(uid);
- if (!ArrayUtils.isEmpty(packageNames)) {
- for (String packageName : packageNames) {
- try {
- final String apkPath = mPm.getPackageInfoAsUser(packageName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId)
- .applicationInfo.publicSourceDir;
- if (TextUtils.isEmpty(apkPath)) {
- Slog.w(TAG, "Cannot find apkPath for " + packageName);
- continue;
+ return mCachedUidDigestMap.computeIfAbsent(uid, key -> {
+ final String[] packageNames = mPm.getPackagesForUid(key);
+ final int userId = UserHandle.getUserId(uid);
+ if (!ArrayUtils.isEmpty(packageNames)) {
+ for (String packageName : packageNames) {
+ try {
+ final String apkPath = mPm.getPackageInfoAsUser(packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId)
+ .applicationInfo.publicSourceDir;
+ if (TextUtils.isEmpty(apkPath)) {
+ Slog.w(TAG, "Cannot find apkPath for " + packageName);
+ continue;
+ }
+ return DigestUtils.getSha256Hash(new File(apkPath));
+ } catch (NameNotFoundException | NoSuchAlgorithmException | IOException e) {
+ Slog.e(TAG, "Should not happen", e);
+ return null;
}
- final byte[] digest = DigestUtils.getSha256Hash(new File(apkPath));
- mCachedUidDigestMap.put(uid, digest);
- return digest;
- } catch (NameNotFoundException | NoSuchAlgorithmException | IOException e) {
- Slog.e(TAG, "Should not happen", e);
- return null;
}
+ } else {
+ Slog.e(TAG, "Should not happen");
}
- } else {
- Slog.e(TAG, "Should not happen");
- }
- return null;
+ return null;
+ });
}
/**
@@ -247,7 +278,7 @@ class WatchlistLoggingHandler extends Handler {
if (ipAddr == null) {
return false;
}
- return mSettings.containsIp(ipAddr);
+ return mConfig.containsIp(ipAddr);
}
/** Search if the host is in watchlist */
@@ -255,7 +286,7 @@ class WatchlistLoggingHandler extends Handler {
if (host == null) {
return false;
}
- return mSettings.containsDomain(host);
+ return mConfig.containsDomain(host);
}
/**
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
index 838aa53938fa..9559685c8870 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
@@ -76,13 +76,20 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper {
*/
public static class AggregatedResult {
// A list of digests that visited c&c domain or ip before.
- Set<String> appDigestList;
+ final Set<String> appDigestList;
// The c&c domain or ip visited before.
- String cncDomainVisited;
+ final String cncDomainVisited;
// A list of app digests and c&c domain visited.
- HashMap<String, String> appDigestCNCList;
+ final HashMap<String, String> appDigestCNCList;
+
+ public AggregatedResult(Set<String> appDigestList, String cncDomainVisited,
+ HashMap<String, String> appDigestCNCList) {
+ this.appDigestList = appDigestList;
+ this.cncDomainVisited = cncDomainVisited;
+ this.appDigestCNCList = appDigestCNCList;
+ }
}
static File getSystemWatchlistDbFile() {
@@ -151,23 +158,21 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper {
if (c == null || c.getCount() == 0) {
return null;
}
- final AggregatedResult result = new AggregatedResult();
- result.cncDomainVisited = null;
- // After aggregation, each digest maximum will have only 1 record.
- result.appDigestList = new HashSet<>();
- result.appDigestCNCList = new HashMap<>();
+ final HashSet<String> appDigestList = new HashSet<>();
+ final HashMap<String, String> appDigestCNCList = new HashMap<>();
+ String cncDomainVisited = null;
while (c.moveToNext()) {
// We use hex string here as byte[] cannot be a key in HashMap.
String digestHexStr = HexDump.toHexString(c.getBlob(INDEX_DIGEST));
String cncDomain = c.getString(INDEX_CNC_DOMAIN);
- result.appDigestList.add(digestHexStr);
- if (result.cncDomainVisited != null) {
- result.cncDomainVisited = cncDomain;
+ appDigestList.add(digestHexStr);
+ if (cncDomainVisited != null) {
+ cncDomainVisited = cncDomain;
}
- result.appDigestCNCList.put(digestHexStr, cncDomain);
+ appDigestCNCList.put(digestHexStr, cncDomain);
}
- return result;
+ return new AggregatedResult(appDigestList, cncDomainVisited, appDigestCNCList);
} finally {
if (c != null) {
c.close();
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
index 70002ea21aff..b78fe4d2cca7 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import android.util.Log;
import android.util.Slog;
import android.util.Xml;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.HexDump;
@@ -35,199 +34,126 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.CRC32;
/**
- * A util class to do watchlist settings operations, like setting watchlist, query if a domain
- * exists in watchlist.
+ * Class for handling watchlist settings operations, like getting differential privacy secret key.
+ * Unlike WatchlistConfig, which will read configs that pushed from ConfigUpdater only, this class
+ * can read and write all settings for watchlist operations.
*/
class WatchlistSettings {
- private static final String TAG = "WatchlistSettings";
- // Watchlist config that pushed by ConfigUpdater.
- private static final String NETWORK_WATCHLIST_DB_PATH =
- "/data/misc/network_watchlist/network_watchlist.xml";
-
- private static class XmlTags {
- private static final String WATCHLIST_SETTINGS = "watchlist-settings";
- private static final String SHA256_DOMAIN = "sha256-domain";
- private static final String CRC32_DOMAIN = "crc32-domain";
- private static final String SHA256_IP = "sha256-ip";
- private static final String CRC32_IP = "crc32-ip";
- private static final String HASH = "hash";
- }
+ private static final String TAG = "WatchlistSettings";
- private static class CrcShaDigests {
- final HarmfulDigests crc32Digests;
- final HarmfulDigests sha256Digests;
-
- public CrcShaDigests(HarmfulDigests crc32Digests, HarmfulDigests sha256Digests) {
- this.crc32Digests = crc32Digests;
- this.sha256Digests = sha256Digests;
- }
- }
+ private static final String FILE_NAME = "watchlist_settings.xml";
+ // Rappor requires min entropy input size = 48 bytes
+ private static final int SECRET_KEY_LENGTH = 48;
private final static WatchlistSettings sInstance = new WatchlistSettings();
private final AtomicFile mXmlFile;
- private volatile CrcShaDigests mDomainDigests;
- private volatile CrcShaDigests mIpDigests;
+ private byte[] mPrivacySecretKey = null;
public static WatchlistSettings getInstance() {
return sInstance;
}
private WatchlistSettings() {
- this(new File(NETWORK_WATCHLIST_DB_PATH));
+ this(getSystemWatchlistFile());
+ }
+
+ static File getSystemWatchlistFile() {
+ return new File(Environment.getDataSystemDirectory(), FILE_NAME);
}
@VisibleForTesting
protected WatchlistSettings(File xmlFile) {
mXmlFile = new AtomicFile(xmlFile);
reloadSettings();
+ if (mPrivacySecretKey == null) {
+ // Generate a new secret key and save settings
+ mPrivacySecretKey = generatePrivacySecretKey();
+ saveSettings();
+ }
}
public void reloadSettings() {
try (FileInputStream stream = mXmlFile.openRead()){
-
- final List<byte[]> crc32DomainList = new ArrayList<>();
- final List<byte[]> sha256DomainList = new ArrayList<>();
- final List<byte[]> crc32IpList = new ArrayList<>();
- final List<byte[]> sha256IpList = new ArrayList<>();
-
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, StandardCharsets.UTF_8.name());
- parser.nextTag();
- parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
- while (parser.nextTag() == XmlPullParser.START_TAG) {
- String tagName = parser.getName();
- switch (tagName) {
- case XmlTags.CRC32_DOMAIN:
- parseHash(parser, tagName, crc32DomainList);
- break;
- case XmlTags.CRC32_IP:
- parseHash(parser, tagName, crc32IpList);
- break;
- case XmlTags.SHA256_DOMAIN:
- parseHash(parser, tagName, sha256DomainList);
- break;
- case XmlTags.SHA256_IP:
- parseHash(parser, tagName, sha256IpList);
- break;
- default:
- Log.w(TAG, "Unknown element: " + parser.getName());
- XmlUtils.skipCurrentTag(parser);
+ XmlUtils.beginDocument(parser, "network-watchlist-settings");
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals("secret-key")) {
+ mPrivacySecretKey = parseSecretKey(parser);
}
}
- parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
- writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
- Log.i(TAG, "Reload watchlist done");
+ Log.i(TAG, "Reload watchlist settings done");
} catch (IllegalStateException | NullPointerException | NumberFormatException |
XmlPullParserException | IOException | IndexOutOfBoundsException e) {
Slog.e(TAG, "Failed parsing xml", e);
}
}
- private void parseHash(XmlPullParser parser, String tagName, List<byte[]> hashSet)
+ private byte[] parseSecretKey(XmlPullParser parser)
throws IOException, XmlPullParserException {
- parser.require(XmlPullParser.START_TAG, null, tagName);
- while (parser.nextTag() == XmlPullParser.START_TAG) {
- parser.require(XmlPullParser.START_TAG, null, XmlTags.HASH);
- byte[] hash = HexDump.hexStringToByteArray(parser.nextText());
- parser.require(XmlPullParser.END_TAG, null, XmlTags.HASH);
- hashSet.add(hash);
+ parser.require(XmlPullParser.START_TAG, null, "secret-key");
+ byte[] key = HexDump.hexStringToByteArray(parser.nextText());
+ parser.require(XmlPullParser.END_TAG, null, "secret-key");
+ if (key == null || key.length != SECRET_KEY_LENGTH) {
+ Log.e(TAG, "Unable to parse secret key");
+ return null;
}
- parser.require(XmlPullParser.END_TAG, null, tagName);
+ return key;
}
/**
- * Write network watchlist settings to memory.
+ * Get DP secret key.
+ * Make sure it is not exported or logged in anywhere.
*/
- public void writeSettingsToMemory(List<byte[]> newCrc32DomainList,
- List<byte[]> newSha256DomainList,
- List<byte[]> newCrc32IpList,
- List<byte[]> newSha256IpList) {
- mDomainDigests = new CrcShaDigests(new HarmfulDigests(newCrc32DomainList),
- new HarmfulDigests(newSha256DomainList));
- mIpDigests = new CrcShaDigests(new HarmfulDigests(newCrc32IpList),
- new HarmfulDigests(newSha256IpList));
+ synchronized byte[] getPrivacySecretKey() {
+ final byte[] key = new byte[SECRET_KEY_LENGTH];
+ System.arraycopy(mPrivacySecretKey, 0, key, 0, SECRET_KEY_LENGTH);
+ return key;
}
- public boolean containsDomain(String domain) {
- final CrcShaDigests domainDigests = mDomainDigests;
- if (domainDigests == null) {
- Slog.wtf(TAG, "domainDigests should not be null");
- return false;
- }
- // First it does a quick CRC32 check.
- final byte[] crc32 = getCrc32(domain);
- if (!domainDigests.crc32Digests.contains(crc32)) {
- return false;
- }
- // Now we do a slow SHA256 check.
- final byte[] sha256 = getSha256(domain);
- return domainDigests.sha256Digests.contains(sha256);
+ private byte[] generatePrivacySecretKey() {
+ final byte[] key = new byte[SECRET_KEY_LENGTH];
+ (new SecureRandom()).nextBytes(key);
+ return key;
}
- public boolean containsIp(String ip) {
- final CrcShaDigests ipDigests = mIpDigests;
- if (ipDigests == null) {
- Slog.wtf(TAG, "ipDigests should not be null");
- return false;
- }
- // First it does a quick CRC32 check.
- final byte[] crc32 = getCrc32(ip);
- if (!ipDigests.crc32Digests.contains(crc32)) {
- return false;
+ private void saveSettings() {
+ FileOutputStream stream;
+ try {
+ stream = mXmlFile.startWrite();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to write display settings: " + e);
+ return;
}
- // Now we do a slow SHA256 check.
- final byte[] sha256 = getSha256(ip);
- return ipDigests.sha256Digests.contains(sha256);
- }
-
-
- /** Get CRC32 of a string
- *
- * TODO: Review if we should use CRC32 or other algorithms
- */
- private byte[] getCrc32(String str) {
- final CRC32 crc = new CRC32();
- crc.update(str.getBytes());
- final long tmp = crc.getValue();
- return new byte[]{(byte) (tmp >> 24 & 255), (byte) (tmp >> 16 & 255),
- (byte) (tmp >> 8 & 255), (byte) (tmp & 255)};
- }
-
- /** Get SHA256 of a string */
- private byte[] getSha256(String str) {
- MessageDigest messageDigest;
try {
- messageDigest = MessageDigest.getInstance("SHA256");
- } catch (NoSuchAlgorithmException e) {
- /* can't happen */
- return null;
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, "network-watchlist-settings");
+ out.startTag(null, "secret-key");
+ out.text(HexDump.toHexString(mPrivacySecretKey));
+ out.endTag(null, "secret-key");
+ out.endTag(null, "network-watchlist-settings");
+ out.endDocument();
+ mXmlFile.finishWrite(stream);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to write display settings, restoring backup.", e);
+ mXmlFile.failWrite(stream);
}
- messageDigest.update(str.getBytes());
- return messageDigest.digest();
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("Domain CRC32 digest list:");
- mDomainDigests.crc32Digests.dump(fd, pw, args);
- pw.println("Domain SHA256 digest list:");
- mDomainDigests.sha256Digests.dump(fd, pw, args);
- pw.println("Ip CRC32 digest list:");
- mIpDigests.crc32Digests.dump(fd, pw, args);
- pw.println("Ip SHA256 digest list:");
- mIpDigests.sha256Digests.dump(fd, pw, args);
}
}
diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsService.java b/services/core/java/com/android/server/pm/CrossProfileAppsService.java
index 0913269f35e1..027a302a325e 100644
--- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsService.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsService.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.pm.crossprofile;
+package com.android.server.pm;
import android.content.Context;
diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index a517d6d1a99e..2007a0e43aa1 100644
--- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.pm.crossprofile;
+package com.android.server.pm;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
@@ -25,14 +25,12 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ICrossProfileApps;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
-import android.content.pm.crossprofile.ICrossProfileApps;
-import android.graphics.Rect;
import android.os.Binder;
-import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index a43818a27a73..cc448daa2619 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -17,10 +17,12 @@
package com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
+import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA;
import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
@@ -96,6 +98,7 @@ import com.android.server.LocalServices;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
+import android.content.pm.dex.DexMetadataHelper;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -259,6 +262,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// entries like "lost+found".
if (file.isDirectory()) return false;
if (file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
+ if (DexMetadataHelper.isDexMetadataFile(file)) return false;
return true;
}
};
@@ -939,6 +943,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mInstallerPackageName, mInstallerUid, user, mSigningDetails);
}
+ private static void maybeRenameFile(File from, File to) throws PackageManagerException {
+ if (!from.equals(to)) {
+ if (!from.renameTo(to)) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Could not rename file " + from + " to " + to);
+ }
+ }
+ }
+
/**
* Validate install by confirming that all application packages are have
* consistent package name, version code, and signing certificates.
@@ -983,6 +996,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
}
+
// Verify that all staged packages are internally consistent
final ArraySet<String> stagedSplits = new ArraySet<>();
for (File addedFile : addedFiles) {
@@ -1013,9 +1027,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// Take this opportunity to enforce uniform naming
final String targetName;
if (apk.splitName == null) {
- targetName = "base.apk";
+ targetName = "base" + APK_FILE_EXTENSION;
} else {
- targetName = "split_" + apk.splitName + ".apk";
+ targetName = "split_" + apk.splitName + APK_FILE_EXTENSION;
}
if (!FileUtils.isValidExtFilename(targetName)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
@@ -1023,9 +1037,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
final File targetFile = new File(mResolvedStageDir, targetName);
- if (!addedFile.equals(targetFile)) {
- addedFile.renameTo(targetFile);
- }
+ maybeRenameFile(addedFile, targetFile);
// Base is coming from session
if (apk.splitName == null) {
@@ -1033,6 +1045,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
mResolvedStagedFiles.add(targetFile);
+
+ final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(addedFile);
+ if (dexMetadataFile != null) {
+ if (!FileUtils.isValidExtFilename(dexMetadataFile.getName())) {
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+ "Invalid filename: " + dexMetadataFile);
+ }
+ final File targetDexMetadataFile = new File(mResolvedStageDir,
+ DexMetadataHelper.buildDexMetadataPathForApk(targetName));
+ mResolvedStagedFiles.add(targetDexMetadataFile);
+ maybeRenameFile(dexMetadataFile, targetDexMetadataFile);
+ }
}
if (removeSplitList.size() > 0) {
@@ -1097,6 +1121,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (mResolvedBaseFile == null) {
mResolvedBaseFile = new File(appInfo.getBaseCodePath());
mResolvedInheritedFiles.add(mResolvedBaseFile);
+ // Inherit the dex metadata if present.
+ final File baseDexMetadataFile =
+ DexMetadataHelper.findDexMetadataForFile(mResolvedBaseFile);
+ if (baseDexMetadataFile != null) {
+ mResolvedInheritedFiles.add(baseDexMetadataFile);
+ }
}
// Inherit splits if not overridden
@@ -1107,6 +1137,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final boolean splitRemoved = removeSplitList.contains(splitName);
if (!stagedSplits.contains(splitName) && !splitRemoved) {
mResolvedInheritedFiles.add(splitFile);
+ // Inherit the dex metadata if present.
+ final File splitDexMetadataFile =
+ DexMetadataHelper.findDexMetadataForFile(splitFile);
+ if (splitDexMetadataFile != null) {
+ mResolvedInheritedFiles.add(splitDexMetadataFile);
+ }
}
}
}
@@ -1163,43 +1199,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
/**
- * Calculate the final install footprint size, combining both staged and
- * existing APKs together and including unpacked native code from both.
- */
- private long calculateInstalledSize() throws PackageManagerException {
- Preconditions.checkNotNull(mResolvedBaseFile);
-
- final ApkLite baseApk;
- try {
- baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0);
- } catch (PackageParserException e) {
- throw PackageManagerException.from(e);
- }
-
- final List<String> splitPaths = new ArrayList<>();
- for (File file : mResolvedStagedFiles) {
- if (mResolvedBaseFile.equals(file)) continue;
- splitPaths.add(file.getAbsolutePath());
- }
- for (File file : mResolvedInheritedFiles) {
- if (mResolvedBaseFile.equals(file)) continue;
- splitPaths.add(file.getAbsolutePath());
- }
-
- // This is kind of hacky; we're creating a half-parsed package that is
- // straddled between the inherited and staged APKs.
- final PackageLite pkg = new PackageLite(null, baseApk, null, null, null, null,
- splitPaths.toArray(new String[splitPaths.size()]), null);
-
- try {
- return PackageHelper.calculateInstalledSize(pkg, params.abiOverride);
- } catch (IOException e) {
- throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
- "Failed to calculate install size", e);
- }
- }
-
- /**
* Determine if creating hard links between source and destination is
* possible. That is, do they all live on the same underlying device.
*/
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 711ea13a7c15..2585cf3d7fa5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -191,6 +191,7 @@ import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VerifierInfo;
import android.content.pm.VersionedPackage;
+import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.dex.IArtManager;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -16486,6 +16487,7 @@ Slog.e("TODD",
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
+ DexMetadataHelper.validatePackageDexMetadata(pkg);
} catch (PackageParserException e) {
res.setError("Failed parse during installPackageLI", e);
return;
@@ -18898,6 +18900,14 @@ Slog.e("TODD",
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
+ private int getPackageTargetSdkVersionLockedLPr(String packageName) {
+ final PackageParser.Package p = mPackages.get(packageName);
+ if (p != null) {
+ return p.applicationInfo.targetSdkVersion;
+ }
+ return Build.VERSION_CODES.CUR_DEVELOPMENT;
+ }
+
@Override
public void addPreferredActivity(IntentFilter filter, int match,
ComponentName[] set, ComponentName activity, int userId) {
@@ -23417,6 +23427,13 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
@Override
+ public int getPackageTargetSdkVersion(String packageName) {
+ synchronized (mPackages) {
+ return getPackageTargetSdkVersionLockedLPr(packageName);
+ }
+ }
+
+ @Override
public boolean canAccessInstantApps(int callingUid, int userId) {
return PackageManagerService.this.canViewInstantApps(callingUid, userId);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 11b804111e61..a33f0716c4cb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -47,6 +47,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.pm.VersionedPackage;
+import android.content.pm.dex.DexMetadataHelper;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.net.Uri;
@@ -80,7 +81,6 @@ import dalvik.system.DexFile;
import libcore.io.IoUtils;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -2258,6 +2258,14 @@ class PackageManagerShellCommand extends ShellCommand {
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
+ // Sanity check that all .dm files match an apk.
+ // (The installer does not support standalone .dm files and will not process them.)
+ try {
+ DexMetadataHelper.validateDexPaths(session.getNames());
+ } catch (IllegalStateException | IOException e) {
+ pw.println("Warning [Could not validate the dex paths: " + e.getMessage() + "]");
+ }
+
final LocalIntentReceiver receiver = new LocalIntentReceiver();
session.commit(receiver.getIntentSender());
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index de723c6701d3..d84fbc53c5f9 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -185,6 +185,14 @@ public class VrManagerService extends SystemService
ComponentName component = null;
synchronized (mLock) {
component = ((mCurrentVrService == null) ? null : mCurrentVrService.getComponent());
+
+ // If the VrCore main service was disconnected or the binding died we'll rebind
+ // automatically. Call focusedActivityChanged() once we rebind.
+ if (component != null && component.equals(event.component) &&
+ (event.event == LogEvent.EVENT_DISCONNECTED ||
+ event.event == LogEvent.EVENT_BINDING_DIED)) {
+ callFocusedActivityChangedLocked();
+ }
}
// If not on an AIO device and we permanently stopped trying to connect to the
@@ -980,16 +988,7 @@ public class VrManagerService extends SystemService
oldVrServicePackage, oldUserId);
if (mCurrentVrService != null && sendUpdatedCaller) {
- final ComponentName c = mCurrentVrModeComponent;
- final boolean b = running2dInVr;
- final int pid = processId;
- mCurrentVrService.sendEvent(new PendingEvent() {
- @Override
- public void runEvent(IInterface service) throws RemoteException {
- IVrListener l = (IVrListener) service;
- l.focusedActivityChanged(c, b, pid);
- }
- });
+ callFocusedActivityChangedLocked();
}
if (!nothingChanged) {
@@ -1002,6 +1001,23 @@ public class VrManagerService extends SystemService
}
}
+ private void callFocusedActivityChangedLocked() {
+ final ComponentName c = mCurrentVrModeComponent;
+ final boolean b = mRunning2dInVr;
+ final int pid = mVrAppProcessId;
+ mCurrentVrService.sendEvent(new PendingEvent() {
+ @Override
+ public void runEvent(IInterface service) throws RemoteException {
+ // Under specific (and unlikely) timing scenarios, when VrCore
+ // crashes and is rebound, focusedActivityChanged() may be
+ // called a 2nd time with the same arguments. IVrListeners
+ // should make sure to handle that scenario gracefully.
+ IVrListener l = (IVrListener) service;
+ l.focusedActivityChanged(c, b, pid);
+ }
+ });
+ }
+
private boolean isDefaultAllowed(String packageName) {
PackageManager pm = mContext.getPackageManager();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ef486614fd2a..fba404ed6f0e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -134,6 +134,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.MutableBoolean;
import android.util.Slog;
@@ -330,6 +331,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
final PinnedStackController mPinnedStackControllerLocked;
final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
+ /** A collection of windows that provide tap exclude regions inside of them. */
+ final ArraySet<WindowState> mTapExcludeProvidingWindows = new ArraySet<>();
private boolean mHaveBootMsg = false;
private boolean mHaveApp = false;
@@ -1866,10 +1869,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
}
for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) {
- WindowState win = mTapExcludedWindows.get(i);
+ final WindowState win = mTapExcludedWindows.get(i);
win.getTouchableRegion(mTmpRegion);
mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
}
+ for (int i = mTapExcludeProvidingWindows.size() - 1; i >= 0; i--) {
+ final WindowState win = mTapExcludeProvidingWindows.valueAt(i);
+ win.amendTapExcludeRegion(mTouchExcludeRegion);
+ }
// TODO(multi-display): Support docked stacks on secondary displays.
if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStack() != null) {
mDividerControllerLocked.getTouchRegion(mTmpRect);
@@ -3690,6 +3697,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
.setParent(mOverlayLayer);
}
+ /**
+ * Reparents the given surface to mOverlayLayer.
+ */
+ void reparentToOverlay(Transaction transaction, SurfaceControl surface) {
+ transaction.reparent(surface, mOverlayLayer.getHandle());
+ }
+
void applyMagnificationSpec(MagnificationSpec spec) {
applyMagnificationSpec(getPendingTransaction(), spec);
getPendingTransaction().apply();
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 0cf8d2f85b62..36e5d100edb6 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -454,8 +454,11 @@ public class DockedStackDividerController {
inputMethodManagerInternal.hideCurrentInputMethod();
mImeHideRequested = true;
}
+
+ // If a primary stack was just created, it will not have access to display content at
+ // this point so pass it from here to get a valid dock side.
final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
- mOriginalDockedSide = stack.getDockSide();
+ mOriginalDockedSide = stack.getDockSideForDisplay(mDisplayContent);
return;
}
mOriginalDockedSide = DOCKED_INVALID;
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 0171b56ffc47..d55a64926504 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -18,12 +18,10 @@ package com.android.server.wm;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.content.ClipData;
-import android.graphics.PixelFormat;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -32,8 +30,8 @@ import android.os.Message;
import android.util.Slog;
import android.view.Display;
import android.view.IWindow;
-import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.View;
@@ -50,10 +48,9 @@ class DragDropController {
private static final long DRAG_TIMEOUT_MS = 5000;
// Messages for Handler.
- private static final int MSG_DRAG_START_TIMEOUT = 0;
- static final int MSG_DRAG_END_TIMEOUT = 1;
- static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 2;
- static final int MSG_ANIMATION_END = 3;
+ static final int MSG_DRAG_END_TIMEOUT = 0;
+ static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1;
+ static final int MSG_ANIMATION_END = 2;
/**
* Drag state per operation.
@@ -95,87 +92,35 @@ class DragDropController {
mDragState.sendDragStartedIfNeededLocked(window);
}
- IBinder prepareDrag(SurfaceSession session, int callerPid,
- int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) {
+ IBinder performDrag(SurfaceSession session, int callerPid, int callerUid, IWindow window,
+ int flags, SurfaceControl surface, int touchSource, float touchX, float touchY,
+ float thumbCenterX, float thumbCenterY, ClipData data) {
if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
- + " flags=" + Integer.toHexString(flags) + " win=" + window
- + " asbinder=" + window.asBinder());
- }
-
- if (width <= 0 || height <= 0) {
- Slog.w(TAG_WM, "width and height of drag shadow must be positive");
- return null;
- }
-
- synchronized (mService.mWindowMap) {
- if (dragDropActiveLocked()) {
- Slog.w(TAG_WM, "Drag already in progress");
- return null;
- }
-
- // TODO(multi-display): support other displays
- final DisplayContent displayContent =
- mService.getDefaultDisplayContentLocked();
- final Display display = displayContent.getDisplay();
-
- final SurfaceControl surface = new SurfaceControl.Builder(session)
- .setName("drag surface")
- .setSize(width, height)
- .setFormat(PixelFormat.TRANSLUCENT)
- .build();
- surface.setLayerStack(display.getLayerStack());
- float alpha = 1;
- if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
- alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
- }
- surface.setAlpha(alpha);
-
- if (SHOW_TRANSACTIONS)
- Slog.i(TAG_WM, " DRAG " + surface + ": CREATE");
- outSurface.copyFrom(surface);
- final IBinder winBinder = window.asBinder();
- IBinder token = new Binder();
- mDragState = new DragState(mService, this, token, surface, flags, winBinder);
- mDragState.mPid = callerPid;
- mDragState.mUid = callerUid;
- mDragState.mOriginalAlpha = alpha;
- token = mDragState.mToken = new Binder();
-
- // 5 second timeout for this window to actually begin the drag
- sendTimeoutMessage(MSG_DRAG_START_TIMEOUT, winBinder);
- return token;
- }
- }
-
- boolean performDrag(IWindow window, IBinder dragToken,
- int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
- ClipData data) {
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
+ Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" +
+ Integer.toHexString(flags) + " data=" + data);
}
+ final IBinder dragToken = new Binder();
final boolean callbackResult = mCallback.get().prePerformDrag(window, dragToken,
touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data);
try {
synchronized (mService.mWindowMap) {
- mHandler.removeMessages(MSG_DRAG_START_TIMEOUT, window.asBinder());
try {
if (!callbackResult) {
- return false;
+ Slog.w(TAG_WM, "IDragDropCallback rejects the performDrag request");
+ return null;
}
- Preconditions.checkState(
- mDragState != null, "performDrag() without prepareDrag()");
- Preconditions.checkState(
- mDragState.mToken == dragToken,
- "performDrag() does not match prepareDrag()");
+ if (dragDropActiveLocked()) {
+ Slog.w(TAG_WM, "Drag already in progress");
+ return null;
+ }
final WindowState callingWin = mService.windowForClientLocked(
null, window, false);
if (callingWin == null) {
Slog.w(TAG_WM, "Bad requesting window " + window);
- return false; // !!! TODO: throw here?
+ return null; // !!! TODO: throw here?
}
// !!! TODO: if input is not still focused on the initiating window, fail
@@ -188,18 +133,31 @@ class DragDropController {
// !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
// the actual drag event dispatch stuff in the dragstate
+ // !!! TODO(multi-display): support other displays
+
final DisplayContent displayContent = callingWin.getDisplayContent();
if (displayContent == null) {
Slog.w(TAG_WM, "display content is null");
- return false;
+ return null;
}
+ final float alpha = (flags & View.DRAG_FLAG_OPAQUE) == 0 ?
+ DRAG_SHADOW_ALPHA_TRANSPARENT : 1;
+ final IBinder winBinder = window.asBinder();
+ IBinder token = new Binder();
+ mDragState = new DragState(mService, this, token, surface, flags, winBinder);
+ surface = null;
+ mDragState.mPid = callerPid;
+ mDragState.mUid = callerUid;
+ mDragState.mOriginalAlpha = alpha;
+ mDragState.mToken = dragToken;
+
final Display display = displayContent.getDisplay();
mDragState.register(display);
if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
mDragState.getInputChannel())) {
Slog.e(TAG_WM, "Unable to transfer touch focus");
- return false;
+ return null;
}
mDragState.mDisplayContent = displayContent;
@@ -213,28 +171,31 @@ class DragDropController {
// Make the surface visible at the proper location
final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
- mService.openSurfaceTransaction();
- try {
- surfaceControl.setPosition(touchX - thumbCenterX,
- touchY - thumbCenterY);
- surfaceControl.setLayer(mDragState.getDragLayerLocked());
- surfaceControl.setLayerStack(display.getLayerStack());
- surfaceControl.show();
- } finally {
- mService.closeSurfaceTransaction("performDrag");
- if (SHOW_LIGHT_TRANSACTIONS) {
- Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
- }
+
+ final SurfaceControl.Transaction transaction =
+ callingWin.getPendingTransaction();
+ transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
+ transaction.setPosition(
+ surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY);
+ transaction.show(surfaceControl);
+ displayContent.reparentToOverlay(transaction, surfaceControl);
+ callingWin.scheduleAnimation();
+
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
}
mDragState.notifyLocationLocked(touchX, touchY);
} finally {
+ if (surface != null) {
+ surface.release();
+ }
if (mDragState != null && !mDragState.isInProgress()) {
mDragState.closeLocked();
}
}
}
- return true; // success!
+ return dragToken; // success!
} finally {
mCallback.get().postPerformDrag();
}
@@ -385,21 +346,6 @@ class DragDropController {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_DRAG_START_TIMEOUT: {
- IBinder win = (IBinder) msg.obj;
- if (DEBUG_DRAG) {
- Slog.w(TAG_WM, "Timeout starting drag by win " + win);
- }
-
- synchronized (mService.mWindowMap) {
- // !!! TODO: ANR the app that has failed to start the drag in time
- if (mDragState != null) {
- mDragState.closeLocked();
- }
- }
- break;
- }
-
case MSG_DRAG_END_TIMEOUT: {
final IBinder win = (IBinder) msg.obj;
if (DEBUG_DRAG) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 192d6c84e190..04ae38ec33b1 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -51,6 +51,7 @@ import android.view.IWindowSession;
import android.view.IWindowSessionCallback;
import android.view.InputChannel;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
@@ -308,30 +309,22 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
/* Drag/drop */
+
@Override
- public IBinder prepareDrag(IWindow window, int flags, int width, int height,
- Surface outSurface) {
+ public IBinder performDrag(IWindow window, int flags, SurfaceControl surface, int touchSource,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
final int callerPid = Binder.getCallingPid();
final int callerUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- return mDragDropController.prepareDrag(
- mSurfaceSession, callerPid, callerUid, window, flags, width, height,
- outSurface);
+ return mDragDropController.performDrag(mSurfaceSession, callerPid, callerUid, window,
+ flags, surface, touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
- public boolean performDrag(IWindow window, IBinder dragToken,
- int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
- ClipData data) {
- return mDragDropController.performDrag(window, dragToken, touchSource,
- touchX, touchY, thumbCenterX, thumbCenterY, data);
- }
-
- @Override
public void reportDropResult(IWindow window, boolean consumed) {
final long ident = Binder.clearCallingIdentity();
try {
@@ -467,6 +460,17 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
}
+ @Override
+ public void updateTapExcludeRegion(IWindow window, int regionId, int left, int top, int width,
+ int height) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mService.updateTapExcludeRegion(window, regionId, left, top, width, height);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
void windowAddedLocked(String packageName) {
mPackageName = packageName;
mRelayoutTag = "relayoutWindow: " + mPackageName;
diff --git a/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java b/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java
new file mode 100644
index 000000000000..cbc936f2f1d7
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.SparseArray;
+
+/**
+ * A holder that contains a collection of rectangular areas identified by int id. Each individual
+ * region can be updated separately.
+ */
+class TapExcludeRegionHolder {
+ private SparseArray<Rect> mTapExcludeRects = new SparseArray<>();
+
+ /** Update the specified region with provided position and size. */
+ void updateRegion(int regionId, int left, int top, int width, int height) {
+ if (width <= 0 || height <= 0) {
+ // A region became empty - remove it.
+ mTapExcludeRects.remove(regionId);
+ return;
+ }
+
+ Rect region = mTapExcludeRects.get(regionId);
+ if (region == null) {
+ region = new Rect();
+ }
+ region.set(left, top, left + width, top + height);
+ mTapExcludeRects.put(regionId, region);
+ }
+
+ /**
+ * Union the provided region with current region formed by this container.
+ */
+ void amendRegion(Region region, Rect boundingRegion) {
+ for (int i = mTapExcludeRects.size() - 1; i>= 0 ; --i) {
+ final Rect rect = mTapExcludeRects.valueAt(i);
+ rect.intersect(boundingRegion);
+ region.union(rect);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index fa7ea2ffdd17..26c87b738f8c 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -59,6 +59,8 @@ class TaskPositioner {
private static final String TAG_LOCAL = "TaskPositioner";
private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
+ private static Factory sFactory;
+
// The margin the pointer position has to be within the side of the screen to be
// considered at the side of the screen.
static final int SIDE_MARGIN_DIP = 100;
@@ -214,6 +216,7 @@ class TaskPositioner {
}
}
+ /** Use {@link #create(WindowManagerService)} instead **/
TaskPositioner(WindowManagerService service) {
mService = service;
}
@@ -622,4 +625,22 @@ class TaskPositioner {
public String toShortString() {
return TAG;
}
+
+ static void setFactory(Factory factory) {
+ sFactory = factory;
+ }
+
+ static TaskPositioner create(WindowManagerService service) {
+ if (sFactory == null) {
+ sFactory = new Factory() {};
+ }
+
+ return sFactory.create(service);
+ }
+
+ interface Factory {
+ default TaskPositioner create(WindowManagerService service) {
+ return new TaskPositioner(service);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index 4dfe290c4f7d..a3f4ee80dfcb 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -126,7 +126,7 @@ class TaskPositioningController {
}
Display display = displayContent.getDisplay();
- mTaskPositioner = new TaskPositioner(mService);
+ mTaskPositioner = TaskPositioner.create(mService);
mTaskPositioner.register(displayContent);
mInputMonitor.updateInputWindowsLw(true /*force*/);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 8a36226aeaa3..bc0f9ad71a61 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1397,15 +1397,23 @@ public class TaskStack extends WindowContainer<Task> implements
return getDockSide(getRawBounds());
}
+ int getDockSideForDisplay(DisplayContent dc) {
+ return getDockSide(dc, getRawBounds());
+ }
+
private int getDockSide(Rect bounds) {
- if (!inSplitScreenWindowingMode()) {
+ if (mDisplayContent == null) {
return DOCKED_INVALID;
}
- if (mDisplayContent == null) {
+ return getDockSide(mDisplayContent, bounds);
+ }
+
+ private int getDockSide(DisplayContent dc, Rect bounds) {
+ if (!inSplitScreenWindowingMode()) {
return DOCKED_INVALID;
}
- mDisplayContent.getBounds(mTmpRect);
- final int orientation = mDisplayContent.getConfiguration().orientation;
+ dc.getBounds(mTmpRect);
+ final int orientation = dc.getConfiguration().orientation;
return getDockSideUnchecked(bounds, mTmpRect, orientation);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 10e789345232..53086f714a79 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6922,6 +6922,23 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /**
+ * Update a tap exclude region with a rectangular area in the window identified by the provided
+ * id. Touches on this region will not switch focus to this window. Passing an empty rect will
+ * remove the area from the exclude region of this window.
+ */
+ void updateTapExcludeRegion(IWindow client, int regionId, int left, int top, int width,
+ int height) {
+ synchronized (mWindowMap) {
+ final WindowState callingWin = windowForClientLocked(null, client, false);
+ if (callingWin == null) {
+ Slog.w(TAG_WM, "Bad requesting window " + client);
+ return;
+ }
+ callingWin.updateTapExcludeRegion(regionId, left, top, width, height);
+ }
+ }
+
@Override
public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
throws RemoteException {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index db30db074838..477dd2bb9633 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -630,6 +630,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
private final Point mSurfacePosition = new Point();
/**
+ * A region inside of this window to be excluded from touch-related focus switches.
+ */
+ private TapExcludeRegionHolder mTapExcludeRegionHolder;
+
+ /**
* Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
*/
@@ -1870,6 +1875,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
dc.mTapExcludedWindows.remove(this);
}
+ if (mTapExcludeRegionHolder != null) {
+ // If a tap exclude region container was initialized for this window, then it should've
+ // also been registered in display.
+ dc.mTapExcludeProvidingWindows.remove(this);
+ }
mPolicy.removeWindowLw(this);
disposeInputChannel();
@@ -4620,6 +4630,37 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
+ /**
+ * Update a tap exclude region with a rectangular area identified by provided id. The requested
+ * area will be clipped to the window bounds.
+ */
+ void updateTapExcludeRegion(int regionId, int left, int top, int width, int height) {
+ final DisplayContent currentDisplay = getDisplayContent();
+ if (currentDisplay == null) {
+ throw new IllegalStateException("Trying to update window not attached to any display.");
+ }
+
+ if (mTapExcludeRegionHolder == null) {
+ mTapExcludeRegionHolder = new TapExcludeRegionHolder();
+
+ // Make sure that this window is registered as one that provides a tap exclude region
+ // for its containing display.
+ currentDisplay.mTapExcludeProvidingWindows.add(this);
+ }
+
+ mTapExcludeRegionHolder.updateRegion(regionId, left, top, width, height);
+ // Trigger touch exclude region update on current display.
+ final boolean isAppFocusedOnDisplay = mService.mFocusedApp != null
+ && mService.mFocusedApp.getDisplayContent() == currentDisplay;
+ currentDisplay.setTouchExcludeRegion(isAppFocusedOnDisplay ? mService.mFocusedApp.getTask()
+ : null);
+ }
+
+ /** Union the region with current tap exclude region that this window provides. */
+ void amendTapExcludeRegion(Region region) {
+ mTapExcludeRegionHolder.amendRegion(region, getBounds());
+ }
+
private final class MoveAnimationSpec implements AnimationSpec {
private final long mDuration;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 384b416b0201..99275e88a242 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -126,4 +126,19 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
public String getEndUserSessionMessage(ComponentName admin) {
return null;
}
+
+ @Override
+ public void setPrintingEnabled(ComponentName admin, boolean enabled) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isPrintingEnabled() {
+ return true;
+ }
+
+ @Override
+ public CharSequence getPrintingDisabledReason() {
+ return null;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index cb4f5c1177df..956b185be19e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -74,6 +74,7 @@ import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityThread;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.IActivityManager;
@@ -285,6 +286,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private static final String TAG_PASSWORD_VALIDITY = "password-validity";
+ private static final String TAG_PRINTING_ENABLED = "printing-enabled";
+
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
@@ -589,6 +592,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
long mPasswordTokenHandle = 0;
+ boolean mPrintingEnabled = true;
+
public DevicePolicyData(int userHandle) {
mUserHandle = userHandle;
}
@@ -2880,6 +2885,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
out.endTag(null, TAG_CURRENT_INPUT_METHOD_SET);
}
+ if (!policy.mPrintingEnabled) {
+ out.startTag(null, TAG_PRINTING_ENABLED);
+ out.attribute(null, ATTR_VALUE, Boolean.toString(policy.mPrintingEnabled));
+ out.endTag(null, TAG_PRINTING_ENABLED);
+ }
+
for (final String cert : policy.mOwnerInstalledCaCerts) {
out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
out.attribute(null, ATTR_ALIAS, cert);
@@ -3098,6 +3109,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
policy.mCurrentInputMethodSet = true;
} else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
+ } else if (TAG_PRINTING_ENABLED.equals(tag)) {
+ String enabled = parser.getAttributeValue(null, ATTR_VALUE);
+ policy.mPrintingEnabled = Boolean.toString(true).equals(enabled);
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -12274,4 +12288,93 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return deviceOwner.endUserSessionMessage;
}
}
+
+ private boolean hasPrinting() {
+ return mInjector.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PRINTING);
+ }
+
+ @Override
+ public void setPrintingEnabled(ComponentName admin, boolean enabled) {
+ if (!mHasFeature || !hasPrinting()) {
+ return;
+ }
+ Preconditions.checkNotNull(admin, "Admin cannot be null.");
+ enforceProfileOrDeviceOwner(admin);
+ synchronized (this) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ DevicePolicyData policy = getUserData(userHandle);
+ if (policy.mPrintingEnabled != enabled) {
+ policy.mPrintingEnabled = enabled;
+ saveSettingsLocked(userHandle);
+ }
+ }
+ }
+
+ /**
+ * Returns whether printing is enabled for current user.
+ * @hide
+ */
+ @Override
+ public boolean isPrintingEnabled() {
+ if (!hasPrinting()) {
+ return false;
+ }
+ if (!mHasFeature) {
+ return true;
+ }
+ synchronized (this) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ DevicePolicyData policy = getUserData(userHandle);
+ return policy.mPrintingEnabled;
+ }
+ }
+
+ /**
+ * Returns text of error message if printing is disabled.
+ * Only to be called by Print Service.
+ * @hide
+ */
+ @Override
+ public CharSequence getPrintingDisabledReason() {
+ if (!hasPrinting() || !mHasFeature) {
+ Log.e(LOG_TAG, "no printing or no management");
+ return null;
+ }
+ synchronized (this) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ DevicePolicyData policy = getUserData(userHandle);
+ if (policy.mPrintingEnabled) {
+ Log.e(LOG_TAG, "printing is enabled");
+ return null;
+ }
+ String ownerPackage = mOwners.getProfileOwnerPackage(userHandle);
+ if (ownerPackage == null) {
+ ownerPackage = mOwners.getDeviceOwnerPackageName();
+ }
+ PackageManager pm = mInjector.getPackageManager();
+ PackageInfo packageInfo;
+ try {
+ packageInfo = pm.getPackageInfo(ownerPackage, 0);
+ } catch (NameNotFoundException e) {
+ Log.e(LOG_TAG, "getPackageInfo error", e);
+ return null;
+ }
+ if (packageInfo == null) {
+ Log.e(LOG_TAG, "packageInfo is inexplicably null");
+ return null;
+ }
+ ApplicationInfo appInfo = packageInfo.applicationInfo;
+ if (appInfo == null) {
+ Log.e(LOG_TAG, "appInfo is inexplicably null");
+ return null;
+ }
+ CharSequence appLabel = pm.getApplicationLabel(appInfo);
+ if (appLabel == null) {
+ Log.e(LOG_TAG, "appLabel is inexplicably null");
+ return null;
+ }
+ return ((Context) ActivityThread.currentActivityThread().getSystemUiContext())
+ .getResources().getString(R.string.printing_disabled_by, appLabel);
+ }
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3199bfa49455..e660c50fcbc1 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -46,10 +46,10 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.IStorageManager;
-import android.util.TimingsTraceLog;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
+import android.util.TimingsTraceLog;
import android.view.WindowManager;
import com.android.internal.R;
@@ -57,20 +57,21 @@ import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
-import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.ConcurrentUtils;
+import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.widget.ILockSettings;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.am.ActivityManagerService;
import com.android.server.audio.AudioService;
+import com.android.server.broadcastradio.BroadcastRadioService;
import com.android.server.camera.CameraServiceProxy;
import com.android.server.car.CarServiceHelperService;
import com.android.server.clipboard.ClipboardService;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.coverage.CoverageService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
-import com.android.server.display.DisplayManagerService;
import com.android.server.display.ColorDisplayService;
+import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
import com.android.server.fingerprint.FingerprintService;
@@ -92,17 +93,16 @@ import com.android.server.om.OverlayManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.SchedulingPolicyService;
import com.android.server.pm.BackgroundDexOptService;
+import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.Installer;
import com.android.server.pm.LauncherAppsService;
import com.android.server.pm.OtaDexoptService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.ShortcutService;
import com.android.server.pm.UserManagerService;
-import com.android.server.pm.crossprofile.CrossProfileAppsService;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
-import com.android.server.broadcastradio.BroadcastRadioService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index cd4e8f977d60..89a5fe1b82c7 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -32,6 +33,7 @@ import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -54,6 +56,7 @@ import android.service.print.PrintServiceDumpProto;
import android.util.Log;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import android.widget.Toast;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
@@ -110,9 +113,12 @@ public final class PrintManagerService extends SystemService {
private final SparseArray<UserState> mUserStates = new SparseArray<>();
+ private final DevicePolicyManager mDpc;
+
PrintManagerImpl(Context context) {
mContext = context;
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mDpc = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
registerContentObservers();
registerBroadcastReceivers();
}
@@ -120,8 +126,26 @@ public final class PrintManagerService extends SystemService {
@Override
public Bundle print(String printJobName, IPrintDocumentAdapter adapter,
PrintAttributes attributes, String packageName, int appId, int userId) {
- printJobName = Preconditions.checkStringNotEmpty(printJobName);
adapter = Preconditions.checkNotNull(adapter);
+ if (!isPrintingEnabled()) {
+ final CharSequence disabledMessage = mDpc.getPrintingDisabledReason();
+ if (disabledMessage != null) {
+ Toast.makeText(mContext, Looper.getMainLooper(), disabledMessage,
+ Toast.LENGTH_LONG).show();
+ }
+ try {
+ adapter.start();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling IPrintDocumentAdapter.start()");
+ }
+ try {
+ adapter.finish();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling IPrintDocumentAdapter.finish()");
+ }
+ return null;
+ }
+ printJobName = Preconditions.checkStringNotEmpty(printJobName);
packageName = Preconditions.checkStringNotEmpty(packageName);
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
@@ -240,7 +264,8 @@ public final class PrintManagerService extends SystemService {
@Override
public void restartPrintJob(PrintJobId printJobId, int appId, int userId) {
- if (printJobId == null) {
+ if (printJobId == null || !isPrintingEnabled()) {
+ // if printing is disabled the state just remains "failed".
return;
}
@@ -685,6 +710,10 @@ public final class PrintManagerService extends SystemService {
}
}
+ private boolean isPrintingEnabled() {
+ return mDpc == null || mDpc.isPrintingEnabled();
+ }
+
private void dump(@NonNull DualDumpOutputStream dumpStream,
@NonNull ArrayList<UserState> userStatesToDump) {
final int userStateCount = userStatesToDump.size();
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index cf0bc235dc10..6753d73590d3 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -19,9 +19,13 @@ package com.android.server.backup;
import static com.android.server.backup.testing.TransportData.genericTransport;
import static com.android.server.backup.testing.TransportTestUtils.mockTransport;
import static com.android.server.backup.testing.TransportTestUtils.setUpTransportsForTransportManager;
-
import static com.google.common.truth.Truth.assertThat;
-
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Stream.concat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -31,23 +35,15 @@ import static org.mockito.Mockito.when;
import static org.robolectric.shadow.api.Shadow.extract;
import static org.testng.Assert.expectThrows;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toSet;
-import static java.util.stream.Stream.concat;
-
import android.annotation.Nullable;
+import android.app.backup.BackupManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.platform.test.annotations.Presubmit;
-
import com.android.server.backup.testing.ShadowContextImplForBackup;
-import com.android.server.testing.shadows.FrameworkShadowPackageManager;
import com.android.server.backup.testing.TransportData;
import com.android.server.backup.testing.TransportTestUtils.TransportMock;
import com.android.server.backup.transport.OnTransportRegisteredListener;
@@ -57,7 +53,12 @@ import com.android.server.backup.transport.TransportNotRegisteredException;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderClasses;
import com.android.server.testing.shadows.FrameworkShadowContextImpl;
-
+import com.android.server.testing.shadows.FrameworkShadowPackageManager;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -68,12 +69,6 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowPackageManager;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Stream;
-
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(
manifest = Config.NONE,
@@ -308,6 +303,43 @@ public class TransportManagerTest {
}
@Test
+ public void testRegisterAndSelectTransport_whenTransportRegistered() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(null, mTransportA1);
+ transportManager.registerTransports();
+ ComponentName transportComponent = mTransportA1.getTransportComponent();
+
+ int result = transportManager.registerAndSelectTransport(transportComponent);
+
+ assertThat(result).isEqualTo(BackupManager.SUCCESS);
+ assertThat(transportManager.getRegisteredTransportComponents())
+ .asList()
+ .contains(transportComponent);
+ assertThat(transportManager.getCurrentTransportName())
+ .isEqualTo(mTransportA1.transportName);
+ }
+
+ @Test
+ public void testRegisterAndSelectTransport_whenTransportNotRegistered() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(null, mTransportA1);
+ ComponentName transportComponent = mTransportA1.getTransportComponent();
+
+ int result = transportManager.registerAndSelectTransport(transportComponent);
+
+ assertThat(result).isEqualTo(BackupManager.SUCCESS);
+ assertThat(transportManager.getRegisteredTransportComponents())
+ .asList()
+ .contains(transportComponent);
+ assertThat(transportManager.getTransportDirName(mTransportA1.transportName))
+ .isEqualTo(mTransportA1.transportDirName);
+ assertThat(transportManager.getCurrentTransportName())
+ .isEqualTo(mTransportA1.transportName);
+ }
+
+ @Test
public void testGetCurrentTransportName_whenSelectTransportNotCalled_returnsDefaultTransport()
throws Exception {
setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
diff --git a/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_config_test1.xml b/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_config_test1.xml
new file mode 100644
index 000000000000..e31fad834023
--- /dev/null
+++ b/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_config_test1.xml
@@ -0,0 +1,27 @@
+<?xml version='1.0'?>
+<watchlist-config>
+ <sha256-domain>
+ <!-- test-cc-domain.com -->
+ <hash>8E7DCD2AEB4F364358242BB3F403263E61E3B4AECE4E2500FF28BF32E52FF0F1</hash>
+ <!-- test-cc-match-sha256-only.com -->
+ <hash>F0905DA7549614957B449034C281EF7BDEFDBC2B6E050AD1E78D6DE18FBD0D5F</hash>
+ </sha256-domain>
+ <sha256-ip>
+ <!-- 127.0.0.2 -->
+ <hash>1EDD62868F2767A1FFF68DF0A4CB3C23448E45100715768DB9310B5E719536A1</hash>
+ <!-- 127.0.0.3, match in sha256 only -->
+ <hash>18DD41C9F2E8E4879A1575FB780514EF33CF6E1F66578C4AE7CCA31F49B9F2ED</hash>
+ </sha256-ip>
+ <crc32-domain>
+ <!-- test-cc-domain.com -->
+ <hash>6C67059D</hash>
+ <!-- test-cc-match-crc32-only.com -->
+ <hash>3DC775F8</hash>
+ </crc32-domain>
+ <crc32-ip>
+ <!-- 127.0.0.2 -->
+ <hash>4EBEB612</hash>
+ <!-- 127.0.0.4, match in crc32 only -->
+ <hash>A7DD1327</hash>
+ </crc32-ip>
+</watchlist-config>
diff --git a/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml b/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml
index bb97e9431f72..5349a13280fd 100644
--- a/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml
+++ b/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml
@@ -1,27 +1,4 @@
<?xml version='1.0'?>
-<watchlist-settings>
- <sha256-domain>
- <!-- test-cc-domain.com -->
- <hash>8E7DCD2AEB4F364358242BB3F403263E61E3B4AECE4E2500FF28BF32E52FF0F1</hash>
- <!-- test-cc-match-sha256-only.com -->
- <hash>F0905DA7549614957B449034C281EF7BDEFDBC2B6E050AD1E78D6DE18FBD0D5F</hash>
- </sha256-domain>
- <sha256-ip>
- <!-- 127.0.0.2 -->
- <hash>1EDD62868F2767A1FFF68DF0A4CB3C23448E45100715768DB9310B5E719536A1</hash>
- <!-- 127.0.0.3, match in sha256 only -->
- <hash>18DD41C9F2E8E4879A1575FB780514EF33CF6E1F66578C4AE7CCA31F49B9F2ED</hash>
- </sha256-ip>
- <crc32-domain>
- <!-- test-cc-domain.com -->
- <hash>6C67059D</hash>
- <!-- test-cc-match-crc32-only.com -->
- <hash>3DC775F8</hash>
- </crc32-domain>
- <crc32-ip>
- <!-- 127.0.0.2 -->
- <hash>4EBEB612</hash>
- <!-- 127.0.0.4, match in crc32 only -->
- <hash>A7DD1327</hash>
- </crc32-ip>
-</watchlist-settings>
+<network-watchlist-settings>
+ <secret-key>1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF</secret-key>
+</network-watchlist-settings>
diff --git a/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test2.xml b/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test2.xml
new file mode 100644
index 000000000000..3e65bc06be52
--- /dev/null
+++ b/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test2.xml
@@ -0,0 +1,4 @@
+<?xml version='1.0'?>
+<network-watchlist-settings>
+
+</network-watchlist-settings>
diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
index 6a21931e0418..66d0da13fff1 100644
--- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
@@ -42,7 +42,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager.ServiceType;
@@ -51,17 +50,13 @@ import android.os.PowerSaveState;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Settings.Global;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.test.mock.MockContentResolver;
import android.util.ArraySet;
import android.util.Pair;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
-import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.ForceAppStandbyTracker.Listener;
import org.junit.Before;
@@ -107,9 +102,6 @@ public class ForceAppStandbyTrackerTest {
PowerManagerInternal injectPowerManagerInternal() {
return mMockPowerManagerInternal;
}
-
- @Override
- boolean isSmallBatteryDevice() { return mIsSmallBatteryDevice; };
}
private static final int UID_1 = Process.FIRST_APPLICATION_UID + 1;
@@ -145,11 +137,7 @@ public class ForceAppStandbyTrackerTest {
private Consumer<PowerSaveState> mPowerSaveObserver;
private BroadcastReceiver mReceiver;
- private MockContentResolver mMockContentResolver;
- private FakeSettingsProvider mFakeSettingsProvider;
-
private boolean mPowerSaveMode;
- private boolean mIsSmallBatteryDevice;
private final ArraySet<Pair<Integer, String>> mRestrictedPackages = new ArraySet();
@@ -186,17 +174,13 @@ public class ForceAppStandbyTrackerTest {
}
private void callStart(ForceAppStandbyTrackerTestable instance) throws RemoteException {
+
// Set up functions that start() calls.
when(mMockPowerManagerInternal.getLowPowerState(eq(ServiceType.FORCE_ALL_APPS_STANDBY)))
.thenAnswer(inv -> getPowerSaveState());
when(mMockAppOpsManager.getPackagesForOps(
any(int[].class)
- )).thenAnswer(inv -> new ArrayList<AppOpsManager.PackageOps>());
-
- mMockContentResolver = new MockContentResolver();
- mFakeSettingsProvider = new FakeSettingsProvider();
- when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
- mMockContentResolver.addProvider(Settings.AUTHORITY, mFakeSettingsProvider);
+ )).thenAnswer(inv -> new ArrayList<AppOpsManager.PackageOps>());
// Call start.
instance.start();
@@ -224,6 +208,7 @@ public class ForceAppStandbyTrackerTest {
verify(mMockPowerManagerInternal).registerLowPowerModeObserver(
eq(ServiceType.FORCE_ALL_APPS_STANDBY),
powerSaveObserverCaptor.capture());
+
verify(mMockContext).registerReceiver(
receiverCaptor.capture(), any(IntentFilter.class));
@@ -236,7 +221,6 @@ public class ForceAppStandbyTrackerTest {
assertNotNull(mAppOpsCallback);
assertNotNull(mPowerSaveObserver);
assertNotNull(mReceiver);
- assertNotNull(instance.mFlagsObserver);
}
private void setAppOps(int uid, String packageName, boolean restrict) throws RemoteException {
@@ -838,33 +822,6 @@ public class ForceAppStandbyTrackerTest {
assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
}
- @Test
- public void testSmallBatteryAndCharging() throws Exception {
- // This is a small battery device
- mIsSmallBatteryDevice = true;
-
- final ForceAppStandbyTrackerTestable instance = newInstance();
- callStart(instance);
- assertFalse(instance.isForceAllAppsStandbyEnabled());
-
- // Setting/experiment for all app standby for small battery is enabled
- Global.putInt(mMockContentResolver, Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 1);
- instance.mFlagsObserver.onChange(true,
- Global.getUriFor(Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED));
- assertTrue(instance.isForceAllAppsStandbyEnabled());
-
- // When battery is charging, force app standby is disabled
- Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
- intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
- mReceiver.onReceive(mMockContext, intent);
- assertFalse(instance.isForceAllAppsStandbyEnabled());
-
- // When battery stops charging, force app standby is enabled
- intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING);
- mReceiver.onReceive(mMockContext, intent);
- assertTrue(instance.isForceAllAppsStandbyEnabled());
- }
-
static int[] array(int... appIds) {
Arrays.sort(appIds);
return appIds;
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index fbcccf0fec2a..7c3082fb93de 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -18,6 +18,7 @@ package com.android.server;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicy.WARNING_DISABLED;
@@ -34,6 +35,7 @@ import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEF
import static android.telephony.CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG;
import static android.telephony.CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG;
import static android.telephony.CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT;
+import static android.telephony.SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.Time.TIMEZONE_UTC;
@@ -62,6 +64,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -86,13 +89,16 @@ import android.net.INetworkManagementEventObserver;
import android.net.INetworkPolicyListener;
import android.net.INetworkStatsService;
import android.net.LinkProperties;
+import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkPolicy;
import android.net.NetworkState;
import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.net.StringNetworkSpecifier;
import android.os.Binder;
import android.os.INetworkManagementService;
import android.os.PersistableBundle;
@@ -105,9 +111,11 @@ import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.Time;
+import android.util.DataUnit;
import android.util.Log;
import android.util.Pair;
import android.util.RecurrenceRule;
@@ -186,6 +194,7 @@ public class NetworkPolicyManagerServiceTest {
private static final long TEST_START = 1194220800000L;
private static final String TEST_IFACE = "test0";
private static final String TEST_SSID = "AndroidAP";
+ private static final String TEST_IMSI = "310210";
private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(TEST_SSID);
@@ -309,6 +318,11 @@ public class NetworkPolicyManagerServiceTest {
return super.getSystemService(name);
}
}
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ // Assume that we're AID_SYSTEM
+ }
};
setNetpolicyXml(context);
@@ -1065,6 +1079,67 @@ public class NetworkPolicyManagerServiceTest {
}
@Test
+ public void testRapidNotification() throws Exception {
+ // Create a place to store fake usage
+ final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
+ when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+ .thenAnswer(new Answer<Long>() {
+ @Override
+ public Long answer(InvocationOnMock invocation) throws Throwable {
+ final NetworkStatsHistory.Entry entry = history.getValues(
+ invocation.getArgument(1), invocation.getArgument(2), null);
+ return entry.rxBytes + entry.txBytes;
+ }
+ });
+
+ // Define simple data plan which gives us effectively 60MB/day
+ final SubscriptionPlan plan = SubscriptionPlan.Builder
+ .createRecurringMonthly(ZonedDateTime.parse("2015-11-01T00:00:00.00Z"))
+ .setDataLimit(DataUnit.MEGABYTES.toBytes(1800), LIMIT_BEHAVIOR_THROTTLED)
+ .build();
+ mService.setSubscriptionPlans(42, new SubscriptionPlan[] { plan },
+ mServiceContext.getOpPackageName());
+
+ // And get that active network in place
+ when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[] {
+ new NetworkState(null, new LinkProperties(),
+ new NetworkCapabilities().addTransportType(TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new StringNetworkSpecifier("42")),
+ new Network(42), TEST_IMSI, null)
+ });
+ mService.updateNetworks();
+
+ // We're 20% through the month (6 days)
+ final long start = parseTime("2015-11-01T00:00Z");
+ final long end = parseTime("2015-11-07T00:00Z");
+ setCurrentTimeMillis(end);
+
+ // Using 20% of data in 20% is normal
+ {
+ history.removeBucketsBefore(Long.MAX_VALUE);
+ history.recordData(start, end,
+ new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
+
+ reset(mNotifManager);
+ mService.updateNotifications();
+ verify(mNotifManager, never()).enqueueNotificationWithTag(any(), any(), any(),
+ anyInt(), any(), anyInt());
+ }
+
+ // Using 80% data in 20% time is alarming
+ {
+ history.removeBucketsBefore(Long.MAX_VALUE);
+ history.recordData(start, end,
+ new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
+
+ reset(mNotifManager);
+ mService.updateNotifications();
+ verify(mNotifManager, atLeastOnce()).enqueueNotificationWithTag(any(), any(), any(),
+ anyInt(), any(), anyInt());
+ }
+ }
+
+ @Test
public void testMeteredNetworkWithoutLimit() throws Exception {
NetworkState[] state = null;
NetworkStats stats = null;
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 d8e3be951bd0..43d026d8efc3 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -6,12 +6,17 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.app.job.JobInfo;
import android.app.job.JobInfo.Builder;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
import android.net.NetworkRequest;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
@@ -24,6 +29,7 @@ import android.util.Pair;
import com.android.internal.util.HexDump;
import com.android.server.IoThread;
+import com.android.server.LocalServices;
import com.android.server.job.JobStore.JobSet;
import com.android.server.job.controllers.JobStatus;
@@ -65,6 +71,13 @@ public class JobStoreTest {
JobStore.initAndGetForTesting(mTestContext, mTestContext.getFilesDir());
mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName());
+ // Assume all packages are current SDK
+ final PackageManagerInternal pm = mock(PackageManagerInternal.class);
+ when(pm.getPackageTargetSdkVersion(anyString()))
+ .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, pm);
+
// Freeze the clocks at this moment in time
JobSchedulerService.sSystemClock =
Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
diff --git a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
new file mode 100644
index 000000000000..f6a749df1df6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.job.controllers;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobInfo;
+import android.content.ComponentName;
+import android.content.pm.PackageManagerInternal;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.Build;
+import android.os.SystemClock;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.DataUnit;
+
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Clock;
+import java.time.ZoneOffset;
+
+@RunWith(AndroidJUnit4.class)
+public class ConnectivityControllerTest {
+ @Before
+ public void setUp() throws Exception {
+ // Assume all packages are current SDK
+ final PackageManagerInternal pm = mock(PackageManagerInternal.class);
+ when(pm.getPackageTargetSdkVersion(anyString()))
+ .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, pm);
+
+ // Freeze the clocks at this moment in time
+ JobSchedulerService.sSystemClock =
+ Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sUptimeMillisClock =
+ Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+ }
+
+ @Test
+ public void testInsane() throws Exception {
+ final Network network = new Network(101);
+ final JobInfo.Builder job = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+
+ // Slow network is too slow
+ assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), network,
+ createCapabilities().setLinkUpstreamBandwidthKbps(1)
+ .setLinkDownstreamBandwidthKbps(1)));
+ // Fast network looks great
+ assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), network,
+ createCapabilities().setLinkUpstreamBandwidthKbps(1024)
+ .setLinkDownstreamBandwidthKbps(1024)));
+ }
+
+ @Test
+ public void testCongestion() throws Exception {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final JobInfo.Builder job = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
+ final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
+
+ // Uncongested network is whenever
+ {
+ final Network network = new Network(101);
+ final NetworkCapabilities capabilities = createCapabilities()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED);
+ assertTrue(ConnectivityController.isSatisfied(early, network, capabilities));
+ assertTrue(ConnectivityController.isSatisfied(late, network, capabilities));
+ }
+
+ // Congested network is more selective
+ {
+ final Network network = new Network(101);
+ final NetworkCapabilities capabilities = createCapabilities();
+ assertFalse(ConnectivityController.isSatisfied(early, network, capabilities));
+ assertTrue(ConnectivityController.isSatisfied(late, network, capabilities));
+ }
+ }
+
+ @Test
+ public void testRelaxed() throws Exception {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final JobInfo.Builder job = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+ final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
+ final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
+
+ job.setIsPrefetch(true);
+ final JobStatus earlyPrefetch = createJobStatus(job, now - 1000, now + 2000);
+ final JobStatus latePrefetch = createJobStatus(job, now - 2000, now + 1000);
+
+ // Unmetered network is whenever
+ {
+ final Network network = new Network(101);
+ final NetworkCapabilities capabilities = createCapabilities()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NET_CAPABILITY_NOT_METERED);
+ assertTrue(ConnectivityController.isSatisfied(early, network, capabilities));
+ assertTrue(ConnectivityController.isSatisfied(late, network, capabilities));
+ assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, network, capabilities));
+ assertTrue(ConnectivityController.isSatisfied(latePrefetch, network, capabilities));
+ }
+
+ // Metered network is only when prefetching and late
+ {
+ final Network network = new Network(101);
+ final NetworkCapabilities capabilities = createCapabilities()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED);
+ assertFalse(ConnectivityController.isSatisfied(early, network, capabilities));
+ assertFalse(ConnectivityController.isSatisfied(late, network, capabilities));
+ assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, network, capabilities));
+ assertTrue(ConnectivityController.isSatisfied(latePrefetch, network, capabilities));
+ }
+ }
+
+ private static NetworkCapabilities createCapabilities() {
+ return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_VALIDATED);
+ }
+
+ private static JobInfo.Builder createJob() {
+ return new JobInfo.Builder(101, new ComponentName("foo", "bar"));
+ }
+
+ private static JobStatus createJobStatus(JobInfo.Builder job) {
+ return createJobStatus(job, 0, Long.MAX_VALUE);
+ }
+
+ private static JobStatus createJobStatus(JobInfo.Builder job, long earliestRunTimeElapsedMillis,
+ long latestRunTimeElapsedMillis) {
+ return new JobStatus(job.build(), 0, null, -1, 0, 0, null, earliestRunTimeElapsedMillis,
+ latestRunTimeElapsedMillis, 0, 0, null);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java
new file mode 100644
index 000000000000..15c24ac7efd6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.job.controllers;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.job.JobInfo;
+import android.content.ComponentName;
+import android.os.SystemClock;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.job.JobSchedulerService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Clock;
+import java.time.ZoneOffset;
+
+@RunWith(AndroidJUnit4.class)
+public class JobStatusTest {
+ private static final double DELTA = 0.00001;
+
+ @Before
+ public void setUp() throws Exception {
+ // Freeze the clocks at this moment in time
+ JobSchedulerService.sSystemClock =
+ Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sUptimeMillisClock =
+ Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+ }
+
+ @Test
+ public void testFraction() throws Exception {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ assertEquals(1, createJobStatus(0, Long.MAX_VALUE).getFractionRunTime(), DELTA);
+
+ assertEquals(1, createJobStatus(0, now - 1000).getFractionRunTime(), DELTA);
+ assertEquals(0, createJobStatus(0, now + 1000).getFractionRunTime(), DELTA);
+
+ assertEquals(1, createJobStatus(now - 1000, Long.MAX_VALUE).getFractionRunTime(), DELTA);
+ assertEquals(0, createJobStatus(now + 1000, Long.MAX_VALUE).getFractionRunTime(), DELTA);
+
+ assertEquals(0, createJobStatus(now, now + 2000).getFractionRunTime(), DELTA);
+ assertEquals(0.25, createJobStatus(now - 500, now + 1500).getFractionRunTime(), DELTA);
+ assertEquals(0.5, createJobStatus(now - 1000, now + 1000).getFractionRunTime(), DELTA);
+ assertEquals(0.75, createJobStatus(now - 1500, now + 500).getFractionRunTime(), DELTA);
+ assertEquals(1, createJobStatus(now - 2000, now).getFractionRunTime(), DELTA);
+ }
+
+ private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
+ long latestRunTimeElapsedMillis) {
+ final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build();
+ return new JobStatus(job, 0, null, -1, 0, 0, null, earliestRunTimeElapsedMillis,
+ latestRunTimeElapsedMillis, 0, 0, null);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index c6fc675db6b1..7eec4fea64dc 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -78,7 +78,7 @@ public class KeySyncTaskTest {
private static final int TEST_RECOVERY_AGENT_UID = 10009;
private static final int TEST_RECOVERY_AGENT_UID2 = 10010;
private static final byte[] TEST_VAULT_HANDLE =
- new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 15, 16, 17};
+ new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17};
private static final String TEST_APP_KEY_ALIAS = "rcleaver";
private static final int TEST_GENERATION_ID = 2;
private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD;
@@ -278,9 +278,13 @@ public class KeySyncTaskTest {
public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception {
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
+
+ mRecoverableKeyStoreDb.setServerParams(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
SecretKey applicationKey =
addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+
mKeySyncTask.run();
KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
@@ -293,6 +297,7 @@ public class KeySyncTaskTest {
KeyDerivationParams.getSalt(),
TEST_CREDENTIAL);
Long counterId = mRecoverableKeyStoreDb.getCounterId(TEST_USER_ID, TEST_RECOVERY_AGENT_UID);
+ counterId = 1L; // TODO: use value from the database.
assertThat(counterId).isNotNull();
byte[] recoveryKey = decryptThmEncryptedKey(
lockScreenHash,
@@ -304,6 +309,11 @@ public class KeySyncTaskTest {
TEST_VAULT_HANDLE));
List<WrappedApplicationKey> applicationKeys = keychainSnapshot.getWrappedApplicationKeys();
assertThat(applicationKeys).hasSize(1);
+ assertThat(keychainSnapshot.getCounterId()).isEqualTo(counterId);
+ assertThat(keychainSnapshot.getMaxAttempts()).isEqualTo(10);
+ assertThat(keychainSnapshot.getTrustedHardwarePublicKey())
+ .isEqualTo(SecureBox.encodePublicKey(mKeyPair.getPublic()));
+ assertThat(keychainSnapshot.getServerParams()).isEqualTo(TEST_VAULT_HANDLE);
WrappedApplicationKey keyData = applicationKeys.get(0);
assertEquals(TEST_APP_KEY_ALIAS, keyData.getAlias());
assertThat(keyData.getAlias()).isEqualTo(keyData.getAlias());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index ba6b274b83b8..a251c9d7898e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -51,7 +51,7 @@ public class KeySyncUtilsTest {
private static final int THM_KF_HASH_SIZE = 256;
private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
private static final byte[] TEST_VAULT_HANDLE =
- new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 15, 16, 17};
+ new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17};
private static final String SHA_256_ALGORITHM = "SHA-256";
private static final String APPLICATION_KEY_ALGORITHM = "AES";
private static final byte[] LOCK_SCREEN_HASH_1 =
@@ -417,8 +417,7 @@ public class KeySyncUtilsTest {
byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES + Integer.BYTES);
byte[] vaultHandle = new byte[VAULT_HANDLE_LENGTH_BYTES];
byteBuffer.get(vaultHandle);
- // TODO: Fix this once we fix the code in the KeySyncUtils class
- assertArrayEquals(new byte[VAULT_HANDLE_LENGTH_BYTES], vaultHandle);
+ assertArrayEquals(TEST_VAULT_HANDLE, vaultHandle);
}
private static byte[] randomBytes(int n) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 402a37c420a7..970bc33337da 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -330,7 +330,7 @@ public class RecoverableKeyStoreManagerTest {
TEST_VAULT_CHALLENGE,
ImmutableList.of());
fail("should have thrown");
- } catch (ServiceSpecificException e) {
+ } catch (UnsupportedOperationException e) {
assertThat(e.getMessage()).startsWith(
"Only a single KeychainProtectionParams is supported");
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 5cb7b677dbbd..f0254c6d5dfe 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -28,8 +28,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.security.keystore.RecoveryManager;
+import android.security.keystore.RecoveryController;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -283,7 +282,7 @@ public class RecoverableKeyStoreDbTest {
Map<String, Integer> statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid);
assertThat(statuses).hasSize(3);
- assertThat(statuses).containsEntry(alias, RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+ assertThat(statuses).containsEntry(alias, RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS);
assertThat(statuses).containsEntry(alias2, status);
assertThat(statuses).containsEntry(alias3, status);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
index 0f95748a8ea7..bb0474efee58 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
@@ -125,7 +125,7 @@ public class RecoverySessionStorageTest {
storage.remove(TEST_USER_ID, TEST_SESSION_ID);
- assertNotNull(storage.get(TEST_USER_ID, TEST_SESSION_ID));
+ assertNotNull(storage.get(TEST_USER_ID, otherSessionId));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/PrivacyUtilsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/PrivacyUtilsTests.java
new file mode 100644
index 000000000000..a31b46ce5534
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/PrivacyUtilsTests.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.privacy.DifferentialPrivacyEncoder;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.PrivacyUtilsTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PrivacyUtilsTests {
+
+ private static final List<String> TEST_DIGEST_LIST = Arrays.asList(
+ "B86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB43",
+ "E86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB45",
+ "C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB44",
+ "C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB47",
+ "C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB48",
+ "C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB49");
+
+ private static final WatchlistReportDbHelper.AggregatedResult TEST_AGGREGATED_RESULT1 =
+ new WatchlistReportDbHelper.AggregatedResult(new HashSet<>(Arrays.asList(
+ "B86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB43",
+ "C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB48",
+ "E86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB45")), null,
+ new HashMap<>());
+
+ private static final byte[] TEST_SECRET = new byte[]{
+ (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
+ (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+ (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
+ (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+ (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
+ (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54
+ };
+
+ @Test
+ public void testPrivacyUtils_encodeReport() throws Exception {
+ Map<String, Boolean> result = PrivacyUtils.createDpEncodedReportMap(false, null,
+ TEST_DIGEST_LIST, TEST_AGGREGATED_RESULT1);
+ assertEquals(6, result.size());
+ assertTrue(result.get("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB48"));
+ assertTrue(result.get("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB49"));
+ assertFalse(result.get("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB47"));
+ assertTrue(result.get("E86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB45"));
+ assertFalse(result.get("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB44"));
+ assertTrue(result.get("B86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB43"));
+ }
+
+ @Test
+ public void testPrivacyUtils_createInsecureDPEncoderForTest() throws Exception {
+ DifferentialPrivacyEncoder encoder = PrivacyUtils.createInsecureDPEncoderForTest("foo");
+ assertEquals(
+ "EncoderId: watchlist_encoder:foo, ProbabilityF: 0.400, ProbabilityP: 0.250, "
+ + "ProbabilityQ: 1.000",
+ encoder.getConfig().toString());
+ assertTrue(encoder.isInsecureEncoderForTest());
+ }
+
+ @Test
+ public void testPrivacyUtils_createSecureDPEncoderTest() throws Exception {
+ DifferentialPrivacyEncoder encoder = PrivacyUtils.createSecureDPEncoder(TEST_SECRET, "foo");
+ assertEquals(
+ "EncoderId: watchlist_encoder:foo, ProbabilityF: 0.400, ProbabilityP: 0.250, "
+ + "ProbabilityQ: 1.000",
+ encoder.getConfig().toString());
+ assertFalse(encoder.isInsecureEncoderForTest());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/ReportUtilsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/ReportUtilsTests.java
new file mode 100644
index 000000000000..395969eb7c77
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/ReportUtilsTests.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.ReportUtilsTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ReportUtilsTests {
+
+ private static final String TEST_XML_1 = "NetworkWatchlistTest/watchlist_config_test1.xml";
+ private static final String TEST_XML_1_HASH =
+ "C99F27A08B1FDB15B101098E12BB2A0AA0D474E23C50F24920A52AB2322BFD94";
+ private static final String REPORT_HEADER_MAGIC = "8D370AAC";
+ private static final String REPORT_HEADER_VERSION = "0001";
+
+ private Context mContext;
+ private File mTestXmlFile;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mTestXmlFile = new File(mContext.getFilesDir(), "test_watchlist_config.xml");
+ mTestXmlFile.delete();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mTestXmlFile.delete();
+ }
+
+ @Test
+ public void testReportUtils_serializeReport() throws Exception {
+ final byte[] expectedResult = HexDump.hexStringToByteArray(
+ REPORT_HEADER_MAGIC + REPORT_HEADER_VERSION + TEST_XML_1_HASH
+ + "B86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB43" + "01"
+ + "C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB44" + "00"
+ + "D86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB45" + "00"
+ + "E86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB46" + "01"
+ + "F86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB47" + "01"
+ );
+ HashMap<String, Boolean> input = new HashMap<>();
+ input.put("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB44", false);
+ input.put("B86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB43", true);
+ input.put("D86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB45", false);
+ input.put("E86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB46", true);
+ input.put("F86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB47", true);
+
+ copyWatchlistConfigXml(mContext, TEST_XML_1, mTestXmlFile);
+ WatchlistConfig config = new WatchlistConfig(mTestXmlFile);
+
+ byte[] result = ReportEncoder.serializeReport(config, input);
+ assertArrayEquals(expectedResult, result);
+ }
+
+ private static void copyWatchlistConfigXml(Context context, String xmlAsset, File outFile)
+ throws IOException {
+ writeToFile(outFile, readAsset(context, xmlAsset));
+ }
+
+ private static String readAsset(Context context, String assetPath) throws IOException {
+ final StringBuilder sb = new StringBuilder();
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(
+ context.getResources().getAssets().open(assetPath)))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ sb.append(System.lineSeparator());
+ }
+ }
+ return sb.toString();
+ }
+
+ private static void writeToFile(File path, String content)
+ throws IOException {
+ path.getParentFile().mkdirs();
+
+ try (FileWriter writer = new FileWriter(path)) {
+ writer.write(content);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistConfigTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistConfigTests.java
new file mode 100644
index 000000000000..851d2c62e87e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistConfigTests.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.WatchlistConfigTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WatchlistConfigTests {
+
+ private static final String TEST_XML_1 = "NetworkWatchlistTest/watchlist_config_test1.xml";
+ private static final String TEST_XML_1_HASH =
+ "C99F27A08B1FDB15B101098E12BB2A0AA0D474E23C50F24920A52AB2322BFD94";
+ private static final String TEST_CC_DOMAIN = "test-cc-domain.com";
+ private static final String TEST_CC_IP = "127.0.0.2";
+ private static final String TEST_NOT_EXIST_CC_DOMAIN = "test-not-exist-cc-domain.com";
+ private static final String TEST_NOT_EXIST_CC_IP = "1.2.3.4";
+ private static final String TEST_SHA256_ONLY_DOMAIN = "test-cc-match-sha256-only.com";
+ private static final String TEST_SHA256_ONLY_IP = "127.0.0.3";
+ private static final String TEST_CRC32_ONLY_DOMAIN = "test-cc-match-crc32-only.com";
+ private static final String TEST_CRC32_ONLY_IP = "127.0.0.4";
+
+ private static final String TEST_NEW_CC_DOMAIN = "test-new-cc-domain.com";
+ private static final byte[] TEST_NEW_CC_DOMAIN_SHA256 = HexDump.hexStringToByteArray(
+ "B86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB43");
+ private static final byte[] TEST_NEW_CC_DOMAIN_CRC32 = HexDump.hexStringToByteArray("76795BD3");
+
+ private static final String TEST_NEW_CC_IP = "1.1.1.2";
+ private static final byte[] TEST_NEW_CC_IP_SHA256 = HexDump.hexStringToByteArray(
+ "721BAB5E313CF0CC76B10F9592F18B9D1B8996497501A3306A55B3AE9F1CC87C");
+ private static final byte[] TEST_NEW_CC_IP_CRC32 = HexDump.hexStringToByteArray("940B8BEE");
+
+ private Context mContext;
+ private File mTestXmlFile;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mTestXmlFile = new File(mContext.getFilesDir(), "test_watchlist_config.xml");
+ mTestXmlFile.delete();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mTestXmlFile.delete();
+ }
+
+ @Test
+ public void testWatchlistConfig_parsing() throws Exception {
+ copyWatchlistConfigXml(mContext, TEST_XML_1, mTestXmlFile);
+ WatchlistConfig config = new WatchlistConfig(mTestXmlFile);
+ assertTrue(config.containsDomain(TEST_CC_DOMAIN));
+ assertTrue(config.containsIp(TEST_CC_IP));
+ assertFalse(config.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+ assertFalse(config.containsIp(TEST_NOT_EXIST_CC_IP));
+ assertFalse(config.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+ assertFalse(config.containsIp(TEST_SHA256_ONLY_IP));
+ assertFalse(config.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+ assertFalse(config.containsIp(TEST_CRC32_ONLY_IP));
+ }
+
+ @Test
+ public void testWatchlistConfig_noXml() throws Exception {
+ WatchlistConfig config = new WatchlistConfig(mTestXmlFile);
+ assertFalse(config.containsDomain(TEST_CC_DOMAIN));
+ assertFalse(config.containsIp(TEST_CC_IP));
+ assertFalse(config.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+ assertFalse(config.containsIp(TEST_NOT_EXIST_CC_IP));
+ assertFalse(config.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+ assertFalse(config.containsIp(TEST_SHA256_ONLY_IP));
+ assertFalse(config.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+ assertFalse(config.containsIp(TEST_CRC32_ONLY_IP));
+ }
+
+ @Test
+ public void testWatchlistConfig_getWatchlistConfigHash() throws Exception {
+ copyWatchlistConfigXml(mContext, TEST_XML_1, mTestXmlFile);
+ WatchlistConfig config = new WatchlistConfig(mTestXmlFile);
+ assertEquals(TEST_XML_1_HASH, HexDump.toHexString(config.getWatchlistConfigHash()));
+ }
+
+ private static void copyWatchlistConfigXml(Context context, String xmlAsset, File outFile)
+ throws IOException {
+ writeToFile(outFile, readAsset(context, xmlAsset));
+ }
+
+ private static String readAsset(Context context, String assetPath) throws IOException {
+ final StringBuilder sb = new StringBuilder();
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(
+ context.getResources().getAssets().open(assetPath)))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ sb.append(System.lineSeparator());
+ }
+ }
+ return sb.toString();
+ }
+
+ private static void writeToFile(File path, String content)
+ throws IOException {
+ path.getParentFile().mkdirs();
+
+ try (FileWriter writer = new FileWriter(path)) {
+ writer.write(content);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java
index e356b13d01d5..070de5b95356 100644
--- a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java
@@ -36,14 +36,6 @@ import java.util.Arrays;
@SmallTest
public class WatchlistLoggingHandlerTests {
- @Before
- public void setUp() throws Exception {
- }
-
- @After
- public void tearDown() throws Exception {
- }
-
@Test
public void testWatchlistLoggingHandler_getAllSubDomains() throws Exception {
String[] subDomains = WatchlistLoggingHandler.getAllSubDomains("abc.def.gh.i.jkl.mm");
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
index 212d25d42420..07158afbaaff 100644
--- a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,8 @@
package com.android.server.net.watchlist;
-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 android.content.Context;
import android.support.test.InstrumentationRegistry;
@@ -36,7 +36,6 @@ import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.util.Arrays;
/**
* runtest frameworks-services -c com.android.server.net.watchlist.WatchlistSettingsTests
@@ -46,24 +45,9 @@ import java.util.Arrays;
public class WatchlistSettingsTests {
private static final String TEST_XML_1 = "NetworkWatchlistTest/watchlist_settings_test1.xml";
- private static final String TEST_CC_DOMAIN = "test-cc-domain.com";
- private static final String TEST_CC_IP = "127.0.0.2";
- private static final String TEST_NOT_EXIST_CC_DOMAIN = "test-not-exist-cc-domain.com";
- private static final String TEST_NOT_EXIST_CC_IP = "1.2.3.4";
- private static final String TEST_SHA256_ONLY_DOMAIN = "test-cc-match-sha256-only.com";
- private static final String TEST_SHA256_ONLY_IP = "127.0.0.3";
- private static final String TEST_CRC32_ONLY_DOMAIN = "test-cc-match-crc32-only.com";
- private static final String TEST_CRC32_ONLY_IP = "127.0.0.4";
-
- private static final String TEST_NEW_CC_DOMAIN = "test-new-cc-domain.com";
- private static final byte[] TEST_NEW_CC_DOMAIN_SHA256 = HexDump.hexStringToByteArray(
- "B86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB43");
- private static final byte[] TEST_NEW_CC_DOMAIN_CRC32 = HexDump.hexStringToByteArray("76795BD3");
-
- private static final String TEST_NEW_CC_IP = "1.1.1.2";
- private static final byte[] TEST_NEW_CC_IP_SHA256 = HexDump.hexStringToByteArray(
- "721BAB5E313CF0CC76B10F9592F18B9D1B8996497501A3306A55B3AE9F1CC87C");
- private static final byte[] TEST_NEW_CC_IP_CRC32 = HexDump.hexStringToByteArray("940B8BEE");
+ private static final String TEST_XML_2 = "NetworkWatchlistTest/watchlist_settings_test2.xml";
+ private static final String HARD_CODED_SECRET_KEY = "1234567890ABCDEF1234567890ABCDEF"
+ + "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF";
private Context mContext;
private File mTestXmlFile;
@@ -71,7 +55,7 @@ public class WatchlistSettingsTests {
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
- mTestXmlFile = new File(mContext.getFilesDir(), "test_watchlist_settings.xml");
+ mTestXmlFile = new File(mContext.getFilesDir(), "test_settings_config.xml");
mTestXmlFile.delete();
}
@@ -84,55 +68,48 @@ public class WatchlistSettingsTests {
public void testWatchlistSettings_parsing() throws Exception {
copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
- assertTrue(settings.containsDomain(TEST_CC_DOMAIN));
- assertTrue(settings.containsIp(TEST_CC_IP));
- assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
- assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
- assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+ assertEquals(HARD_CODED_SECRET_KEY, HexDump.toHexString(settings.getPrivacySecretKey()));
+ // Try again.
+ assertEquals(HARD_CODED_SECRET_KEY, HexDump.toHexString(settings.getPrivacySecretKey()));
}
@Test
- public void testWatchlistSettings_writeSettingsToMemory() throws Exception {
- copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
+ public void testWatchlistSettings_parsingWithoutKey() throws Exception {
+ copyWatchlistSettingsXml(mContext, TEST_XML_2, mTestXmlFile);
WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
- settings.writeSettingsToMemory(Arrays.asList(TEST_NEW_CC_DOMAIN_CRC32),
- Arrays.asList(TEST_NEW_CC_DOMAIN_SHA256), Arrays.asList(TEST_NEW_CC_IP_CRC32),
- Arrays.asList(TEST_NEW_CC_IP_SHA256));
- // Ensure old watchlist is not in memory
- assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_CC_IP));
- assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
- assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
- assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
- // Ensure new watchlist is in memory
- assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
- assertTrue(settings.containsIp(TEST_NEW_CC_IP));
- // Reload settings from disk and test again
+ final String tmpKey1 = HexDump.toHexString(settings.getPrivacySecretKey());
+ assertNotEquals(HARD_CODED_SECRET_KEY, tmpKey1);
+ assertEquals(96, tmpKey1.length());
+ // Try again to make sure it's the same.
+ assertEquals(tmpKey1, HexDump.toHexString(settings.getPrivacySecretKey()));
+ // Create new settings object again to make sure it can get the new saved key.
settings = new WatchlistSettings(mTestXmlFile);
- // Ensure old watchlist is in memory
- assertTrue(settings.containsDomain(TEST_CC_DOMAIN));
- assertTrue(settings.containsIp(TEST_CC_IP));
- assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
- assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
- assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
- // Ensure new watchlist is not in memory
- assertFalse(settings.containsDomain(TEST_NEW_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_NEW_CC_IP));;
+ assertEquals(tmpKey1, HexDump.toHexString(settings.getPrivacySecretKey()));
+ }
+
+ @Test
+ public void testWatchlistSettings_noExistingXml() throws Exception {
+ WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
+ final String tmpKey1 = HexDump.toHexString(settings.getPrivacySecretKey());
+ assertNotEquals(HARD_CODED_SECRET_KEY, tmpKey1);
+ assertEquals(96, tmpKey1.length());
+ // Try again to make sure it's the same.
+ assertEquals(tmpKey1, HexDump.toHexString(settings.getPrivacySecretKey()));
+ // Create new settings object again to make sure it can get the new saved key.
+ settings = new WatchlistSettings(mTestXmlFile);
+ assertEquals(tmpKey1, HexDump.toHexString(settings.getPrivacySecretKey()));
+ // Delete xml and generate key again, to make sure key is randomly generated.
+ mTestXmlFile.delete();
+ settings = new WatchlistSettings(mTestXmlFile);
+ final String tmpKey2 = HexDump.toHexString(settings.getPrivacySecretKey());
+ assertNotEquals(HARD_CODED_SECRET_KEY, tmpKey2);
+ assertNotEquals(tmpKey1, tmpKey2);
+ assertEquals(96, tmpKey2.length());
}
private static void copyWatchlistSettingsXml(Context context, String xmlAsset, File outFile)
throws IOException {
writeToFile(outFile, readAsset(context, xmlAsset));
-
}
private static String readAsset(Context context, String assetPath) throws IOException {
diff --git a/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index ff55a2ba120b..c69437dc798e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -1,9 +1,7 @@
-package com.android.server.pm.crossprofile;
+package com.android.server.pm;
import static com.google.common.truth.Truth.assertThat;
-import static junit.framework.Assert.assertEquals;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -34,7 +32,6 @@ import android.util.SparseArray;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@@ -44,7 +41,7 @@ import java.util.List;
/**
* Build/Install/Run:
- * bit FrameworksServicesTests:com.android.server.pm.crossprofile.CrossProfileAppsServiceImplTest
+ * atest FrameworksServicesTests:com.android.server.pm.CrossProfileAppsServiceImplTest
*/
@Presubmit
@RunWith(MockitoJUnitRunner.class)
diff --git a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
index ac291632c877..57da6a3a60a6 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
@@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -28,6 +27,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.ClipData;
+import android.graphics.PixelFormat;
import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
@@ -36,7 +36,7 @@ import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.InputChannel;
-import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
import com.android.internal.annotations.GuardedBy;
@@ -146,14 +146,6 @@ public class DragDropControllerTests extends WindowTestsBase {
}
@Test
- public void testPrepareDrag_ZeroSizeSurface() throws Exception {
- final Surface surface = new Surface();
- mToken = mTarget.prepareDrag(
- new SurfaceSession(), 0, 0, mWindow.mClient, 0, 0, 0, surface);
- assertNull(mToken);
- }
-
- @Test
public void testPerformDrag_NullDataWithGrantUri() throws Exception {
dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
}
@@ -169,16 +161,24 @@ public class DragDropControllerTests extends WindowTestsBase {
}
private void dragFlow(int flag, ClipData data, float dropX, float dropY) {
- final Surface surface = new Surface();
- mToken = mTarget.prepareDrag(
- new SurfaceSession(), 0, 0, mWindow.mClient, flag, 100, 100, surface);
- assertNotNull(mToken);
-
- assertTrue(sWm.mInputManager.transferTouchFocus(null, null));
- assertTrue(mTarget.performDrag(
- mWindow.mClient, mToken, 0, 0, 0, 0, 0, data));
-
- mTarget.handleMotionEvent(false, dropX, dropY);
- mToken = mWindow.mClient.asBinder();
+ final SurfaceSession appSession = new SurfaceSession();
+ try {
+ final SurfaceControl surface = new SurfaceControl.Builder(appSession)
+ .setName("drag surface")
+ .setSize(100, 100)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
+
+ assertTrue(sWm.mInputManager.transferTouchFocus(null, null));
+ mToken = mTarget.performDrag(
+ new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
+ data);
+ assertNotNull(mToken);
+
+ mTarget.handleMotionEvent(false, dropX, dropY);
+ mToken = mWindow.mClient.asBinder();
+ } finally {
+ appSession.kill();
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
index 873a01bd8116..7bf7dd78711c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
@@ -33,6 +33,7 @@ import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
@@ -57,6 +58,9 @@ public class TaskPositionerTests extends WindowTestsBase {
@Before
public void setUp() throws Exception {
super.setUp();
+
+ TaskPositioner.setFactory(null);
+
final Display display = mDisplayContent.getDisplay();
final DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
@@ -65,10 +69,26 @@ public class TaskPositionerTests extends WindowTestsBase {
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
- mPositioner = new TaskPositioner(sWm);
+ mPositioner = TaskPositioner.create(sWm);
mPositioner.register(mDisplayContent);
}
+ @Test
+ public void testOverrideFactory() throws Exception {
+ final boolean[] created = new boolean[1];
+ created[0] = false;
+ TaskPositioner.setFactory(new TaskPositioner.Factory() {
+ @Override
+ public TaskPositioner create(WindowManagerService service) {
+ created[0] = true;
+ return null;
+ }
+ });
+
+ assertNull(TaskPositioner.create(sWm));
+ assertTrue(created[0]);
+ }
+
/**
* This tests that free resizing will allow to change the orientation as well
* as does some basic tests (e.g. dragging in Y only will keep X stable).
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 423dc80af54d..14060935f4ff 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -16,6 +16,10 @@
package android.telephony;
+import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
+import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
+
+import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -30,6 +34,7 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.INetworkPolicyManager;
+import android.net.NetworkCapabilities;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -38,7 +43,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.DisplayMetrics;
-import android.util.Log;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.ISub;
@@ -1738,6 +1742,75 @@ public class SubscriptionManager {
}
/**
+ * Temporarily override the billing relationship plan between a carrier and
+ * a specific subscriber to be considered unmetered. This will be reflected
+ * to apps via {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED}.
+ * <p>
+ * This method is only accessible to the following narrow set of apps:
+ * <ul>
+ * <li>The carrier app for this subscriberId, as determined by
+ * {@link TelephonyManager#hasCarrierPrivileges()}.
+ * <li>The carrier app explicitly delegated access through
+ * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
+ * </ul>
+ *
+ * @param subId the subscriber this override applies to.
+ * @param overrideUnmetered set if the billing relationship should be
+ * considered unmetered.
+ * @param timeoutMillis the timeout after which the requested override will
+ * be automatically cleared, or {@code 0} to leave in the
+ * requested state until explicitly cleared, or the next reboot,
+ * whichever happens first.
+ * @hide
+ */
+ @SystemApi
+ public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered,
+ @DurationMillisLong long timeoutMillis) {
+ try {
+ final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0;
+ mNetworkPolicy.setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue,
+ timeoutMillis, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Temporarily override the billing relationship plan between a carrier and
+ * a specific subscriber to be considered congested. This will cause the
+ * device to delay certain network requests when possible, such as developer
+ * jobs that are willing to run in a flexible time window.
+ * <p>
+ * This method is only accessible to the following narrow set of apps:
+ * <ul>
+ * <li>The carrier app for this subscriberId, as determined by
+ * {@link TelephonyManager#hasCarrierPrivileges()}.
+ * <li>The carrier app explicitly delegated access through
+ * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
+ * </ul>
+ *
+ * @param subId the subscriber this override applies to.
+ * @param overrideCongested set if the subscription should be considered
+ * congested.
+ * @param timeoutMillis the timeout after which the requested override will
+ * be automatically cleared, or {@code 0} to leave in the
+ * requested state until explicitly cleared, or the next reboot,
+ * whichever happens first.
+ * @hide
+ */
+ @SystemApi
+ public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
+ @DurationMillisLong long timeoutMillis) {
+ try {
+ final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0;
+ mNetworkPolicy.setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue,
+ timeoutMillis, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Create an {@link Intent} that can be launched towards the carrier app
* that is currently defining the billing relationship plan through
* {@link #setSubscriptionPlans(int, List)}.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8edc8b17c9e0..de9e691eadcf 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2122,6 +2122,27 @@ public class TelephonyManager {
* carrier restrictions.
*/
public static final int SIM_STATE_CARD_RESTRICTED = 9;
+ /**
+ * SIM card state: Loaded: SIM card applications have been loaded
+ * @hide
+ */
+ @SystemApi
+ public static final int SIM_STATE_LOADED = 10;
+ /**
+ * SIM card state: SIM Card is present
+ * @hide
+ */
+ @SystemApi
+ public static final int SIM_STATE_PRESENT = 11;
+
+ /**
+ * Extra included in {@link Intent.ACTION_SIM_CARD_STATE_CHANGED} and
+ * {@link Intent.ACTION_SIM_APPLICATION_STATE_CHANGED} to indicate the card/application state.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE";
/**
* @return true if a ICC card is present
@@ -2168,6 +2189,14 @@ public class TelephonyManager {
* @see #SIM_STATE_CARD_RESTRICTED
*/
public int getSimState() {
+ int simState = getSimStateIncludingLoaded();
+ if (simState == SIM_STATE_LOADED) {
+ simState = SIM_STATE_READY;
+ }
+ return simState;
+ }
+
+ private int getSimStateIncludingLoaded() {
int slotIndex = getSlotIndex();
// slotIndex may be invalid due to sim being absent. In that case query all slots to get
// sim state
@@ -2186,7 +2215,63 @@ public class TelephonyManager {
"state as absent");
return SIM_STATE_ABSENT;
}
- return getSimState(slotIndex);
+ return SubscriptionManager.getSimStateForSlotIndex(slotIndex);
+ }
+
+ /**
+ * Returns a constant indicating the state of the default SIM card.
+ *
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_ABSENT
+ * @see #SIM_STATE_CARD_IO_ERROR
+ * @see #SIM_STATE_CARD_RESTRICTED
+ * @see #SIM_STATE_PRESENT
+ *
+ * @hide
+ */
+ @SystemApi
+ public int getSimCardState() {
+ int simCardState = getSimState();
+ switch (simCardState) {
+ case SIM_STATE_UNKNOWN:
+ case SIM_STATE_ABSENT:
+ case SIM_STATE_CARD_IO_ERROR:
+ case SIM_STATE_CARD_RESTRICTED:
+ return simCardState;
+ default:
+ return SIM_STATE_PRESENT;
+ }
+ }
+
+ /**
+ * Returns a constant indicating the state of the card applications on the default SIM card.
+ *
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_PIN_REQUIRED
+ * @see #SIM_STATE_PUK_REQUIRED
+ * @see #SIM_STATE_NETWORK_LOCKED
+ * @see #SIM_STATE_NOT_READY
+ * @see #SIM_STATE_PERM_DISABLED
+ * @see #SIM_STATE_LOADED
+ *
+ * @hide
+ */
+ @SystemApi
+ public int getSimApplicationState() {
+ int simApplicationState = getSimStateIncludingLoaded();
+ switch (simApplicationState) {
+ case SIM_STATE_UNKNOWN:
+ case SIM_STATE_ABSENT:
+ case SIM_STATE_CARD_IO_ERROR:
+ case SIM_STATE_CARD_RESTRICTED:
+ return SIM_STATE_UNKNOWN;
+ case SIM_STATE_READY:
+ // Ready is not a valid state anymore. The state that is broadcast goes from
+ // NOT_READY to either LOCKED or LOADED.
+ return SIM_STATE_NOT_READY;
+ default:
+ return simApplicationState;
+ }
}
/**
@@ -2207,6 +2292,9 @@ public class TelephonyManager {
*/
public int getSimState(int slotIndex) {
int simState = SubscriptionManager.getSimStateForSlotIndex(slotIndex);
+ if (simState == SIM_STATE_LOADED) {
+ simState = SIM_STATE_READY;
+ }
return simState;
}
diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
index 4e095e3a7003..519710728403 100644
--- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
@@ -19,6 +19,7 @@ package android.telephony.ims.feature;
import android.app.PendingIntent;
import android.os.Message;
import android.os.RemoteException;
+import android.telephony.ims.internal.stub.SmsImplBase;
import com.android.ims.ImsCallProfile;
import com.android.ims.internal.IImsCallSession;
@@ -28,6 +29,7 @@ import com.android.ims.internal.IImsEcbm;
import com.android.ims.internal.IImsMMTelFeature;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsSmsListener;
import com.android.ims.internal.IImsUt;
import com.android.ims.internal.ImsCallSession;
@@ -171,6 +173,42 @@ public class MMTelFeature extends ImsFeature {
return MMTelFeature.this.getMultiEndpointInterface();
}
}
+
+ @Override
+ public void setSmsListener(IImsSmsListener l) throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.setSmsListener(l);
+ }
+ }
+
+ @Override
+ public void sendSms(int token, int messageRef, String format, String smsc, boolean retry,
+ byte[] pdu) {
+ synchronized (mLock) {
+ MMTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu);
+ }
+ }
+
+ @Override
+ public void acknowledgeSms(int token, int messageRef, int result) {
+ synchronized (mLock) {
+ MMTelFeature.this.acknowledgeSms(token, messageRef, result);
+ }
+ }
+
+ @Override
+ public void acknowledgeSmsReport(int token, int messageRef, int result) {
+ synchronized (mLock) {
+ MMTelFeature.this.acknowledgeSmsReport(token, messageRef, result);
+ }
+ }
+
+ @Override
+ public String getSmsFormat() {
+ synchronized (mLock) {
+ return MMTelFeature.this.getSmsFormat();
+ }
+ }
};
/**
@@ -346,6 +384,39 @@ public class MMTelFeature extends ImsFeature {
return null;
}
+ public void setSmsListener(IImsSmsListener listener) {
+ getSmsImplementation().registerSmsListener(listener);
+ }
+
+ public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+ byte[] pdu) {
+ getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu);
+ }
+
+ public void acknowledgeSms(int token, int messageRef,
+ @SmsImplBase.DeliverStatusResult int result) {
+ getSmsImplementation().acknowledgeSms(token, messageRef, result);
+ }
+
+ public void acknowledgeSmsReport(int token, int messageRef,
+ @SmsImplBase.StatusReportResult int result) {
+ getSmsImplementation().acknowledgeSmsReport(token, messageRef, result);
+ }
+
+ /**
+ * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default
+ * non-functional implementation is returned.
+ *
+ * @return an instance of {@link SmsImplBase} which should be implemented by the IMS Provider.
+ */
+ protected SmsImplBase getSmsImplementation() {
+ return new SmsImplBase();
+ }
+
+ public String getSmsFormat() {
+ return getSmsImplementation().getSmsFormat();
+ }
+
@Override
public void onFeatureReady() {
diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
index eb805a88da8e..33b23d94ad34 100644
--- a/telephony/java/android/telephony/ims/internal/SmsImplBase.java
+++ b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
@@ -17,7 +17,6 @@
package android.telephony.ims.internal;
import android.annotation.IntDef;
-import android.annotation.SystemApi;
import android.os.RemoteException;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
@@ -37,6 +36,7 @@ import java.lang.annotation.RetentionPolicy;
public class SmsImplBase {
private static final String LOG_TAG = "SmsImplBase";
+ /** @hide */
@IntDef({
SEND_STATUS_OK,
SEND_STATUS_ERROR,
@@ -68,6 +68,7 @@ public class SmsImplBase {
*/
public static final int SEND_STATUS_ERROR_FALLBACK = 4;
+ /** @hide */
@IntDef({
DELIVER_STATUS_OK,
DELIVER_STATUS_ERROR
@@ -84,6 +85,7 @@ public class SmsImplBase {
*/
public static final int DELIVER_STATUS_ERROR = 2;
+ /** @hide */
@IntDef({
STATUS_REPORT_STATUS_OK,
STATUS_REPORT_STATUS_ERROR
@@ -114,9 +116,9 @@ public class SmsImplBase {
* @hide
*/
public final void registerSmsListener(IImsSmsListener listener) {
- synchronized (mLock) {
- mListener = listener;
- }
+ synchronized (mLock) {
+ mListener = listener;
+ }
}
/**
@@ -128,8 +130,8 @@ public class SmsImplBase {
* callbacks for this specific message.
* @param messageRef the message reference.
* @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
* @param smsc the Short Message Service Center address.
- * {@link SmsMessage#FORMAT_3GPP2}.
* @param isRetry whether it is a retry of an already attempted message or not.
* @param pdu PDUs representing the contents of the message.
*/
@@ -154,7 +156,7 @@ public class SmsImplBase {
* @param messageRef the message reference
*/
public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
-
+ Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
}
/**
@@ -168,7 +170,7 @@ public class SmsImplBase {
* @param messageRef the message reference
*/
public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
-
+ Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
}
/**
@@ -193,7 +195,6 @@ public class SmsImplBase {
}
try {
mListener.onSmsReceived(token, format, pdu);
- acknowledgeSms(token, 0, DELIVER_STATUS_OK);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage());
acknowledgeSms(token, 0, DELIVER_STATUS_ERROR);
diff --git a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
new file mode 100644
index 000000000000..113dad4696ef
--- /dev/null
+++ b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
@@ -0,0 +1,271 @@
+/*
+ * 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.internal.stub;
+
+import android.annotation.IntDef;
+import android.os.RemoteException;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.telephony.ims.internal.feature.MmTelFeature;
+import android.util.Log;
+
+import com.android.ims.internal.IImsSmsListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base implementation for SMS over IMS.
+ *
+ * Any service wishing to provide SMS over IMS should extend this class and implement all methods
+ * that the service supports.
+ * @hide
+ */
+public class SmsImplBase {
+ private static final String LOG_TAG = "SmsImplBase";
+
+ @IntDef({
+ SEND_STATUS_OK,
+ SEND_STATUS_ERROR,
+ SEND_STATUS_ERROR_RETRY,
+ SEND_STATUS_ERROR_FALLBACK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SendStatusResult {}
+ /**
+ * Message was sent successfully.
+ */
+ public static final int SEND_STATUS_OK = 1;
+
+ /**
+ * IMS provider failed to send the message and platform should not retry falling back to sending
+ * the message using the radio.
+ */
+ public static final int SEND_STATUS_ERROR = 2;
+
+ /**
+ * IMS provider failed to send the message and platform should retry again after setting TP-RD bit
+ * to high.
+ */
+ public static final int SEND_STATUS_ERROR_RETRY = 3;
+
+ /**
+ * IMS provider failed to send the message and platform should retry falling back to sending
+ * the message using the radio.
+ */
+ public static final int SEND_STATUS_ERROR_FALLBACK = 4;
+
+ @IntDef({
+ DELIVER_STATUS_OK,
+ DELIVER_STATUS_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeliverStatusResult {}
+ /**
+ * Message was delivered successfully.
+ */
+ public static final int DELIVER_STATUS_OK = 1;
+
+ /**
+ * Message was not delivered.
+ */
+ public static final int DELIVER_STATUS_ERROR = 2;
+
+ @IntDef({
+ STATUS_REPORT_STATUS_OK,
+ STATUS_REPORT_STATUS_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StatusReportResult {}
+
+ /**
+ * Status Report was set successfully.
+ */
+ public static final int STATUS_REPORT_STATUS_OK = 1;
+
+ /**
+ * Error while setting status report.
+ */
+ public static final int STATUS_REPORT_STATUS_ERROR = 2;
+
+
+ // Lock for feature synchronization
+ private final Object mLock = new Object();
+ private IImsSmsListener mListener;
+
+ /**
+ * Registers a listener responsible for handling tasks like delivering messages.
+ *
+ * @param listener listener to register.
+ *
+ * @hide
+ */
+ public final void registerSmsListener(IImsSmsListener listener) {
+ synchronized (mLock) {
+ mListener = listener;
+ }
+ }
+
+ /**
+ * This method will be triggered by the platform when the user attempts to send an SMS. This
+ * method should be implemented by the IMS providers to provide implementation of sending an SMS
+ * over IMS.
+ *
+ * @param token unique token generated by the platform that should be used when triggering
+ * callbacks for this specific message.
+ * @param messageRef the message reference.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param smsc the Short Message Service Center address.
+ * @param isRetry whether it is a retry of an already attempted message or not.
+ * @param pdu PDUs representing the contents of the message.
+ */
+ public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+ byte[] pdu) {
+ // Base implementation returns error. Should be overridden.
+ try {
+ onSendSmsResult(token, messageRef, SEND_STATUS_ERROR,
+ SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not send sms: " + e.getMessage());
+ }
+ }
+
+ /**
+ * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])}
+ * has been called to deliver the result to the IMS provider.
+ *
+ * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
+ * @param result result of delivering the message. Valid values are defined in
+ * {@link DeliverStatusResult}
+ * @param messageRef the message reference
+ */
+ public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
+ Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
+ }
+
+ /**
+ * This method will be triggered by the platform after
+ * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the
+ * result to the IMS provider.
+ *
+ * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+ * @param result result of delivering the message. Valid values are defined in
+ * {@link StatusReportResult}
+ * @param messageRef the message reference
+ */
+ public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
+ Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
+ }
+
+ /**
+ * This method should be triggered by the IMS providers when there is an incoming message. The
+ * platform will deliver the message to the messages database and notify the IMS provider of the
+ * result by calling {@link #acknowledgeSms(int, int, int)}.
+ *
+ * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
+ *
+ * @param token unique token generated by IMS providers that the platform will use to trigger
+ * callbacks for this message.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param pdu PDUs representing the contents of the message.
+ * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ */
+ public final void onSmsReceived(int token, String format, byte[] pdu)
+ throws IllegalStateException {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new IllegalStateException("Feature not ready.");
+ }
+ try {
+ mListener.onSmsReceived(token, format, pdu);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage());
+ acknowledgeSms(token, 0, DELIVER_STATUS_ERROR);
+ }
+ }
+ }
+
+ /**
+ * This method should be triggered by the IMS providers to pass the result of the sent message
+ * to the platform.
+ *
+ * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
+ *
+ * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+ * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+ * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult}
+ * @param reason reason in case status is failure. Valid values are:
+ * {@link SmsManager#RESULT_ERROR_NONE},
+ * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
+ * {@link SmsManager#RESULT_ERROR_RADIO_OFF},
+ * {@link SmsManager#RESULT_ERROR_NULL_PDU},
+ * {@link SmsManager#RESULT_ERROR_NO_SERVICE},
+ * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
+ * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
+ * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED}
+ * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ * @throws RemoteException if the connection to the framework is not available. If this happens
+ * attempting to send the SMS should be aborted.
+ */
+ public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
+ int reason) throws IllegalStateException, RemoteException {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new IllegalStateException("Feature not ready.");
+ }
+ mListener.onSendSmsResult(token, messageRef, status, reason);
+ }
+ }
+
+ /**
+ * Sets the status report of the sent message.
+ *
+ * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+ * @param messageRef the message reference.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param pdu PDUs representing the content of the status report.
+ * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ */
+ public final void onSmsStatusReportReceived(int token, int messageRef, String format,
+ byte[] pdu) {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new IllegalStateException("Feature not ready.");
+ }
+ try {
+ mListener.onSmsStatusReportReceived(token, messageRef, format, pdu);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
+ acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR);
+ }
+ }
+ }
+
+ /**
+ * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
+ * Provider.
+ *
+ * @return the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ */
+ public String getSmsFormat() {
+ return SmsMessage.FORMAT_3GPP;
+ }
+}
diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
index 52b3853ec5b6..cce39f4daf2a 100644
--- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
+++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
@@ -25,6 +25,7 @@ import com.android.ims.internal.IImsConfig;
import com.android.ims.internal.IImsEcbm;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsSmsListener;
import com.android.ims.internal.IImsUt;
import android.os.Message;
@@ -53,4 +54,11 @@ interface IImsMMTelFeature {
IImsEcbm getEcbmInterface();
void setUiTTYMode(int uiTtyMode, in Message onComplete);
IImsMultiEndpoint getMultiEndpointInterface();
+ // SMS APIs
+ void setSmsListener(IImsSmsListener l);
+ oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry,
+ in byte[] pdu);
+ oneway void acknowledgeSms(int token, int messageRef, int result);
+ oneway void acknowledgeSmsReport(int token, int messageRef, int result);
+ String getSmsFormat();
}
diff --git a/telephony/java/com/android/ims/internal/IImsSmsListener.aidl b/telephony/java/com/android/ims/internal/IImsSmsListener.aidl
new file mode 100644
index 000000000000..5a4b7e489025
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsSmsListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+/**
+ * See SmsImplBase for more information.
+ * {@hide}
+ */
+interface IImsSmsListener {
+ void onSendSmsResult(int token, int messageRef, int status, int reason);
+ void onSmsStatusReportReceived(int token, int messageRef, in String format,
+ in byte[] pdu);
+ void onSmsReceived(int token, in String format, in byte[] pdu);
+} \ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/IccCardConstants.java b/telephony/java/com/android/internal/telephony/IccCardConstants.java
index f3d9335f4052..d57f9afa01e9 100644
--- a/telephony/java/com/android/internal/telephony/IccCardConstants.java
+++ b/telephony/java/com/android/internal/telephony/IccCardConstants.java
@@ -30,16 +30,14 @@ public class IccCardConstants {
public static final String INTENT_VALUE_ICC_NOT_READY = "NOT_READY";
/* ABSENT means ICC is missing */
public static final String INTENT_VALUE_ICC_ABSENT = "ABSENT";
+ /* PRESENT means ICC is present */
+ public static final String INTENT_VALUE_ICC_PRESENT = "PRESENT";
/* CARD_IO_ERROR means for three consecutive times there was SIM IO error */
static public final String INTENT_VALUE_ICC_CARD_IO_ERROR = "CARD_IO_ERROR";
/* CARD_RESTRICTED means card is present but not usable due to carrier restrictions */
static public final String INTENT_VALUE_ICC_CARD_RESTRICTED = "CARD_RESTRICTED";
/* LOCKED means ICC is locked by pin or by network */
public static final String INTENT_VALUE_ICC_LOCKED = "LOCKED";
- //TODO: we can remove this state in the future if Bug 18489776 analysis
- //#42's first race condition is resolved
- /* INTERNAL LOCKED means ICC is locked by pin or by network */
- public static final String INTENT_VALUE_ICC_INTERNAL_LOCKED = "INTERNAL_LOCKED";
/* READY means ICC is ready to access */
public static final String INTENT_VALUE_ICC_READY = "READY";
/* IMSI means ICC IMSI is ready in property */
@@ -77,7 +75,8 @@ public class IccCardConstants {
NOT_READY, /** ordinal(6) == {@See TelephonyManager#SIM_STATE_NOT_READY} */
PERM_DISABLED, /** ordinal(7) == {@See TelephonyManager#SIM_STATE_PERM_DISABLED} */
CARD_IO_ERROR, /** ordinal(8) == {@See TelephonyManager#SIM_STATE_CARD_IO_ERROR} */
- CARD_RESTRICTED;/** ordinal(9) == {@See TelephonyManager#SIM_STATE_CARD_RESTRICTED} */
+ CARD_RESTRICTED,/** ordinal(9) == {@See TelephonyManager#SIM_STATE_CARD_RESTRICTED} */
+ LOADED; /** ordinal(9) == {@See TelephonyManager#SIM_STATE_LOADED} */
public boolean isPinLocked() {
return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED));
@@ -85,9 +84,9 @@ public class IccCardConstants {
public boolean iccCardExist() {
return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED)
- || (this == NETWORK_LOCKED) || (this == READY)
+ || (this == NETWORK_LOCKED) || (this == READY) || (this == NOT_READY)
|| (this == PERM_DISABLED) || (this == CARD_IO_ERROR)
- || (this == CARD_RESTRICTED));
+ || (this == CARD_RESTRICTED) || (this == LOADED));
}
public static State intToState(int state) throws IllegalArgumentException {
@@ -102,6 +101,7 @@ public class IccCardConstants {
case 7: return PERM_DISABLED;
case 8: return CARD_IO_ERROR;
case 9: return CARD_RESTRICTED;
+ case 10: return LOADED;
default:
throw new IllegalArgumentException();
}
diff --git a/tests/UiBench/src/com/android/test/uibench/leanback/BrowseFragment.java b/tests/UiBench/src/com/android/test/uibench/leanback/BrowseFragment.java
index 11ea36132dcc..4ab38a66d504 100644
--- a/tests/UiBench/src/com/android/test/uibench/leanback/BrowseFragment.java
+++ b/tests/UiBench/src/com/android/test/uibench/leanback/BrowseFragment.java
@@ -24,6 +24,7 @@ public class BrowseFragment extends android.support.v17.leanback.app.BrowseSuppo
@Override
public void onCreate(Bundle savedInstanceState) {
+ TestHelper.initHeaderState(this);
super.onCreate(savedInstanceState);
BitmapLoader.clear();
TestHelper.initBackground(getActivity());
diff --git a/tests/UiBench/src/com/android/test/uibench/leanback/TestHelper.java b/tests/UiBench/src/com/android/test/uibench/leanback/TestHelper.java
index 2bf388501ba0..bf408f7475ac 100644
--- a/tests/UiBench/src/com/android/test/uibench/leanback/TestHelper.java
+++ b/tests/UiBench/src/com/android/test/uibench/leanback/TestHelper.java
@@ -40,6 +40,7 @@ public class TestHelper {
public static final String EXTRA_CARD_ROUND_RECT = "extra_card_round_rect";
public static final String EXTRA_ENTRANCE_TRANSITION = "extra_entrance_transition";
public static final String EXTRA_BITMAP_UPLOAD = "extra_bitmap_upload";
+ public static final String EXTRA_SHOW_FAST_LANE = "extra_show_fast_lane";
/**
* Dont change the default values, they gave baseline for measuring the performance
@@ -53,6 +54,7 @@ public class TestHelper {
static final boolean DEFAULT_CARD_SHADOW = true;
static final boolean DEFAULT_CARD_ROUND_RECT = true;
static final boolean DEFAULT_BITMAP_UPLOAD = true;
+ static final boolean DEFAULT_SHOW_FAST_LANE = true;
static long sCardIdSeed = 0;
static long sRowIdSeed = 0;
@@ -235,4 +237,11 @@ public class TestHelper {
manager.setBitmap(bitmap);
}
}
+
+ public static void initHeaderState(BrowseFragment fragment) {
+ if (!fragment.getActivity().getIntent()
+ .getBooleanExtra(EXTRA_SHOW_FAST_LANE, DEFAULT_SHOW_FAST_LANE)) {
+ fragment.setHeadersState(BrowseFragment.HEADERS_HIDDEN);
+ }
+ }
}
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 9905f827d663..95bf9210ba97 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -47,6 +47,13 @@ static bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const Stri
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
}
+template <typename T>
+static bool less_than_struct_with_name_and_id(const std::unique_ptr<T>& lhs,
+ const std::pair<StringPiece, Maybe<uint8_t>>& rhs) {
+ int name_cmp = lhs->name.compare(0, lhs->name.size(), rhs.first.data(), rhs.first.size());
+ return name_cmp < 0 || (name_cmp == 0 && lhs->id < rhs.second);
+}
+
ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) const {
const auto last = packages.end();
auto iter = std::lower_bound(packages.begin(), last, name,
@@ -79,6 +86,22 @@ ResourceTablePackage* ResourceTable::CreatePackage(const StringPiece& name, Mayb
return package;
}
+ResourceTablePackage* ResourceTable::CreatePackageAllowingDuplicateNames(const StringPiece& name,
+ const Maybe<uint8_t> id) {
+ const auto last = packages.end();
+ auto iter = std::lower_bound(packages.begin(), last, std::make_pair(name, id),
+ less_than_struct_with_name_and_id<ResourceTablePackage>);
+
+ if (iter != last && name == (*iter)->name && id == (*iter)->id) {
+ return iter->get();
+ }
+
+ std::unique_ptr<ResourceTablePackage> new_package = util::make_unique<ResourceTablePackage>();
+ new_package->name = name.to_string();
+ new_package->id = id;
+ return packages.emplace(iter, std::move(new_package))->get();
+}
+
ResourceTablePackage* ResourceTable::FindOrCreatePackage(const StringPiece& name) {
const auto last = packages.end();
auto iter = std::lower_bound(packages.begin(), last, name,
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 95e30c442042..374fe1ea66a1 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -229,6 +229,11 @@ class ResourceTable {
ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {});
+ // Attempts to find a package having the specified name and ID. If not found, a new package
+ // of the specified parameters is created and returned.
+ ResourceTablePackage* CreatePackageAllowingDuplicateNames(const android::StringPiece& name,
+ const Maybe<uint8_t> id);
+
std::unique_ptr<ResourceTable> Clone() const;
// The string pool used by this resource table. Values that reference strings must use
@@ -239,7 +244,8 @@ class ResourceTable {
// destroyed.
StringPool string_pool;
- // The list of packages in this table, sorted alphabetically by package name.
+ // The list of packages in this table, sorted alphabetically by package name and increasing
+ // package ID (missing ID being the lowest).
std::vector<std::unique_ptr<ResourceTablePackage>> packages;
// Set of dynamic packages that this table may reference. Their package names get encoded
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 8552195d338d..069360e3d6f3 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -306,10 +306,25 @@ message FileReference {
}
// A value that represents a primitive data type (float, int, boolean, etc.).
-// Corresponds to the fields (type/data) of the C struct android::Res_value.
message Primitive {
- uint32 type = 1;
- uint32 data = 2;
+ message NullType {
+ }
+ message EmptyType {
+ }
+ oneof oneof_value {
+ NullType null_value = 1;
+ EmptyType empty_value = 2;
+ float float_value = 3;
+ float dimension_value = 4;
+ float fraction_value = 5;
+ int32 int_decimal_value = 6;
+ uint32 int_hexidecimal_value = 7;
+ bool boolean_value = 8;
+ uint32 color_argb8_value = 9;
+ uint32 color_rgb8_value = 10;
+ uint32 color_argb4_value = 11;
+ uint32 color_rgb4_value = 12;
+ }
}
// A value that represents an XML attribute and what values it accepts.
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index d8635a989bed..81bc2c88939e 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -23,6 +23,7 @@
#include "Locale.h"
#include "ResourceTable.h"
#include "ResourceUtils.h"
+#include "ResourceValues.h"
#include "ValueVisitor.h"
using ::android::ResStringPool;
@@ -380,7 +381,8 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
std::map<ResourceId, ResourceNameRef> id_index;
- ResourceTablePackage* pkg = out_table->CreatePackage(pb_package.package_name(), id);
+ ResourceTablePackage* pkg =
+ out_table->CreatePackageAllowingDuplicateNames(pb_package.package_name(), id);
for (const pb::Type& pb_type : pb_package.type()) {
const ResourceType* res_type = ParseResourceType(pb_type.name());
if (res_type == nullptr) {
@@ -761,8 +763,66 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
case pb::Item::kPrim: {
const pb::Primitive& pb_prim = pb_item.prim();
- return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()),
- pb_prim.data());
+ android::Res_value val = {};
+ switch (pb_prim.oneof_value_case()) {
+ case pb::Primitive::kNullValue: {
+ val.dataType = android::Res_value::TYPE_NULL;
+ val.data = android::Res_value::DATA_NULL_UNDEFINED;
+ } break;
+ case pb::Primitive::kEmptyValue: {
+ val.dataType = android::Res_value::TYPE_NULL;
+ val.data = android::Res_value::DATA_NULL_EMPTY;
+ } break;
+ case pb::Primitive::kFloatValue: {
+ val.dataType = android::Res_value::TYPE_FLOAT;
+ float float_val = pb_prim.float_value();
+ val.data = *(uint32_t*)&float_val;
+ } break;
+ case pb::Primitive::kDimensionValue: {
+ val.dataType = android::Res_value::TYPE_DIMENSION;
+ float dimen_val = pb_prim.dimension_value();
+ val.data = *(uint32_t*)&dimen_val;
+ } break;
+ case pb::Primitive::kFractionValue: {
+ val.dataType = android::Res_value::TYPE_FRACTION;
+ float fraction_val = pb_prim.fraction_value();
+ val.data = *(uint32_t*)&fraction_val;
+ } break;
+ case pb::Primitive::kIntDecimalValue: {
+ val.dataType = android::Res_value::TYPE_INT_DEC;
+ val.data = static_cast<uint32_t>(pb_prim.int_decimal_value());
+ } break;
+ case pb::Primitive::kIntHexidecimalValue: {
+ val.dataType = android::Res_value::TYPE_INT_HEX;
+ val.data = pb_prim.int_hexidecimal_value();
+ } break;
+ case pb::Primitive::kBooleanValue: {
+ val.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+ val.data = pb_prim.boolean_value() ? 0xFFFFFFFF : 0x0;
+ } break;
+ case pb::Primitive::kColorArgb8Value: {
+ val.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
+ val.data = pb_prim.color_argb8_value();
+ } break;
+ case pb::Primitive::kColorRgb8Value: {
+ val.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
+ val.data = pb_prim.color_rgb8_value();
+ } break;
+ case pb::Primitive::kColorArgb4Value: {
+ val.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
+ val.data = pb_prim.color_argb4_value();
+ } break;
+ case pb::Primitive::kColorRgb4Value: {
+ val.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
+ val.data = pb_prim.color_rgb4_value();
+ } break;
+ default: {
+ LOG(FATAL) << "Unexpected Primitive type: "
+ << static_cast<uint32_t>(pb_prim.oneof_value_case());
+ return {};
+ } break;
+ }
+ return util::make_unique<BinaryPrimitive>(val);
} break;
case pb::Item::kId: {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 78f12814389d..e9622f54f6ae 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -436,8 +436,51 @@ class ValueSerializer : public ConstValueVisitor {
prim->Flatten(&val);
pb::Primitive* pb_prim = out_value_->mutable_item()->mutable_prim();
- pb_prim->set_type(val.dataType);
- pb_prim->set_data(val.data);
+
+ switch (val.dataType) {
+ case android::Res_value::TYPE_NULL: {
+ if (val.data == android::Res_value::DATA_NULL_UNDEFINED) {
+ pb_prim->set_allocated_null_value(new pb::Primitive_NullType());
+ } else if (val.data == android::Res_value::DATA_NULL_EMPTY) {
+ pb_prim->set_allocated_empty_value(new pb::Primitive_EmptyType());
+ } else {
+ LOG(FATAL) << "Unexpected data value for TYPE_NULL BinaryPrimitive: " << val.data;
+ }
+ } break;
+ case android::Res_value::TYPE_FLOAT: {
+ pb_prim->set_float_value(*(float*)&val.data);
+ } break;
+ case android::Res_value::TYPE_DIMENSION: {
+ pb_prim->set_dimension_value(*(float*)&val.data);
+ } break;
+ case android::Res_value::TYPE_FRACTION: {
+ pb_prim->set_fraction_value(*(float*)&val.data);
+ } break;
+ case android::Res_value::TYPE_INT_DEC: {
+ pb_prim->set_int_decimal_value(static_cast<int32_t>(val.data));
+ } break;
+ case android::Res_value::TYPE_INT_HEX: {
+ pb_prim->set_int_hexidecimal_value(val.data);
+ } break;
+ case android::Res_value::TYPE_INT_BOOLEAN: {
+ pb_prim->set_boolean_value(static_cast<bool>(val.data));
+ } break;
+ case android::Res_value::TYPE_INT_COLOR_ARGB8: {
+ pb_prim->set_color_argb8_value(val.data);
+ } break;
+ case android::Res_value::TYPE_INT_COLOR_RGB8: {
+ pb_prim->set_color_rgb8_value(val.data);
+ } break;
+ case android::Res_value::TYPE_INT_COLOR_ARGB4: {
+ pb_prim->set_color_argb4_value(val.data);
+ } break;
+ case android::Res_value::TYPE_INT_COLOR_RGB4: {
+ pb_prim->set_color_rgb4_value(val.data);
+ } break;
+ default:
+ LOG(FATAL) << "Unexpected BinaryPrimitive type: " << val.dataType;
+ break;
+ }
}
void Visit(const Attribute* attr) override {
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index ccba5c652822..9081ab6abd0a 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -254,6 +254,111 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
EXPECT_THAT(child_text->text, StrEq("woah there"));
}
+TEST(ProtoSerializeTest, SerializeAndDeserializePrimitives) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddValue("android:bool/boolean_true",
+ test::BuildPrimitive(android::Res_value::TYPE_INT_BOOLEAN, true))
+ .AddValue("android:bool/boolean_false",
+ test::BuildPrimitive(android::Res_value::TYPE_INT_BOOLEAN, false))
+ .AddValue("android:color/color_rgb8", ResourceUtils::TryParseColor("#AABBCC"))
+ .AddValue("android:color/color_argb8", ResourceUtils::TryParseColor("#11223344"))
+ .AddValue("android:color/color_rgb4", ResourceUtils::TryParseColor("#DEF"))
+ .AddValue("android:color/color_argb4", ResourceUtils::TryParseColor("#5678"))
+ .AddValue("android:integer/integer_444", ResourceUtils::TryParseInt("444"))
+ .AddValue("android:integer/integer_neg_333", ResourceUtils::TryParseInt("-333"))
+ .AddValue("android:integer/hex_int_abcd", ResourceUtils::TryParseInt("0xABCD"))
+ .AddValue("android:dimen/dimen_1.39mm", ResourceUtils::TryParseFloat("1.39mm"))
+ .AddValue("android:fraction/fraction_27", ResourceUtils::TryParseFloat("27%"))
+ .AddValue("android:integer/null", ResourceUtils::MakeEmpty())
+ .Build();
+
+ pb::ResourceTable pb_table;
+ SerializeTableToPb(*table, &pb_table);
+
+ test::TestFile file_a("res/layout/main.xml");
+ MockFileCollection files;
+ EXPECT_CALL(files, FindFile(Eq("res/layout/main.xml")))
+ .WillRepeatedly(::testing::Return(&file_a));
+
+ ResourceTable new_table;
+ std::string error;
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
+ EXPECT_THAT(error, IsEmpty());
+
+ BinaryPrimitive* bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ &new_table, "android:bool/boolean_true", ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_BOOLEAN));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseBool("true")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:bool/boolean_false",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_BOOLEAN));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseBool("false")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:color/color_rgb8",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_COLOR_RGB8));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseColor("#AABBCC")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:color/color_argb8",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_COLOR_ARGB8));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseColor("#11223344")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:color/color_rgb4",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_COLOR_RGB4));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseColor("#DEF")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:color/color_argb4",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_COLOR_ARGB4));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseColor("#5678")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:integer/integer_444",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_DEC));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseInt("444")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ &new_table, "android:integer/integer_neg_333", ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_DEC));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseInt("-333")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ &new_table, "android:integer/hex_int_abcd", ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_HEX));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseInt("0xABCD")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:dimen/dimen_1.39mm",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_DIMENSION));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseFloat("1.39mm")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ &new_table, "android:fraction/fraction_27", ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_FRACTION));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseFloat("27%")->value.data));
+
+ bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:integer/null",
+ ConfigDescription::DefaultConfig(), "");
+ ASSERT_THAT(bp, NotNull());
+ EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_NULL));
+ EXPECT_THAT(bp->value.data, Eq(ResourceUtils::MakeEmpty()->value.data));
+}
+
static void ExpectConfigSerializes(const StringPiece& config_str) {
const ConfigDescription expected_config = test::ParseConfigOrDie(config_str);
pb::Configuration pb_config;
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index 15d39fdfd6ea..ec40a2229c33 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -270,6 +270,9 @@ def parse_fonts_xml(fonts_xml_path):
if index:
index = int(index)
+ if not path.exists(path.join(_fonts_dir, font_file)):
+ continue # Missing font is a valid case. Just ignore the missing font files.
+
record = FontRecord(
name,
frozenset(scripts),
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
new file mode 100644
index 000000000000..b8d2971e74bb
--- /dev/null
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+/**
+ * Interface for Soft AP callback.
+ *
+ * @hide
+ */
+oneway interface ISoftApCallback
+{
+ /**
+ * Service to manager callback providing current soft AP state. The possible
+ * parameter values listed are defined in WifiManager.java
+ *
+ * @param state new AP state. One of WIFI_AP_STATE_DISABLED,
+ * WIFI_AP_STATE_DISABLING, WIFI_AP_STATE_ENABLED,
+ * WIFI_AP_STATE_ENABLING, WIFI_AP_STATE_FAILED
+ * @param failureReason reason when in failed state. One of
+ * SAP_START_FAILURE_GENERAL, SAP_START_FAILURE_NO_CHANNEL
+ */
+ void onStateChanged(int state, int failureReason);
+
+ /**
+ * Service to manager callback providing number of connected clients.
+ *
+ * @param numClients number of connected clients
+ */
+ void onNumClientsChanged(int numClients);
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 70d6ce46bcf0..e9e61a546e14 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -23,15 +23,15 @@ import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.IProvisioningCallback;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.ScanSettings;
-import android.net.wifi.ScanResult;
+import android.net.DhcpInfo;
+import android.net.Network;
+import android.net.wifi.ISoftApCallback;
import android.net.wifi.PasspointManagementObjectDefinition;
+import android.net.wifi.ScanResult;
+import android.net.wifi.ScanSettings;
import android.net.wifi.WifiActivityEnergyInfo;
-import android.net.Network;
-
-import android.net.DhcpInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
import android.os.Messenger;
import android.os.ResultReceiver;
@@ -178,5 +178,9 @@ interface IWifiManager
void restoreSupplicantBackupData(in byte[] supplicantData, in byte[] ipConfigData);
void startSubscriptionProvisioning(in OsuProvider provider, in IProvisioningCallback callback);
+
+ void registerSoftApCallback(in IBinder binder, in ISoftApCallback callback, int callbackIdentifier);
+
+ void unregisterSoftApCallback(int callbackIdentifier);
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index aa75a0703a84..99080d6cc2c0 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -16,6 +16,7 @@
package android.net.wifi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -55,6 +56,8 @@ import com.android.server.net.NetworkPinner;
import dalvik.system.CloseGuard;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.util.Collections;
@@ -432,6 +435,17 @@ public class WifiManager {
*/
public static final String EXTRA_WIFI_AP_MODE = "wifi_ap_mode";
+ /** @hide */
+ @IntDef(flag = false, prefix = { "WIFI_AP_STATE_" }, value = {
+ WIFI_AP_STATE_DISABLING,
+ WIFI_AP_STATE_DISABLED,
+ WIFI_AP_STATE_ENABLING,
+ WIFI_AP_STATE_ENABLED,
+ WIFI_AP_STATE_FAILED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiApState {}
+
/**
* Wi-Fi AP is currently being disabled. The state will change to
* {@link #WIFI_AP_STATE_DISABLED} if it finishes successfully.
@@ -486,6 +500,14 @@ public class WifiManager {
@SystemApi
public static final int WIFI_AP_STATE_FAILED = 14;
+ /** @hide */
+ @IntDef(flag = false, prefix = { "SAP_START_FAILURE_" }, value = {
+ SAP_START_FAILURE_GENERAL,
+ SAP_START_FAILURE_NO_CHANNEL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SapStartFailure {}
+
/**
* If WIFI AP start failed, this reason code means there is no legal channel exists on
* user selected band by regulatory
@@ -2324,6 +2346,119 @@ public class WifiManager {
}
/**
+ * Base class for soft AP callback. Should be extended by applications and set when calling
+ * {@link WifiManager#registerSoftApCallback(SoftApCallback, Handler)}.
+ *
+ * @hide
+ */
+ public interface SoftApCallback {
+ /**
+ * Called when soft AP state changes.
+ *
+ * @param state new new AP state. One of {@link #WIFI_AP_STATE_DISABLED},
+ * {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
+ * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
+ * @param failureReason reason when in failed state. One of
+ * {@link #SAP_START_FAILURE_GENERAL}, {@link #SAP_START_FAILURE_NO_CHANNEL}
+ */
+ public abstract void onStateChanged(@WifiApState int state,
+ @SapStartFailure int failureReason);
+
+ /**
+ * Called when number of connected clients to soft AP changes.
+ *
+ * @param numClients number of connected clients
+ */
+ public abstract void onNumClientsChanged(int numClients);
+ }
+
+ /**
+ * Callback proxy for SoftApCallback objects.
+ *
+ * @hide
+ */
+ private static class SoftApCallbackProxy extends ISoftApCallback.Stub {
+ private final Handler mHandler;
+ private final SoftApCallback mCallback;
+
+ SoftApCallbackProxy(Looper looper, SoftApCallback callback) {
+ mHandler = new Handler(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onStateChanged(int state, int failureReason) throws RemoteException {
+ Log.v(TAG, "SoftApCallbackProxy: onStateChanged: state=" + state + ", failureReason=" +
+ failureReason);
+ mHandler.post(() -> {
+ mCallback.onStateChanged(state, failureReason);
+ });
+ }
+
+ @Override
+ public void onNumClientsChanged(int numClients) throws RemoteException {
+ Log.v(TAG, "SoftApCallbackProxy: onNumClientsChanged: numClients=" + numClients);
+ mHandler.post(() -> {
+ mCallback.onNumClientsChanged(numClients);
+ });
+ }
+ }
+
+ /**
+ * Registers a callback for Soft AP. See {@link SoftApCallback}. Caller will receive the current
+ * soft AP state and number of connected devices immediately after a successful call to this API
+ * via callback. Note that receiving an immediate WIFI_AP_STATE_FAILED value for soft AP state
+ * indicates that the latest attempt to start soft AP has failed. Caller can unregister a
+ * previously registered callback using {@link unregisterSoftApCallback}
+ * <p>
+ * Applications should have the
+ * {@link android.Manifest.permission#NETWORK_SETTINGS NETWORK_SETTINGS} permission. Callers
+ * without the permission will trigger a {@link java.lang.SecurityException}.
+ * <p>
+ *
+ * @param callback Callback for soft AP events
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code callback}
+ * object. If null, then the application's main thread will be used.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void registerSoftApCallback(@NonNull SoftApCallback callback,
+ @Nullable Handler handler) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "registerSoftApCallback: callback=" + callback + ", handler=" + handler);
+
+ Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper();
+ Binder binder = new Binder();
+ try {
+ mService.registerSoftApCallback(binder, new SoftApCallbackProxy(looper, callback),
+ callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allow callers to unregister a previously registered callback. After calling this method,
+ * applications will no longer receive soft AP events.
+ *
+ * @param callback Callback to unregister for soft AP events
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void unregisterSoftApCallback(@NonNull SoftApCallback callback) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "unregisterSoftApCallback: callback=" + callback);
+
+ try {
+ mService.unregisterSoftApCallback(callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* LocalOnlyHotspotReservation that contains the {@link WifiConfiguration} for the active
* LocalOnlyHotspot request.
* <p>
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 0df5615385fe..4b5f6452d392 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -24,11 +24,19 @@ import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_INCOMP
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_NO_CHANNEL;
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED;
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.REQUEST_REGISTERED;
+import static android.net.wifi.WifiManager.SAP_START_FAILURE_GENERAL;
+import static android.net.wifi.WifiManager.SAP_START_FAILURE_NO_CHANNEL;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;
import android.content.Context;
@@ -37,6 +45,7 @@ import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
import android.net.wifi.WifiManager.LocalOnlyHotspotSubscription;
+import android.net.wifi.WifiManager.SoftApCallback;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -66,6 +75,7 @@ public class WifiManagerTest {
@Mock ApplicationInfo mApplicationInfo;
@Mock WifiConfiguration mApConfig;
@Mock IBinder mAppBinder;
+ @Mock SoftApCallback mSoftApCallback;
private Handler mHandler;
private TestLooper mLooper;
@@ -632,6 +642,149 @@ public class WifiManagerTest {
}
/**
+ * Verify an IllegalArgumentException is thrown if callback is not provided.
+ */
+ @Test
+ public void registerSoftApCallbackThrowsIllegalArgumentExceptionOnNullArgumentForCallback() {
+ try {
+ mWifiManager.registerSoftApCallback(null, mHandler);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ /**
+ * Verify an IllegalArgumentException is thrown if callback is not provided.
+ */
+ @Test
+ public void unregisterSoftApCallbackThrowsIllegalArgumentExceptionOnNullArgumentForCallback() {
+ try {
+ mWifiManager.unregisterSoftApCallback(null);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ /**
+ * Verify main looper is used when handler is not provided.
+ */
+ @Test
+ public void registerSoftApCallbackUsesMainLooperOnNullArgumentForHandler() {
+ when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+ mWifiManager.registerSoftApCallback(mSoftApCallback, null);
+ verify(mContext).getMainLooper();
+ }
+
+ /**
+ * Verify the call to registerSoftApCallback goes to WifiServiceImpl.
+ */
+ @Test
+ public void registerSoftApCallbackCallGoesToWifiServiceImpl() throws Exception {
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class),
+ any(ISoftApCallback.Stub.class), anyInt());
+ }
+
+ /**
+ * Verify the call to unregisterSoftApCallback goes to WifiServiceImpl.
+ */
+ @Test
+ public void unregisterSoftApCallbackCallGoesToWifiServiceImpl() throws Exception {
+ ArgumentCaptor<Integer> callbackIdentifier = ArgumentCaptor.forClass(Integer.class);
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class),
+ any(ISoftApCallback.Stub.class), callbackIdentifier.capture());
+
+ mWifiManager.unregisterSoftApCallback(mSoftApCallback);
+ verify(mWifiService).unregisterSoftApCallback(eq((int) callbackIdentifier.getValue()));
+ }
+
+ /*
+ * Verify client provided callback is being called through callback proxy
+ */
+ @Test
+ public void softApCallbackProxyCallsOnStateChanged() throws Exception {
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+ mLooper.dispatchAll();
+ verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+ }
+
+ /*
+ * Verify client provided callback is being called through callback proxy
+ */
+ @Test
+ public void softApCallbackProxyCallsOnNumClientsChanged() throws Exception {
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ final int testNumClients = 3;
+ callbackCaptor.getValue().onNumClientsChanged(testNumClients);
+ mLooper.dispatchAll();
+ verify(mSoftApCallback).onNumClientsChanged(testNumClients);
+ }
+
+ /*
+ * Verify client provided callback is being called through callback proxy on multiple events
+ */
+ @Test
+ public void softApCallbackProxyCallsOnMultipleUpdates() throws Exception {
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ final int testNumClients = 5;
+ callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_ENABLING, 0);
+ callbackCaptor.getValue().onNumClientsChanged(testNumClients);
+ callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL);
+
+ mLooper.dispatchAll();
+ verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLING, 0);
+ verify(mSoftApCallback).onNumClientsChanged(testNumClients);
+ verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL);
+ }
+
+ /*
+ * Verify client provided callback is being called on the correct thread
+ */
+ @Test
+ public void softApCallbackIsCalledOnCorrectThread() throws Exception {
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ TestLooper altLooper = new TestLooper();
+ Handler altHandler = new Handler(altLooper.getLooper());
+ mWifiManager.registerSoftApCallback(mSoftApCallback, altHandler);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+ altLooper.dispatchAll();
+ verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+ }
+
+ /**
+ * Verify that the handler provided by the caller is used for registering soft AP callback.
+ */
+ @Test
+ public void testCorrectLooperIsUsedForSoftApCallbackHandler() throws Exception {
+ mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
+ mLooper.dispatchAll();
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class),
+ any(ISoftApCallback.Stub.class), anyInt());
+ verify(mContext, never()).getMainLooper();
+ }
+
+ /**
* Verify that the handler provided by the caller is used for the observer.
*/
@Test